diff mbox

[v4] media: Driver for Toshiba et8ek8 5MP sensor

Message ID 20161023200355.GA5391@amd (mailing list archive)
State New, archived
Headers show

Commit Message

Pavel Machek Oct. 23, 2016, 8:03 p.m. UTC
Add driver for et8ek8 sensor, found in Nokia N900 main camera. Can be
used for taking photos in 2.5MP resolution with fcam-dev.

Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>

---
From v4 I did cleanups to coding style and removed various oddities.

Comments

Sakari Ailus Oct. 23, 2016, 8:19 p.m. UTC | #1
Hi Pavel,

Thanks, this answered half of my questions already. ;-)

Do all the modes work for you currently btw.?

On Sun, Oct 23, 2016 at 10:03:55PM +0200, Pavel Machek wrote:
> 
> Add driver for et8ek8 sensor, found in Nokia N900 main camera. Can be
> used for taking photos in 2.5MP resolution with fcam-dev.
> 
> Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> ---
> From v4 I did cleanups to coding style and removed various oddities.
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 2669b4b..6d01e15 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -667,6 +667,7 @@ config VIDEO_S5K5BAF
>  	  camera sensor with an embedded SoC image signal processor.
>  
>  source "drivers/media/i2c/smiapp/Kconfig"
> +source "drivers/media/i2c/et8ek8/Kconfig"
>  
>  config VIDEO_S5C73M3
>  	tristate "Samsung S5C73M3 sensor support"
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 92773b2..5bc7bbe 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -2,6 +2,7 @@ msp3400-objs	:=	msp3400-driver.o msp3400-kthreads.o
>  obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
>  
>  obj-$(CONFIG_VIDEO_SMIAPP)	+= smiapp/
> +obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8/
>  obj-$(CONFIG_VIDEO_CX25840) += cx25840/
>  obj-$(CONFIG_VIDEO_M5MOLS)	+= m5mols/
>  obj-y				+= soc_camera/
> diff --git a/drivers/media/i2c/et8ek8/Kconfig b/drivers/media/i2c/et8ek8/Kconfig
> new file mode 100644
> index 0000000..1439936
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/Kconfig
> @@ -0,0 +1,6 @@
> +config VIDEO_ET8EK8
> +	tristate "ET8EK8 camera sensor support"
> +	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> +	---help---
> +	  This is a driver for the Toshiba ET8EK8 5 MP camera sensor.
> +	  It is used for example in Nokia N900 (RX-51).
> diff --git a/drivers/media/i2c/et8ek8/Makefile b/drivers/media/i2c/et8ek8/Makefile
> new file mode 100644
> index 0000000..66d1b7d
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/Makefile
> @@ -0,0 +1,2 @@
> +et8ek8-objs			+= et8ek8_mode.o et8ek8_driver.o
> +obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8.o
> diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> new file mode 100644
> index 0000000..0301e81
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> @@ -0,0 +1,1588 @@
> +/*
> + * et8ek8_driver.c
> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukkat76@gmail.com>
> + *
> + * Based on code from Toni Leinonen <toni.leinonen@offcode.fi>.
> + *
> + * This driver is based on the Micron MT9T012 camera imager driver
> + * (C) Texas Instruments.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/sort.h>
> +#include <linux/v4l2-mediabus.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "et8ek8_reg.h"
> +
> +#define ET8EK8_NAME		"et8ek8"
> +#define ET8EK8_PRIV_MEM_SIZE	128
> +#define ET8EK8_MAX_MSG		48
> +
> +struct et8ek8_sensor {
> +	struct v4l2_subdev subdev;
> +	struct media_pad pad;
> +	struct v4l2_mbus_framefmt format;
> +	struct gpio_desc *reset;
> +	struct regulator *vana;
> +	struct clk *ext_clk;
> +	u32 xclk_freq;
> +
> +	u16 version;
> +
> +	struct v4l2_ctrl_handler ctrl_handler;
> +	struct v4l2_ctrl *exposure;
> +	struct v4l2_ctrl *pixel_rate;
> +	struct et8ek8_reglist *current_reglist;
> +
> +	u8 priv_mem[ET8EK8_PRIV_MEM_SIZE];
> +
> +	struct mutex power_lock;
> +	int power_count;
> +};
> +
> +#define to_et8ek8_sensor(sd)	container_of(sd, struct et8ek8_sensor, subdev)
> +
> +enum et8ek8_versions {
> +	ET8EK8_REV_1 = 0x0001,
> +	ET8EK8_REV_2,
> +};
> +
> +/*
> + * This table describes what should be written to the sensor register
> + * for each gain value. The gain(index in the table) is in terms of
> + * 0.1EV, i.e. 10 indexes in the table give 2 time more gain [0] in
> + * the *analog gain, [1] in the digital gain
> + *
> + * Analog gain [dB] = 20*log10(regvalue/32); 0x20..0x100
> + */
> +static struct et8ek8_gain {
> +	u16 analog;
> +	u16 digital;
> +} const et8ek8_gain_table[] = {
> +	{ 32,    0},  /* x1 */
> +	{ 34,    0},
> +	{ 37,    0},
> +	{ 39,    0},
> +	{ 42,    0},
> +	{ 45,    0},
> +	{ 49,    0},
> +	{ 52,    0},
> +	{ 56,    0},
> +	{ 60,    0},
> +	{ 64,    0},  /* x2 */
> +	{ 69,    0},
> +	{ 74,    0},
> +	{ 79,    0},
> +	{ 84,    0},
> +	{ 91,    0},
> +	{ 97,    0},
> +	{104,    0},
> +	{111,    0},
> +	{119,    0},
> +	{128,    0},  /* x4 */
> +	{137,    0},
> +	{147,    0},
> +	{158,    0},
> +	{169,    0},
> +	{181,    0},
> +	{194,    0},
> +	{208,    0},
> +	{223,    0},
> +	{239,    0},
> +	{256,    0},  /* x8 */
> +	{256,   73},
> +	{256,  152},
> +	{256,  236},
> +	{256,  327},
> +	{256,  424},
> +	{256,  528},
> +	{256,  639},
> +	{256,  758},
> +	{256,  886},
> +	{256, 1023},  /* x16 */
> +};
> +
> +/* Register definitions */
> +#define REG_REVISION_NUMBER_L	0x1200
> +#define REG_REVISION_NUMBER_H	0x1201
> +
> +#define PRIV_MEM_START_REG	0x0008
> +#define PRIV_MEM_WIN_SIZE	8
> +
> +#define ET8EK8_I2C_DELAY	3	/* msec delay b/w accesses */
> +
> +#define USE_CRC			1
> +
> +/*
> + * Register access helpers
> + *
> + * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
> + * Returns zero if successful, or non-zero otherwise.
> + */
> +static int et8ek8_i2c_read_reg(struct i2c_client *client, u16 data_length,
> +			       u16 reg, u32 *val)
> +{
> +	int r;
> +	struct i2c_msg msg;
> +	unsigned char data[4];
> +
> +	if (!client->adapter)
> +		return -ENODEV;
> +	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
> +		return -EINVAL;
> +
> +	msg.addr = client->addr;
> +	msg.flags = 0;
> +	msg.len = 2;
> +	msg.buf = data;
> +
> +	/* high byte goes out first */
> +	data[0] = (u8) (reg >> 8);
> +	data[1] = (u8) (reg & 0xff);
> +	r = i2c_transfer(client->adapter, &msg, 1);
> +	if (r < 0)
> +		goto err;
> +
> +	msg.len = data_length;
> +	msg.flags = I2C_M_RD;
> +	r = i2c_transfer(client->adapter, &msg, 1);
> +	if (r < 0)
> +		goto err;
> +
> +	*val = 0;
> +	/* high byte comes first */
> +	if (data_length == ET8EK8_REG_8BIT)
> +		*val = data[0];
> +	else
> +		*val = (data[0] << 8) + data[1];
> +
> +	return 0;
> +
> +err:
> +	dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r);
> +
> +	return r;
> +}
> +
> +static void et8ek8_i2c_create_msg(struct i2c_client *client, u16 len, u16 reg,
> +				  u32 val, struct i2c_msg *msg,
> +				  unsigned char *buf)
> +{
> +	msg->addr = client->addr;
> +	msg->flags = 0; /* Write */
> +	msg->len = 2 + len;
> +	msg->buf = buf;
> +
> +	/* high byte goes out first */
> +	buf[0] = (u8) (reg >> 8);
> +	buf[1] = (u8) (reg & 0xff);
> +
> +	switch (len) {
> +	case ET8EK8_REG_8BIT:
> +		buf[2] = (u8) (val) & 0xff;
> +		break;
> +	case ET8EK8_REG_16BIT:
> +		buf[2] = (u8) (val >> 8) & 0xff;
> +		buf[3] = (u8) (val & 0xff);
> +		break;
> +	default:
> +		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
> +			  __func__);
> +	}
> +}
> +
> +/*
> + * A buffered write method that puts the wanted register write
> + * commands in a message list and passes the list to the i2c framework
> + */
> +static int et8ek8_i2c_buffered_write_regs(struct i2c_client *client,
> +					  const struct et8ek8_reg *wnext,
> +					  int cnt)
> +{
> +	struct i2c_msg msg[ET8EK8_MAX_MSG];
> +	unsigned char data[ET8EK8_MAX_MSG][6];
> +	int wcnt = 0;
> +	u16 reg, data_length;
> +	u32 val;
> +
> +	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
> +		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
> +		return -EINVAL;
> +	}
> +
> +	/* Create new write messages for all writes */
> +	while (wcnt < cnt) {
> +		data_length = wnext->type;
> +		reg = wnext->reg;
> +		val = wnext->val;
> +		wnext++;
> +
> +		et8ek8_i2c_create_msg(client, data_length, reg,
> +				    val, &msg[wcnt], &data[wcnt][0]);
> +
> +		/* Update write count */
> +		wcnt++;
> +	}
> +
> +	/* Now we send everything ... */
> +	return i2c_transfer(client->adapter, msg, wcnt);
> +}
> +
> +/*
> + * Write a list of registers to i2c device.
> + *
> + * The list of registers is terminated by ET8EK8_REG_TERM.
> + * Returns zero if successful, or non-zero otherwise.
> + */
> +static int et8ek8_i2c_write_regs(struct i2c_client *client,
> +				 const struct et8ek8_reg *regs)
> +{
> +	int r, cnt = 0;
> +	const struct et8ek8_reg *next;
> +
> +	if (!client->adapter)
> +		return -ENODEV;
> +
> +	if (!regs)
> +		return -EINVAL;
> +
> +	/* Initialize list pointers to the start of the list */
> +	next = regs;
> +
> +	do {
> +		/*
> +		 * We have to go through the list to figure out how
> +		 * many regular writes we have in a row
> +		 */
> +		while (next->type != ET8EK8_REG_TERM &&
> +		       next->type != ET8EK8_REG_DELAY) {
> +			/*
> +			 * Here we check that the actual length fields
> +			 * are valid
> +			 */
> +			if (WARN(next->type != ET8EK8_REG_8BIT &&
> +				 next->type != ET8EK8_REG_16BIT,
> +				 "Invalid type = %d", next->type)) {
> +				return -EINVAL;
> +			}
> +			/*
> +			 * Increment count of successive writes and
> +			 * read pointer
> +			 */
> +			cnt++;
> +			next++;
> +		}
> +
> +		/* Now we start writing ... */
> +		r = et8ek8_i2c_buffered_write_regs(client, regs, cnt);
> +
> +		/* ... and then check that everything was OK */
> +		if (r < 0) {
> +			dev_err(&client->dev, "i2c transfer error!\n");
> +			return r;
> +		}
> +
> +		/*
> +		 * If we ran into a sleep statement when going through
> +		 * the list, this is where we snooze for the required time
> +		 */
> +		if (next->type == ET8EK8_REG_DELAY) {
> +			msleep(next->val);
> +			/*
> +			 * ZZZ ...
> +			 * Update list pointers and cnt and start over ...
> +			 */
> +			next++;
> +			regs = next;
> +			cnt = 0;
> +		}
> +	} while (next->type != ET8EK8_REG_TERM);
> +
> +	return 0;
> +}
> +
> +/*
> + * Write to a 8/16-bit register.
> + * Returns zero if successful, or non-zero otherwise.
> + */
> +static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length,
> +				u16 reg, u32 val)
> +{
> +	int r;
> +	struct i2c_msg msg;
> +	unsigned char data[6];
> +
> +	if (!client->adapter)
> +		return -ENODEV;
> +	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
> +		return -EINVAL;
> +
> +	et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data);
> +
> +	r = i2c_transfer(client->adapter, &msg, 1);
> +	if (r < 0)
> +		dev_err(&client->dev,
> +			"wrote 0x%x to offset 0x%x error %d\n", val, reg, r);
> +	else
> +		r = 0; /* on success i2c_transfer() returns messages trasfered */
> +
> +	return r;
> +}
> +
> +static struct et8ek8_reglist *et8ek8_reglist_find_type(
> +		struct et8ek8_meta_reglist *meta,
> +		u16 type)
> +{
> +	struct et8ek8_reglist **next = &meta->reglist[0].ptr;
> +
> +	while (*next) {
> +		if ((*next)->type == type)
> +			return *next;
> +
> +		next++;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int et8ek8_i2c_reglist_find_write(struct i2c_client *client,
> +					 struct et8ek8_meta_reglist *meta,
> +					 u16 type)
> +{
> +	struct et8ek8_reglist *reglist;
> +
> +	reglist = et8ek8_reglist_find_type(meta, type);
> +	if (!reglist)
> +		return -EINVAL;
> +
> +	return et8ek8_i2c_write_regs(client, reglist->regs);
> +}
> +
> +static struct et8ek8_reglist **et8ek8_reglist_first(
> +		struct et8ek8_meta_reglist *meta)
> +{
> +	return &meta->reglist[0].ptr;
> +}
> +
> +static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist,
> +				   struct v4l2_mbus_framefmt *fmt)
> +{
> +	fmt->width = reglist->mode.window_width;
> +	fmt->height = reglist->mode.window_height;
> +
> +	if (reglist->mode.pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
> +		fmt->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
> +	else
> +		fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +}
> +
> +static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt(
> +		struct et8ek8_meta_reglist *meta,
> +		struct v4l2_mbus_framefmt *fmt)
> +{
> +	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
> +	struct et8ek8_reglist *best_match = NULL;
> +	struct et8ek8_reglist *best_other = NULL;
> +	struct v4l2_mbus_framefmt format;
> +	unsigned int max_dist_match = (unsigned int)-1;
> +	unsigned int max_dist_other = (unsigned int)-1;
> +
> +	/*
> +	 * Find the mode with the closest image size. The distance between
> +	 * image sizes is the size in pixels of the non-overlapping regions
> +	 * between the requested size and the frame-specified size.
> +	 *
> +	 * Store both the closest mode that matches the requested format, and
> +	 * the closest mode for all other formats. The best match is returned
> +	 * if found, otherwise the best mode with a non-matching format is
> +	 * returned.
> +	 */
> +	for (; *list; list++) {
> +		unsigned int dist;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		et8ek8_reglist_to_mbus(*list, &format);
> +
> +		dist = min(fmt->width, format.width)
> +		     * min(fmt->height, format.height);
> +		dist = format.width * format.height
> +		     + fmt->width * fmt->height - 2 * dist;
> +
> +
> +		if (fmt->code == format.code) {
> +			if (dist < max_dist_match || !best_match) {
> +				best_match = *list;
> +				max_dist_match = dist;
> +			}
> +		} else {
> +			if (dist < max_dist_other || !best_other) {
> +				best_other = *list;
> +				max_dist_other = dist;
> +			}
> +		}
> +	}
> +
> +	return best_match ? best_match : best_other;
> +}
> +
> +#define TIMEPERFRAME_AVG_FPS(t)						\
> +	(((t).denominator + ((t).numerator >> 1)) / (t).numerator)
> +
> +static struct et8ek8_reglist *et8ek8_reglist_find_mode_ival(
> +		struct et8ek8_meta_reglist *meta,
> +		struct et8ek8_reglist *current_reglist,
> +		struct v4l2_fract *timeperframe)
> +{
> +	int fps = TIMEPERFRAME_AVG_FPS(*timeperframe);
> +	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
> +	struct et8ek8_mode *current_mode = &current_reglist->mode;
> +
> +	for (; *list; list++) {
> +		struct et8ek8_mode *mode = &(*list)->mode;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		if (mode->window_width != current_mode->window_width ||
> +		    mode->window_height != current_mode->window_height)
> +			continue;
> +
> +		if (TIMEPERFRAME_AVG_FPS(mode->timeperframe) == fps)
> +			return *list;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int et8ek8_reglist_cmp(const void *a, const void *b)
> +{
> +	const struct et8ek8_reglist **list1 = (const struct et8ek8_reglist **)a,
> +		**list2 = (const struct et8ek8_reglist **)b;
> +
> +	/* Put real modes in the beginning. */
> +	if ((*list1)->type == ET8EK8_REGLIST_MODE &&
> +	    (*list2)->type != ET8EK8_REGLIST_MODE)
> +		return -1;
> +	if ((*list1)->type != ET8EK8_REGLIST_MODE &&
> +	    (*list2)->type == ET8EK8_REGLIST_MODE)
> +		return 1;
> +
> +	/* Descending width. */
> +	if ((*list1)->mode.window_width > (*list2)->mode.window_width)
> +		return -1;
> +	if ((*list1)->mode.window_width < (*list2)->mode.window_width)
> +		return 1;
> +
> +	if ((*list1)->mode.window_height > (*list2)->mode.window_height)
> +		return -1;
> +	if ((*list1)->mode.window_height < (*list2)->mode.window_height)
> +		return 1;
> +
> +	return 0;
> +}
> +
> +static int et8ek8_reglist_import(struct i2c_client *client,
> +				 struct et8ek8_meta_reglist *meta)
> +{
> +	int nlists = 0, i;
> +
> +	dev_info(&client->dev, "meta_reglist version %s\n", meta->version);
> +
> +	while (meta->reglist[nlists].ptr)
> +		nlists++;
> +
> +	if (!nlists)
> +		return -EINVAL;
> +
> +	sort(&meta->reglist[0].ptr, nlists, sizeof(meta->reglist[0].ptr),
> +	     et8ek8_reglist_cmp, NULL);
> +
> +	i = nlists;
> +	nlists = 0;
> +
> +	while (i--) {
> +		struct et8ek8_reglist *list;
> +
> +		list = meta->reglist[nlists].ptr;
> +
> +		dev_dbg(&client->dev,
> +		       "%s: type %d\tw %d\th %d\tfmt %x\tival %d/%d\tptr %p\n",
> +		       __func__,
> +		       list->type,
> +		       list->mode.window_width, list->mode.window_height,
> +		       list->mode.pixel_format,
> +		       list->mode.timeperframe.numerator,
> +		       list->mode.timeperframe.denominator,
> +		       (void *)meta->reglist[nlists].ptr);
> +
> +		nlists++;
> +	}
> +
> +	return 0;
> +}
> +
> +typedef unsigned int fixpoint8; /* .8 fixed point format. */
> +
> +/*
> + * Return time of one row in microseconds
> + * If the sensor is not set to any mode, return zero.
> + */
> +fixpoint8 et8ek8_get_row_time(struct et8ek8_sensor *sensor)
> +{
> +	unsigned int clock;	/* Pixel clock in Hz>>10 fixed point */
> +	fixpoint8 rt;	/* Row time in .8 fixed point */
> +
> +	if (!sensor->current_reglist)
> +		return 0;
> +
> +	clock = sensor->current_reglist->mode.pixel_clock;
> +	clock = (clock + (1 << 9)) >> 10;
> +	rt = sensor->current_reglist->mode.width * (1000000 >> 2);
> +	rt = (rt + (clock >> 1)) / clock;
> +
> +	return rt;
> +}
> +
> +/*
> + * Convert exposure time `us' to rows. Modify `us' to make it to
> + * correspond to the actual exposure time.
> + */
> +static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us)

Should a driver do something like this to begin with?

The smiapp driver does use the native unit of exposure (lines) for the
control and I think the et8ek8 driver should do so as well.

The HBLANK, VBLANK and PIXEL_RATE controls are used to provide the user with
enough information to perform the conversion (if necessary).

> +{
> +	unsigned int rows;	/* Exposure value as written to HW (ie. rows) */
> +	fixpoint8 rt;	/* Row time in .8 fixed point */
> +
> +	/* Assume that the maximum exposure time is at most ~8 s,
> +	 * and the maximum width (with blanking) ~8000 pixels.
> +	 * The formula here is in principle as simple as
> +	 *    rows = exptime / 1e6 / width * pixel_clock
> +	 * but to get accurate results while coping with value ranges,
> +	 * have to do some fixed point math.
> +	 */
> +
> +	rt = et8ek8_get_row_time(sensor);
> +	rows = ((*us << 8) + (rt >> 1)) / rt;
> +
> +	if (rows > sensor->current_reglist->mode.max_exp)
> +		rows = sensor->current_reglist->mode.max_exp;
> +
> +	/* Set the exposure time to the rounded value */
> +	*us = (rt * rows + (1 << 7)) >> 8;
> +
> +	return rows;
> +}
> +
> +/*
> + * Convert exposure time in rows to microseconds
> + */
> +static int et8ek8_exposure_rows_to_us(struct et8ek8_sensor *sensor, int rows)
> +{
> +	return (et8ek8_get_row_time(sensor) * rows + (1 << 7)) >> 8;
> +}
> +
> +/* Called to change the V4L2 gain control value. This function
> + * rounds and clamps the given value and updates the V4L2 control value.
> + * If power is on, also updates the sensor analog and digital gains.
> + * gain is in 0.1 EV (exposure value) units.
> + */
> +static int et8ek8_set_gain(struct et8ek8_sensor *sensor, s32 gain)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +	struct et8ek8_gain new;
> +	int r;
> +
> +	new = et8ek8_gain_table[gain];
> +
> +	/* FIXME: optimise I2C writes! */
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x124a, new.analog >> 8);
> +	if (r)
> +		return r;
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x1249, new.analog & 0xff);
> +	if (r)
> +		return r;
> +
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x124d, new.digital >> 8);
> +	if (r)
> +		return r;
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x124c, new.digital & 0xff);
> +
> +	return r;
> +}
> +
> +static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +	int cbh_mode, cbv_mode, tp_mode, din_sw, r1420, rval;
> +
> +	/* Values for normal mode */
> +	cbh_mode = 0;
> +	cbv_mode = 0;
> +	tp_mode  = 0;
> +	din_sw   = 0x00;
> +	r1420    = 0xF0;
> +
> +	if (mode) {
> +		/* Test pattern mode */
> +		if (mode < 5) {
> +			cbh_mode = 1;
> +			cbv_mode = 1;
> +			tp_mode  = mode + 3;
> +		} else {
> +			cbh_mode = 0;
> +			cbv_mode = 0;
> +			tp_mode  = mode - 4 + 3;
> +		}
> +
> +		din_sw   = 0x01;
> +		r1420    = 0xE0;
> +	}
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x111B,
> +				    tp_mode << 4);
> +	if (rval)
> +		return rval;
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1121,
> +				    cbh_mode << 7);
> +	if (rval)
> +		return rval;
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124,
> +				    cbv_mode << 7);
> +	if (rval)
> +		return rval;		
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x112C, din_sw);
> +	if (rval)
> +		return rval;
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420);
> +	return rval;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 controls
> + */
> +
> +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct et8ek8_sensor *sensor =
> +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +	int uninitialized_var(rows);
> +
> +	if (ctrl->id == V4L2_CID_EXPOSURE)
> +		rows = et8ek8_exposure_us_to_rows(sensor, (u32 *)&ctrl->val);
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_GAIN:
> +		return et8ek8_set_gain(sensor, ctrl->val);
> +
> +	case V4L2_CID_EXPOSURE:
> +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> +					    swab16(rows));
> +
> +	case V4L2_CID_TEST_PATTERN:
> +		return et8ek8_set_test_pattern(sensor, ctrl->val);
> +
> +	case V4L2_CID_PIXEL_RATE:
> +		/* For v4l2_ctrl_s_ctrl_int64() used internally. */
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = {
> +	.s_ctrl = et8ek8_set_ctrl,
> +};
> +
> +static const char * const et8ek8_test_pattern_menu[] = {
> +	"Normal",
> +	"Vertical colorbar",
> +	"Horizontal colorbar",
> +	"Scale",
> +	"Ramp",
> +	"Small vertical colorbar",
> +	"Small horizontal colorbar",
> +	"Small scale",
> +	"Small ramp",
> +};
> +
> +static int et8ek8_init_controls(struct et8ek8_sensor *sensor)
> +{
> +	u32 min, max;
> +
> +	v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4);
> +
> +	/* V4L2_CID_GAIN */
> +	v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> +			  V4L2_CID_GAIN, 0, ARRAY_SIZE(et8ek8_gain_table) - 1,
> +			  1, 0);
> +
> +	/* V4L2_CID_EXPOSURE */
> +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> +	max = et8ek8_exposure_rows_to_us(sensor,
> +				sensor->current_reglist->mode.max_exp);
> +	sensor->exposure =
> +		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> +				  V4L2_CID_EXPOSURE, min, max, min, max);
> +
> +	/* V4L2_CID_PIXEL_RATE */
> +	sensor->pixel_rate =
> +		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> +		V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
> +
> +	/* V4L2_CID_TEST_PATTERN */
> +	v4l2_ctrl_new_std_menu_items(&sensor->ctrl_handler,
> +				     &et8ek8_ctrl_ops, V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(et8ek8_test_pattern_menu) - 1,
> +				     0, 0, et8ek8_test_pattern_menu);
> +
> +	if (sensor->ctrl_handler.error)
> +		return sensor->ctrl_handler.error;
> +
> +	sensor->subdev.ctrl_handler = &sensor->ctrl_handler;
> +
> +	return 0;
> +}
> +
> +static void et8ek8_update_controls(struct et8ek8_sensor *sensor)
> +{
> +	struct v4l2_ctrl *ctrl = sensor->exposure;
> +	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
> +	u32 min, max, pixel_rate;
> +	static const int S = 8;
> +
> +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> +	max = et8ek8_exposure_rows_to_us(sensor, mode->max_exp);
> +
> +	/*
> +	 * Calculate average pixel clock per line. Assume buffers can spread
> +	 * the data over horizontal blanking time. Rounding upwards.
> +	 * Formula taken from stock Nokia N900 kernel.
> +	 */
> +	pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width;
> +	pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width;
> +
> +	v4l2_ctrl_lock(ctrl);
> +	ctrl->minimum = min;
> +	ctrl->maximum = max;
> +	ctrl->step = min;
> +	ctrl->default_value = max;
> +	ctrl->val = max;
> +	ctrl->cur.val = max;
> +	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
> +	v4l2_ctrl_unlock(ctrl);
> +}
> +
> +static int et8ek8_configure(struct et8ek8_sensor *sensor)
> +{
> +	struct v4l2_subdev *subdev = &sensor->subdev;
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	int rval;
> +
> +	rval = et8ek8_i2c_write_regs(client, sensor->current_reglist->regs);
> +	if (rval)
> +		goto fail;
> +
> +	/* Controls set while the power to the sensor is turned off are saved
> +	 * but not applied to the hardware. Now that we're about to start
> +	 * streaming apply all the current values to the hardware.
> +	 */
> +	rval = v4l2_ctrl_handler_setup(&sensor->ctrl_handler);
> +	if (rval)
> +		goto fail;
> +
> +	return 0;
> +
> +fail:
> +	dev_err(&client->dev, "sensor configuration failed\n");
> +
> +	return rval;
> +}
> +
> +static int et8ek8_stream_on(struct et8ek8_sensor *sensor)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +
> +	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0xb0);
> +}
> +
> +static int et8ek8_stream_off(struct et8ek8_sensor *sensor)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +
> +	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0x30);
> +}
> +
> +static int et8ek8_s_stream(struct v4l2_subdev *subdev, int streaming)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	int ret;
> +
> +	if (!streaming)
> +		return et8ek8_stream_off(sensor);
> +
> +	ret = et8ek8_configure(sensor);
> +	if (ret < 0)
> +		return ret;
> +
> +	return et8ek8_stream_on(sensor);
> +}
> +
> +/* --------------------------------------------------------------------------
> + * V4L2 subdev operations
> + */
> +
> +static int et8ek8_power_off(struct et8ek8_sensor *sensor)
> +{
> +	gpiod_set_value(sensor->reset, 0);
> +	udelay(1);
> +
> +	clk_disable_unprepare(sensor->ext_clk);
> +
> +	return regulator_disable(sensor->vana);
> +}
> +
> +static int et8ek8_power_on(struct et8ek8_sensor *sensor)
> +{
> +	struct v4l2_subdev *subdev = &sensor->subdev;
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	unsigned int xclk_freq;
> +	int val, rval;
> +
> +	rval = regulator_enable(sensor->vana);
> +	if (rval) {
> +		dev_err(&client->dev, "failed to enable vana regulator\n");
> +		return rval;
> +	}
> +
> +	if (sensor->current_reglist)
> +		xclk_freq = sensor->current_reglist->mode.ext_clock;
> +	else
> +		xclk_freq = sensor->xclk_freq;
> +
> +	rval = clk_set_rate(sensor->ext_clk, xclk_freq);
> +	if (rval < 0) {
> +		dev_err(&client->dev, "unable to set extclk clock freq to %u\n",
> +			xclk_freq);
> +		goto out;
> +	}
> +	rval = clk_prepare_enable(sensor->ext_clk);
> +	if (rval < 0) {
> +		dev_err(&client->dev, "failed to enable extclk\n");
> +		goto out;
> +	}
> +
> +	if (rval)
> +		goto out;
> +
> +	udelay(10); /* I wish this is a good value */
> +
> +	gpiod_set_value(sensor->reset, 1);
> +
> +	msleep(5000 * 1000 / xclk_freq + 1); /* Wait 5000 cycles */
> +
> +	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
> +					     ET8EK8_REGLIST_POWERON);
> +	if (rval)
> +		goto out;
> +
> +#ifdef USE_CRC
> +	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, 0x1263, &val);
> +	if (rval)
> +		goto out;
> +#if USE_CRC /* TODO get crc setting from DT */
> +	val |= BIT(4);
> +#else
> +	val &= ~BIT(4);
> +#endif
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1263, val);
> +	if (rval)
> +		goto out;
> +#endif
> +
> +out:
> +	if (rval)
> +		et8ek8_power_off(sensor);
> +
> +	return rval;
> +}
> +
> +/* --------------------------------------------------------------------------
> + * V4L2 subdev video operations
> + */
> +#define MAX_FMTS 4
> +static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev,
> +				 struct v4l2_subdev_pad_config *cfg,
> +				 struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct et8ek8_reglist **list =
> +			et8ek8_reglist_first(&meta_reglist);
> +	u32 pixelformat[MAX_FMTS];
> +	int npixelformat = 0;
> +
> +	if (code->index >= MAX_FMTS)
> +		return -EINVAL;
> +
> +	for (; *list; list++) {
> +		struct et8ek8_mode *mode = &(*list)->mode;
> +		int i;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		for (i = 0; i < npixelformat; i++) {
> +			if (pixelformat[i] == mode->pixel_format)
> +				break;
> +		}
> +		if (i != npixelformat)
> +			continue;
> +
> +		if (code->index == npixelformat) {
> +			if (mode->pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
> +				code->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
> +			else
> +				code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +			return 0;
> +		}
> +
> +		pixelformat[npixelformat] = mode->pixel_format;
> +		npixelformat++;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int et8ek8_enum_frame_size(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_pad_config *cfg,
> +				  struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	struct et8ek8_reglist **list =
> +			et8ek8_reglist_first(&meta_reglist);
> +	struct v4l2_mbus_framefmt format;
> +	int cmp_width = INT_MAX;
> +	int cmp_height = INT_MAX;
> +	int index = fse->index;
> +
> +	for (; *list; list++) {
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		et8ek8_reglist_to_mbus(*list, &format);
> +		if (fse->code != format.code)
> +			continue;
> +
> +		/* Assume that the modes are grouped by frame size. */
> +		if (format.width == cmp_width && format.height == cmp_height)
> +			continue;
> +
> +		cmp_width = format.width;
> +		cmp_height = format.height;
> +
> +		if (index-- == 0) {
> +			fse->min_width = format.width;
> +			fse->min_height = format.height;
> +			fse->max_width = format.width;
> +			fse->max_height = format.height;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int et8ek8_enum_frame_ival(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_pad_config *cfg,
> +				  struct v4l2_subdev_frame_interval_enum *fie)
> +{
> +	struct et8ek8_reglist **list =
> +			et8ek8_reglist_first(&meta_reglist);
> +	struct v4l2_mbus_framefmt format;
> +	int index = fie->index;
> +
> +	for (; *list; list++) {
> +		struct et8ek8_mode *mode = &(*list)->mode;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		et8ek8_reglist_to_mbus(*list, &format);
> +		if (fie->code != format.code)
> +			continue;
> +
> +		if (fie->width != format.width || fie->height != format.height)
> +			continue;
> +
> +		if (index-- == 0) {
> +			fie->interval = mode->timeperframe;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +__et8ek8_get_pad_format(struct et8ek8_sensor *sensor,
> +			struct v4l2_subdev_pad_config *cfg,
> +			unsigned int pad, enum v4l2_subdev_format_whence which)
> +{
> +	switch (which) {
> +	case V4L2_SUBDEV_FORMAT_TRY:
> +		return v4l2_subdev_get_try_format(&sensor->subdev, cfg, pad);
> +	case V4L2_SUBDEV_FORMAT_ACTIVE:
> +		return &sensor->format;
> +	default:
> +		return NULL;
> +	}
> +}
> +
> +static int et8ek8_get_pad_format(struct v4l2_subdev *subdev,
> +				 struct v4l2_subdev_pad_config *cfg,
> +				 struct v4l2_subdev_format *fmt)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct v4l2_mbus_framefmt *format;
> +
> +	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
> +	if (!format)
> +		return -EINVAL;
> +
> +	fmt->format = *format;
> +
> +	return 0;
> +}
> +
> +static int et8ek8_set_pad_format(struct v4l2_subdev *subdev,
> +				 struct v4l2_subdev_pad_config *cfg,
> +				 struct v4l2_subdev_format *fmt)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct v4l2_mbus_framefmt *format;
> +	struct et8ek8_reglist *reglist;
> +
> +	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
> +	if (!format)
> +		return -EINVAL;
> +
> +	reglist = et8ek8_reglist_find_mode_fmt(&meta_reglist, &fmt->format);
> +	et8ek8_reglist_to_mbus(reglist, &fmt->format);
> +	*format = fmt->format;
> +
> +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> +		sensor->current_reglist = reglist;
> +		et8ek8_update_controls(sensor);
> +	}
> +
> +	return 0;
> +}
> +
> +static int et8ek8_get_frame_interval(struct v4l2_subdev *subdev,
> +				     struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	memset(fi, 0, sizeof(*fi));
> +	fi->interval = sensor->current_reglist->mode.timeperframe;
> +
> +	return 0;
> +}
> +
> +static int et8ek8_set_frame_interval(struct v4l2_subdev *subdev,
> +				     struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct et8ek8_reglist *reglist;
> +
> +	reglist = et8ek8_reglist_find_mode_ival(&meta_reglist,
> +						sensor->current_reglist,
> +						&fi->interval);
> +
> +	if (!reglist)
> +		return -EINVAL;
> +
> +	if (sensor->current_reglist->mode.ext_clock != reglist->mode.ext_clock)
> +		return -EINVAL;
> +
> +	sensor->current_reglist = reglist;
> +	et8ek8_update_controls(sensor);
> +
> +	return 0;
> +}
> +
> +static int et8ek8_g_priv_mem(struct v4l2_subdev *subdev)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	unsigned int length = ET8EK8_PRIV_MEM_SIZE;
> +	unsigned int offset = 0;
> +	u8 *ptr  = sensor->priv_mem;
> +	int rval = 0;
> +
> +	/* Read the EEPROM window-by-window, each window 8 bytes */
> +	do {
> +		u8 buffer[PRIV_MEM_WIN_SIZE];
> +		struct i2c_msg msg;
> +		int bytes, i;
> +		int ofs;
> +
> +		/* Set the current window */
> +		rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x0001,
> +					    0xe0 | (offset >> 3));
> +		if (rval < 0)
> +			return rval;
> +
> +		/* Wait for status bit */
> +		for (i = 0; i < 1000; ++i) {
> +			u32 status;
> +
> +			rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> +						   0x0003, &status);
> +			if (rval < 0)
> +				return rval;
> +			if (!(status & 0x08))
> +				break;
> +			usleep_range(1000, 2000);
> +		};
> +
> +		if (i == 1000)
> +			return -EIO;
> +
> +		/* Read window, 8 bytes at once, and copy to user space */
> +		ofs = offset & 0x07;	/* Offset within this window */
> +		bytes = length + ofs > 8 ? 8-ofs : length;
> +		msg.addr = client->addr;
> +		msg.flags = 0;
> +		msg.len = 2;
> +		msg.buf = buffer;
> +		ofs += PRIV_MEM_START_REG;
> +		buffer[0] = (u8)(ofs >> 8);
> +		buffer[1] = (u8)(ofs & 0xFF);
> +
> +		rval = i2c_transfer(client->adapter, &msg, 1);
> +		if (rval < 0)
> +			return rval;
> +
> +		mdelay(ET8EK8_I2C_DELAY);
> +		msg.addr = client->addr;
> +		msg.len = bytes;
> +		msg.flags = I2C_M_RD;
> +		msg.buf = buffer;
> +		memset(buffer, 0, sizeof(buffer));
> +
> +		rval = i2c_transfer(client->adapter, &msg, 1);
> +		if (rval < 0)
> +			return rval;
> +
> +		rval = 0;
> +		memcpy(ptr, buffer, bytes);
> +
> +		length -= bytes;
> +		offset += bytes;
> +		ptr += bytes;
> +	} while (length > 0);
> +
> +	return rval;
> +}
> +
> +static int et8ek8_dev_init(struct v4l2_subdev *subdev)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	int rval, rev_l, rev_h;
> +
> +	rval = et8ek8_power_on(sensor);
> +	if (rval) {
> +		dev_err(&client->dev, "could not power on\n");
> +		return rval;
> +	}
> +
> +	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> +				   REG_REVISION_NUMBER_L, &rev_l);
> +	if (!rval)
> +		rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> +					   REG_REVISION_NUMBER_H, &rev_h);
> +	if (rval) {
> +		dev_err(&client->dev, "no et8ek8 sensor detected\n");
> +		goto out_poweroff;
> +	}
> +
> +	sensor->version = (rev_h << 8) + rev_l;
> +	if (sensor->version != ET8EK8_REV_1 && sensor->version != ET8EK8_REV_2)
> +		dev_info(&client->dev,
> +			 "unknown version 0x%x detected, continuing anyway\n",
> +			 sensor->version);
> +
> +	rval = et8ek8_reglist_import(client, &meta_reglist);
> +	if (rval) {
> +		dev_err(&client->dev,
> +			"invalid register list %s, import failed\n",
> +			ET8EK8_NAME);
> +		goto out_poweroff;
> +	}
> +
> +	sensor->current_reglist = et8ek8_reglist_find_type(&meta_reglist,
> +							   ET8EK8_REGLIST_MODE);
> +	if (!sensor->current_reglist) {
> +		dev_err(&client->dev,
> +			"invalid register list %s, no mode found\n",
> +			ET8EK8_NAME);
> +		rval = -ENODEV;
> +		goto out_poweroff;
> +	}
> +
> +	et8ek8_reglist_to_mbus(sensor->current_reglist, &sensor->format);
> +
> +	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
> +					     ET8EK8_REGLIST_POWERON);
> +	if (rval) {
> +		dev_err(&client->dev,
> +			"invalid register list %s, no POWERON mode found\n",
> +			ET8EK8_NAME);
> +		goto out_poweroff;
> +	}
> +	rval = et8ek8_stream_on(sensor); /* Needed to be able to read EEPROM */
> +	if (rval)
> +		goto out_poweroff;
> +	rval = et8ek8_g_priv_mem(subdev);
> +	if (rval)
> +		dev_warn(&client->dev,
> +			"can not read OTP (EEPROM) memory from sensor\n");
> +	rval = et8ek8_stream_off(sensor);
> +	if (rval)
> +		goto out_poweroff;
> +
> +	rval = et8ek8_power_off(sensor);
> +	if (rval)
> +		goto out_poweroff;
> +
> +	return 0;
> +
> +out_poweroff:
> +	et8ek8_power_off(sensor);
> +
> +	return rval;
> +}
> +
> +/* --------------------------------------------------------------------------
> + * sysfs attributes
> + */
> +static ssize_t
> +et8ek8_priv_mem_read(struct device *dev, struct device_attribute *attr,
> +		     char *buf)
> +{
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +#if PAGE_SIZE < ET8EK8_PRIV_MEM_SIZE
> +#error PAGE_SIZE too small!
> +#endif
> +
> +	memcpy(buf, sensor->priv_mem, ET8EK8_PRIV_MEM_SIZE);
> +
> +	return ET8EK8_PRIV_MEM_SIZE;
> +}
> +static DEVICE_ATTR(priv_mem, S_IRUGO, et8ek8_priv_mem_read, NULL);
> +
> +/* --------------------------------------------------------------------------
> + * V4L2 subdev core operations
> + */
> +
> +static int
> +et8ek8_registered(struct v4l2_subdev *subdev)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	struct v4l2_mbus_framefmt *format;
> +	int rval;
> +
> +	dev_dbg(&client->dev, "registered!");
> +
> +	rval = device_create_file(&client->dev, &dev_attr_priv_mem);
> +	if (rval) {
> +		dev_err(&client->dev, "could not register sysfs entry\n");
> +		return rval;
> +	}
> +
> +	rval = et8ek8_dev_init(subdev);
> +	if (rval)
> +		goto err_file;
> +
> +	rval = et8ek8_init_controls(sensor);
> +	if (rval) {
> +		dev_err(&client->dev, "controls initialization failed\n");
> +		goto err_file;
> +	}
> +
> +	format = __et8ek8_get_pad_format(sensor, NULL, 0,
> +					 V4L2_SUBDEV_FORMAT_ACTIVE);
> +	return 0;
> +
> +err_file:
> +	device_remove_file(&client->dev, &dev_attr_priv_mem);
> +
> +	return rval;
> +}
> +
> +static int __et8ek8_set_power(struct et8ek8_sensor *sensor, bool on)
> +{
> +	return on ? et8ek8_power_on(sensor) : et8ek8_power_off(sensor);
> +}
> +
> +static int et8ek8_set_power(struct v4l2_subdev *subdev, int on)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	int ret = 0;
> +
> +	mutex_lock(&sensor->power_lock);
> +
> +	/* If the power count is modified from 0 to != 0 or from != 0 to 0,
> +	 * update the power state.
> +	 */
> +	if (sensor->power_count == !on) {
> +		ret = __et8ek8_set_power(sensor, !!on);
> +		if (ret < 0)
> +			goto done;
> +	}
> +
> +	/* Update the power count. */
> +	sensor->power_count += on ? 1 : -1;
> +	WARN_ON(sensor->power_count < 0);
> +
> +done:
> +	mutex_unlock(&sensor->power_lock);
> +
> +	return ret;
> +}
> +
> +static int et8ek8_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(sd);
> +	struct v4l2_mbus_framefmt *format;
> +	struct et8ek8_reglist *reglist;
> +
> +	reglist = et8ek8_reglist_find_type(&meta_reglist, ET8EK8_REGLIST_MODE);
> +	format = __et8ek8_get_pad_format(sensor, fh->pad, 0,
> +					 V4L2_SUBDEV_FORMAT_TRY);
> +	et8ek8_reglist_to_mbus(reglist, format);
> +
> +	return et8ek8_set_power(sd, true);
> +}
> +
> +static int et8ek8_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> +	return et8ek8_set_power(sd, false);
> +}
> +
> +static const struct v4l2_subdev_video_ops et8ek8_video_ops = {
> +	.s_stream = et8ek8_s_stream,
> +	.g_frame_interval = et8ek8_get_frame_interval,
> +	.s_frame_interval = et8ek8_set_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_core_ops et8ek8_core_ops = {
> +	.s_power = et8ek8_set_power,
> +};
> +
> +static const struct v4l2_subdev_pad_ops et8ek8_pad_ops = {
> +	.enum_mbus_code = et8ek8_enum_mbus_code,
> +	.enum_frame_size = et8ek8_enum_frame_size,
> +	.enum_frame_interval = et8ek8_enum_frame_ival,
> +	.get_fmt = et8ek8_get_pad_format,
> +	.set_fmt = et8ek8_set_pad_format,
> +};
> +
> +static const struct v4l2_subdev_ops et8ek8_ops = {
> +	.core = &et8ek8_core_ops,
> +	.video = &et8ek8_video_ops,
> +	.pad = &et8ek8_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = {
> +	.registered = et8ek8_registered,
> +	.open = et8ek8_open,
> +	.close = et8ek8_close,
> +};
> +
> +/* --------------------------------------------------------------------------
> + * I2C driver
> + */
> +#ifdef CONFIG_PM
> +
> +static int et8ek8_suspend(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	if (!sensor->power_count)
> +		return 0;
> +
> +	return __et8ek8_set_power(sensor, false);
> +}
> +
> +static int et8ek8_resume(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	if (!sensor->power_count)
> +		return 0;
> +
> +	return __et8ek8_set_power(sensor, true);
> +}
> +
> +#else
> +
> +#define et8ek8_suspend NULL
> +#define et8ek8_resume NULL
> +
> +#endif /* CONFIG_PM */
> +
> +static int et8ek8_probe(struct i2c_client *client,
> +			const struct i2c_device_id *devid)
> +{
> +	struct et8ek8_sensor *sensor;
> +	struct device *dev = &client->dev;
> +	int ret;
> +
> +	sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
> +	if (!sensor)
> +		return -ENOMEM;
> +
> +	sensor->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> +	if (IS_ERR(sensor->reset)) {
> +		dev_dbg(&client->dev, "could not request reset gpio\n");
> +		return PTR_ERR(sensor->reset);
> +	}
> +
> +	sensor->vana = devm_regulator_get(dev, "vana");
> +	if (IS_ERR(sensor->vana)) {
> +		dev_err(&client->dev, "could not get regulator for vana\n");
> +		return PTR_ERR(sensor->vana);
> +	}
> +
> +	sensor->ext_clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(sensor->ext_clk)) {
> +		dev_err(&client->dev, "could not get clock\n");
> +		return PTR_ERR(sensor->ext_clk);
> +	}
> +
> +	ret = of_property_read_u32(dev->of_node, "clock-frequency",
> +				   &sensor->xclk_freq);
> +	if (ret) {
> +		dev_warn(dev, "can't get clock-frequency\n");
> +		return ret;
> +	}
> +
> +	mutex_init(&sensor->power_lock);

mutex_destroy() should be called on the mutex if probe fails after this and
in remove().

> +
> +	v4l2_i2c_subdev_init(&sensor->subdev, client, &et8ek8_ops);
> +	sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sensor->subdev.internal_ops = &et8ek8_internal_ops;
> +
> +	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad);
> +	if (ret < 0) {
> +		dev_err(&client->dev, "media entity init failed!\n");
> +		return ret;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&sensor->subdev);
> +	if (ret < 0) {
> +		media_entity_cleanup(&sensor->subdev.entity);
> +		return ret;
> +	}
> +
> +	dev_dbg(dev, "initialized!\n");
> +
> +	return 0;
> +}
> +
> +static int __exit et8ek8_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	if (sensor->power_count) {
> +		gpiod_set_value(sensor->reset, 0);
> +		clk_disable_unprepare(sensor->ext_clk);

How about the regulator? Could you call et8ek8_power_off() instead?

> +		sensor->power_count = 0;
> +	}
> +
> +	v4l2_device_unregister_subdev(&sensor->subdev);
> +	device_remove_file(&client->dev, &dev_attr_priv_mem);
> +	v4l2_ctrl_handler_free(&sensor->ctrl_handler);
> +	media_entity_cleanup(&sensor->subdev.entity);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id et8ek8_of_table[] = {
> +	{ .compatible = "toshiba,et8ek8" },
> +	{ },
> +};
> +
> +static const struct i2c_device_id et8ek8_id_table[] = {
> +	{ ET8EK8_NAME, 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, et8ek8_id_table);
> +
> +static const struct dev_pm_ops et8ek8_pm_ops = {
> +	.suspend	= et8ek8_suspend,
> +	.resume		= et8ek8_resume,
> +};
> +
> +static struct i2c_driver et8ek8_i2c_driver = {
> +	.driver		= {
> +		.name	= ET8EK8_NAME,
> +		.pm	= &et8ek8_pm_ops,
> +		.of_match_table	= et8ek8_of_table,
> +	},
> +	.probe		= et8ek8_probe,
> +	.remove		= __exit_p(et8ek8_remove),
> +	.id_table	= et8ek8_id_table,
> +};
> +
> +module_i2c_driver(et8ek8_i2c_driver);
> +
> +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
> +MODULE_DESCRIPTION("Toshiba ET8EK8 camera sensor driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/i2c/et8ek8/et8ek8_mode.c b/drivers/media/i2c/et8ek8/et8ek8_mode.c
> new file mode 100644
> index 0000000..956fc60
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_mode.c
> @@ -0,0 +1,587 @@
> +/*
> + * et8ek8_mode.c
> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukka.o.toivonen@nokia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include "et8ek8_reg.h"
> +
> +/*
> + * Stingray sensor mode settings for Scooby
> + */
> +
> +/* Mode1_poweron_Mode2_16VGA_2592x1968_12.07fps */
> +static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 640 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 137 (3288)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_POWERON,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3288,
> +		.height = 2016,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 2592,
> +		.window_height = 1968,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 1207
> +		},
> +		.max_exp = 2012,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		/* Need to set firstly */
> +		{ ET8EK8_REG_8BIT, 0x126C, 0xCC },
> +		/* Strobe and Data of CCP2 delay are minimized. */
> +		{ ET8EK8_REG_8BIT, 0x1269, 0x00 },
> +		/* Refined value of Min H_COUNT  */
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		/* Frequency of SPCK setting (SPCK=MRCK) */
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x1241, 0x94 },
> +		{ ET8EK8_REG_8BIT, 0x1242, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x124B, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1255, 0xFF },
> +		{ ET8EK8_REG_8BIT, 0x1256, 0x9F },
> +		{ ET8EK8_REG_8BIT, 0x1258, 0x00 },
> +		/* From parallel out to serial out */
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 },
> +		/* From w/ embeded data to w/o embeded data */
> +		{ ET8EK8_REG_8BIT, 0x125E, 0xC0 },
> +		/* CCP2 out is from STOP to ACTIVE */
> +		{ ET8EK8_REG_8BIT, 0x1263, 0x98 },
> +		{ ET8EK8_REG_8BIT, 0x1268, 0xC6 },
> +		{ ET8EK8_REG_8BIT, 0x1434, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1163, 0x44 },
> +		{ ET8EK8_REG_8BIT, 0x1166, 0x29 },
> +		{ ET8EK8_REG_8BIT, 0x1140, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x1011, 0x24 },
> +		{ ET8EK8_REG_8BIT, 0x1151, 0x80 },
> +		{ ET8EK8_REG_8BIT, 0x1152, 0x23 },
> +		/* Initial setting for improvement2 of lower frequency noise */
> +		{ ET8EK8_REG_8BIT, 0x1014, 0x05 },
> +		{ ET8EK8_REG_8BIT, 0x1033, 0x06 },
> +		{ ET8EK8_REG_8BIT, 0x1034, 0x79 },
> +		{ ET8EK8_REG_8BIT, 0x1423, 0x3F },
> +		{ ET8EK8_REG_8BIT, 0x1424, 0x3F },
> +		{ ET8EK8_REG_8BIT, 0x1426, 0x00 },
> +		/* Switch of Preset-White-balance (0d:disable / 1d:enable) */
> +		{ ET8EK8_REG_8BIT, 0x1439, 0x00 },
> +		/* Switch of blemish correction (0d:disable / 1d:enable) */
> +		{ ET8EK8_REG_8BIT, 0x161F, 0x60 },
> +		/* Switch of auto noise correction (0d:disable / 1d:enable) */
> +		{ ET8EK8_REG_8BIT, 0x1634, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1646, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1648, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x113E, 0x01 },
> +		{ ET8EK8_REG_8BIT, 0x113F, 0x22 },
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode1_16VGA_2592x1968_13.12fps_DPCM10-8 */
> +static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 560 MHz
> + * VCO        = 560 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 128 (3072)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 175
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 6
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3072,
> +		.height = 2016,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 2592,
> +		.window_height = 1968,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 1292
> +		},
> +		.max_exp = 2012,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x57 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x06 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x80 }, /* <-changed to v14 7E->80 */
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode3_4VGA_1296x984_29.99fps_DPCM10-8 */
> +static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = {
> +/* (without the +1)
> + * SPCK       = 96.5333333333333 MHz
> + * CCP2       = 579.2 MHz
> + * VCO        = 579.2 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 133 (3192)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 181
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 5
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3192,
> +		.height = 1008,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 1296,
> +		.window_height = 984,
> +		.pixel_clock = 96533333,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 3000
> +		},
> +		.max_exp = 1004,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x5A },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode4_SVGA_864x656_29.88fps */
> +static struct et8ek8_reglist mode4_svga_864x656_29_88fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 320 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 166 (3984)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 1
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3984,
> +		.height = 672,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 864,
> +		.window_height = 656,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 2988
> +		},
> +		.max_exp = 668,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x62 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x62 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0xA6 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode5_VGA_648x492_29.93fps */
> +static struct et8ek8_reglist mode5_vga_648x492_29_93fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 320 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 221 (5304)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 1
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 5304,
> +		.height = 504,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 648,
> +		.window_height = 492,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 2993
> +		},
> +		.max_exp = 500,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode2_16VGA_2592x1968_3.99fps */
> +static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 640 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 254 (6096)
> + * HCOUNT     = 137 (3288)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3288,
> +		.height = 6096,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 2592,
> +		.window_height = 1968,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 399
> +		},
> +		.max_exp = 6092,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0xFE },
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode_648x492_5fps */
> +static struct et8ek8_reglist mode_648x492_5fps = {
> +/* (without the +1)
> + * SPCK       = 13.3333333333333 MHz
> + * CCP2       = 53.3333333333333 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 221 (5304)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 5
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 1
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 5304,
> +		.height = 504,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 648,
> +		.window_height = 492,
> +		.pixel_clock = 13333333,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 499
> +		},
> +		.max_exp = 500,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x57 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode3_4VGA_1296x984_5fps */
> +static struct et8ek8_reglist mode3_4vga_1296x984_5fps = {
> +/* (without the +1)
> + * SPCK       = 49.4 MHz
> + * CCP2       = 395.2 MHz
> + * VCO        = 790.4 MHz
> + * VCOUNT     = 250 (6000)
> + * HCOUNT     = 137 (3288)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 247
> + * VCO_DIV    = 1
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3288,
> +		.height = 3000,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 1296,
> +		.window_height = 984,
> +		.pixel_clock = 49400000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 501
> +		},
> +		.max_exp = 2996,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x7B },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x17 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0xFA },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode_4VGA_1296x984_25fps_DPCM10-8 */
> +static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = {
> +/* (without the +1)
> + * SPCK       = 84.2666666666667 MHz
> + * CCP2       = 505.6 MHz
> + * VCO        = 505.6 MHz
> + * VCOUNT     = 88 (2112)
> + * HCOUNT     = 133 (3192)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 158
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 5
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3192,
> +		.height = 1056,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 1296,
> +		.window_height = 984,
> +		.pixel_clock = 84266667,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 2500
> +		},
> +		.max_exp = 1052,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x4F },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x58 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 },
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +struct et8ek8_meta_reglist meta_reglist = {
> +	.version = "V14 03-June-2008",
> +	.reglist = {
> +		{ .ptr = &mode1_poweron_mode2_16vga_2592x1968_12_07fps },
> +		{ .ptr = &mode1_16vga_2592x1968_13_12fps_dpcm10_8 },
> +		{ .ptr = &mode3_4vga_1296x984_29_99fps_dpcm10_8 },
> +		{ .ptr = &mode4_svga_864x656_29_88fps },
> +		{ .ptr = &mode5_vga_648x492_29_93fps },
> +		{ .ptr = &mode2_16vga_2592x1968_3_99fps },
> +		{ .ptr = &mode_648x492_5fps },
> +		{ .ptr = &mode3_4vga_1296x984_5fps },
> +		{ .ptr = &mode_4vga_1296x984_25fps_dpcm10_8 },
> +		{ .ptr = NULL }
> +	}
> +};
> diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> new file mode 100644
> index 0000000..9970bff
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> @@ -0,0 +1,96 @@
> +/*
> + * et8ek8.h

et8ek8_reg.h

> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukkat76@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#ifndef ET8EK8REGS_H
> +#define ET8EK8REGS_H
> +
> +#include <linux/i2c.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/v4l2-subdev.h>
> +
> +struct v4l2_mbus_framefmt;
> +struct v4l2_subdev_pad_mbus_code_enum;
> +
> +struct et8ek8_mode {
> +	/* Physical sensor resolution and current image window */
> +	u16 sensor_width;
> +	u16 sensor_height;
> +	u16 sensor_window_origin_x;
> +	u16 sensor_window_origin_y;
> +	u16 sensor_window_width;
> +	u16 sensor_window_height;
> +
> +	/* Image data coming from sensor (after scaling) */
> +	u16 width;
> +	u16 height;
> +	u16 window_origin_x;
> +	u16 window_origin_y;
> +	u16 window_width;
> +	u16 window_height;
> +
> +	u32 pixel_clock;		/* in Hz */
> +	u32 ext_clock;			/* in Hz */
> +	struct v4l2_fract timeperframe;
> +	u32 max_exp;			/* Maximum exposure value */
> +	u32 pixel_format;		/* V4L2_PIX_FMT_xxx */
> +	u32 sensitivity;		/* 16.16 fixed point */
> +};
> +
> +#define ET8EK8_REG_8BIT			1
> +#define ET8EK8_REG_16BIT		2
> +#define ET8EK8_REG_DELAY		100
> +#define ET8EK8_REG_TERM			0xff
> +struct et8ek8_reg {
> +	u16 type;
> +	u16 reg;			/* 16-bit offset */
> +	u32 val;			/* 8/16/32-bit value */
> +};
> +
> +/* Possible struct smia_reglist types. */
> +#define ET8EK8_REGLIST_STANDBY		0
> +#define ET8EK8_REGLIST_POWERON		1
> +#define ET8EK8_REGLIST_RESUME		2
> +#define ET8EK8_REGLIST_STREAMON		3
> +#define ET8EK8_REGLIST_STREAMOFF	4
> +#define ET8EK8_REGLIST_DISABLED		5
> +
> +#define ET8EK8_REGLIST_MODE		10
> +
> +#define ET8EK8_REGLIST_LSC_ENABLE	100
> +#define ET8EK8_REGLIST_LSC_DISABLE	101
> +#define ET8EK8_REGLIST_ANR_ENABLE	102
> +#define ET8EK8_REGLIST_ANR_DISABLE	103
> +
> +struct et8ek8_reglist {
> +	u32 type;
> +	struct et8ek8_mode mode;
> +	struct et8ek8_reg regs[];
> +};
> +
> +#define ET8EK8_MAX_LEN			32
> +struct et8ek8_meta_reglist {
> +	char version[ET8EK8_MAX_LEN];
> +	union {
> +		struct et8ek8_reglist *ptr;
> +	} reglist[];
> +};
> +
> +extern struct et8ek8_meta_reglist meta_reglist;
> +
> +#endif /* ET8EK8REGS */
>
Pavel Machek Oct. 23, 2016, 8:33 p.m. UTC | #2
Hi!

> Thanks, this answered half of my questions already. ;-)

:-).

I'll have to go through the patches, et8ek8 driver is probably not
enough to get useful video. platform/video-bus-switch.c is needed for
camera switching, then some omap3isp patches to bind flash and
autofocus into the subdevice.

Then, device tree support on n900 can be added.

> Do all the modes work for you currently btw.?

I don't think I got 5MP mode to work. Even 2.5MP mode is tricky (needs
a lot of continuous memory).

Anyway, I have to start somewhere, and I believe this is a good
starting place; I'd like to get the code cleaned up and merged, then
move to the next parts.

Best regards,
								Pavel


> On Sun, Oct 23, 2016 at 10:03:55PM +0200, Pavel Machek wrote:
> > 
> > Add driver for et8ek8 sensor, found in Nokia N900 main camera. Can be
> > used for taking photos in 2.5MP resolution with fcam-dev.
> > 
> > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> > 
> > ---
> > From v4 I did cleanups to coding style and removed various oddities.
> > 
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index 2669b4b..6d01e15 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -667,6 +667,7 @@ config VIDEO_S5K5BAF
> >  	  camera sensor with an embedded SoC image signal processor.
> >  
> >  source "drivers/media/i2c/smiapp/Kconfig"
> > +source "drivers/media/i2c/et8ek8/Kconfig"
> >  
> >  config VIDEO_S5C73M3
> >  	tristate "Samsung S5C73M3 sensor support"
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index 92773b2..5bc7bbe 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -2,6 +2,7 @@ msp3400-objs	:=	msp3400-driver.o msp3400-kthreads.o
> >  obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
> >  
> >  obj-$(CONFIG_VIDEO_SMIAPP)	+= smiapp/
> > +obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8/
> >  obj-$(CONFIG_VIDEO_CX25840) += cx25840/
> >  obj-$(CONFIG_VIDEO_M5MOLS)	+= m5mols/
> >  obj-y				+= soc_camera/
> > diff --git a/drivers/media/i2c/et8ek8/Kconfig b/drivers/media/i2c/et8ek8/Kconfig
> > new file mode 100644
> > index 0000000..1439936
> > --- /dev/null
> > +++ b/drivers/media/i2c/et8ek8/Kconfig
> > @@ -0,0 +1,6 @@
> > +config VIDEO_ET8EK8
> > +	tristate "ET8EK8 camera sensor support"
> > +	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> > +	---help---
> > +	  This is a driver for the Toshiba ET8EK8 5 MP camera sensor.
> > +	  It is used for example in Nokia N900 (RX-51).
> > diff --git a/drivers/media/i2c/et8ek8/Makefile b/drivers/media/i2c/et8ek8/Makefile
> > new file mode 100644
> > index 0000000..66d1b7d
> > --- /dev/null
> > +++ b/drivers/media/i2c/et8ek8/Makefile
> > @@ -0,0 +1,2 @@
> > +et8ek8-objs			+= et8ek8_mode.o et8ek8_driver.o
> > +obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8.o
> > diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> > new file mode 100644
> > index 0000000..0301e81
> > --- /dev/null
> > +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> > @@ -0,0 +1,1588 @@
> > +/*
> > + * et8ek8_driver.c
> > + *
> > + * Copyright (C) 2008 Nokia Corporation
> > + *
> > + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> > + *          Tuukka Toivonen <tuukkat76@gmail.com>
> > + *
> > + * Based on code from Toni Leinonen <toni.leinonen@offcode.fi>.
> > + *
> > + * This driver is based on the Micron MT9T012 camera imager driver
> > + * (C) Texas Instruments.
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License
> > + * version 2 as published by the Free Software Foundation.
> > + *
> > + * This program is distributed in the hope that it will be useful, but
> > + * WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + * General Public License for more details.
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/slab.h>
> > +#include <linux/sort.h>
> > +#include <linux/v4l2-mediabus.h>
> > +
> > +#include <media/media-entity.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#include "et8ek8_reg.h"
> > +
> > +#define ET8EK8_NAME		"et8ek8"
> > +#define ET8EK8_PRIV_MEM_SIZE	128
> > +#define ET8EK8_MAX_MSG		48
> > +
> > +struct et8ek8_sensor {
> > +	struct v4l2_subdev subdev;
> > +	struct media_pad pad;
> > +	struct v4l2_mbus_framefmt format;
> > +	struct gpio_desc *reset;
> > +	struct regulator *vana;
> > +	struct clk *ext_clk;
> > +	u32 xclk_freq;
> > +
> > +	u16 version;
> > +
> > +	struct v4l2_ctrl_handler ctrl_handler;
> > +	struct v4l2_ctrl *exposure;
> > +	struct v4l2_ctrl *pixel_rate;
> > +	struct et8ek8_reglist *current_reglist;
> > +
> > +	u8 priv_mem[ET8EK8_PRIV_MEM_SIZE];
> > +
> > +	struct mutex power_lock;
> > +	int power_count;
> > +};
> > +
> > +#define to_et8ek8_sensor(sd)	container_of(sd, struct et8ek8_sensor, subdev)
> > +
> > +enum et8ek8_versions {
> > +	ET8EK8_REV_1 = 0x0001,
> > +	ET8EK8_REV_2,
> > +};
> > +
> > +/*
> > + * This table describes what should be written to the sensor register
> > + * for each gain value. The gain(index in the table) is in terms of
> > + * 0.1EV, i.e. 10 indexes in the table give 2 time more gain [0] in
> > + * the *analog gain, [1] in the digital gain
> > + *
> > + * Analog gain [dB] = 20*log10(regvalue/32); 0x20..0x100
> > + */
> > +static struct et8ek8_gain {
> > +	u16 analog;
> > +	u16 digital;
> > +} const et8ek8_gain_table[] = {
> > +	{ 32,    0},  /* x1 */
> > +	{ 34,    0},
> > +	{ 37,    0},
> > +	{ 39,    0},
> > +	{ 42,    0},
> > +	{ 45,    0},
> > +	{ 49,    0},
> > +	{ 52,    0},
> > +	{ 56,    0},
> > +	{ 60,    0},
> > +	{ 64,    0},  /* x2 */
> > +	{ 69,    0},
> > +	{ 74,    0},
> > +	{ 79,    0},
> > +	{ 84,    0},
> > +	{ 91,    0},
> > +	{ 97,    0},
> > +	{104,    0},
> > +	{111,    0},
> > +	{119,    0},
> > +	{128,    0},  /* x4 */
> > +	{137,    0},
> > +	{147,    0},
> > +	{158,    0},
> > +	{169,    0},
> > +	{181,    0},
> > +	{194,    0},
> > +	{208,    0},
> > +	{223,    0},
> > +	{239,    0},
> > +	{256,    0},  /* x8 */
> > +	{256,   73},
> > +	{256,  152},
> > +	{256,  236},
> > +	{256,  327},
> > +	{256,  424},
> > +	{256,  528},
> > +	{256,  639},
> > +	{256,  758},
> > +	{256,  886},
> > +	{256, 1023},  /* x16 */
> > +};
> > +
> > +/* Register definitions */
> > +#define REG_REVISION_NUMBER_L	0x1200
> > +#define REG_REVISION_NUMBER_H	0x1201
> > +
> > +#define PRIV_MEM_START_REG	0x0008
> > +#define PRIV_MEM_WIN_SIZE	8
> > +
> > +#define ET8EK8_I2C_DELAY	3	/* msec delay b/w accesses */
> > +
> > +#define USE_CRC			1
> > +
> > +/*
> > + * Register access helpers
> > + *
> > + * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
> > + * Returns zero if successful, or non-zero otherwise.
> > + */
> > +static int et8ek8_i2c_read_reg(struct i2c_client *client, u16 data_length,
> > +			       u16 reg, u32 *val)
> > +{
> > +	int r;
> > +	struct i2c_msg msg;
> > +	unsigned char data[4];
> > +
> > +	if (!client->adapter)
> > +		return -ENODEV;
> > +	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
> > +		return -EINVAL;
> > +
> > +	msg.addr = client->addr;
> > +	msg.flags = 0;
> > +	msg.len = 2;
> > +	msg.buf = data;
> > +
> > +	/* high byte goes out first */
> > +	data[0] = (u8) (reg >> 8);
> > +	data[1] = (u8) (reg & 0xff);
> > +	r = i2c_transfer(client->adapter, &msg, 1);
> > +	if (r < 0)
> > +		goto err;
> > +
> > +	msg.len = data_length;
> > +	msg.flags = I2C_M_RD;
> > +	r = i2c_transfer(client->adapter, &msg, 1);
> > +	if (r < 0)
> > +		goto err;
> > +
> > +	*val = 0;
> > +	/* high byte comes first */
> > +	if (data_length == ET8EK8_REG_8BIT)
> > +		*val = data[0];
> > +	else
> > +		*val = (data[0] << 8) + data[1];
> > +
> > +	return 0;
> > +
> > +err:
> > +	dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r);
> > +
> > +	return r;
> > +}
> > +
> > +static void et8ek8_i2c_create_msg(struct i2c_client *client, u16 len, u16 reg,
> > +				  u32 val, struct i2c_msg *msg,
> > +				  unsigned char *buf)
> > +{
> > +	msg->addr = client->addr;
> > +	msg->flags = 0; /* Write */
> > +	msg->len = 2 + len;
> > +	msg->buf = buf;
> > +
> > +	/* high byte goes out first */
> > +	buf[0] = (u8) (reg >> 8);
> > +	buf[1] = (u8) (reg & 0xff);
> > +
> > +	switch (len) {
> > +	case ET8EK8_REG_8BIT:
> > +		buf[2] = (u8) (val) & 0xff;
> > +		break;
> > +	case ET8EK8_REG_16BIT:
> > +		buf[2] = (u8) (val >> 8) & 0xff;
> > +		buf[3] = (u8) (val & 0xff);
> > +		break;
> > +	default:
> > +		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
> > +			  __func__);
> > +	}
> > +}
> > +
> > +/*
> > + * A buffered write method that puts the wanted register write
> > + * commands in a message list and passes the list to the i2c framework
> > + */
> > +static int et8ek8_i2c_buffered_write_regs(struct i2c_client *client,
> > +					  const struct et8ek8_reg *wnext,
> > +					  int cnt)
> > +{
> > +	struct i2c_msg msg[ET8EK8_MAX_MSG];
> > +	unsigned char data[ET8EK8_MAX_MSG][6];
> > +	int wcnt = 0;
> > +	u16 reg, data_length;
> > +	u32 val;
> > +
> > +	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
> > +		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
> > +		return -EINVAL;
> > +	}
> > +
> > +	/* Create new write messages for all writes */
> > +	while (wcnt < cnt) {
> > +		data_length = wnext->type;
> > +		reg = wnext->reg;
> > +		val = wnext->val;
> > +		wnext++;
> > +
> > +		et8ek8_i2c_create_msg(client, data_length, reg,
> > +				    val, &msg[wcnt], &data[wcnt][0]);
> > +
> > +		/* Update write count */
> > +		wcnt++;
> > +	}
> > +
> > +	/* Now we send everything ... */
> > +	return i2c_transfer(client->adapter, msg, wcnt);
> > +}
> > +
> > +/*
> > + * Write a list of registers to i2c device.
> > + *
> > + * The list of registers is terminated by ET8EK8_REG_TERM.
> > + * Returns zero if successful, or non-zero otherwise.
> > + */
> > +static int et8ek8_i2c_write_regs(struct i2c_client *client,
> > +				 const struct et8ek8_reg *regs)
> > +{
> > +	int r, cnt = 0;
> > +	const struct et8ek8_reg *next;
> > +
> > +	if (!client->adapter)
> > +		return -ENODEV;
> > +
> > +	if (!regs)
> > +		return -EINVAL;
> > +
> > +	/* Initialize list pointers to the start of the list */
> > +	next = regs;
> > +
> > +	do {
> > +		/*
> > +		 * We have to go through the list to figure out how
> > +		 * many regular writes we have in a row
> > +		 */
> > +		while (next->type != ET8EK8_REG_TERM &&
> > +		       next->type != ET8EK8_REG_DELAY) {
> > +			/*
> > +			 * Here we check that the actual length fields
> > +			 * are valid
> > +			 */
> > +			if (WARN(next->type != ET8EK8_REG_8BIT &&
> > +				 next->type != ET8EK8_REG_16BIT,
> > +				 "Invalid type = %d", next->type)) {
> > +				return -EINVAL;
> > +			}
> > +			/*
> > +			 * Increment count of successive writes and
> > +			 * read pointer
> > +			 */
> > +			cnt++;
> > +			next++;
> > +		}
> > +
> > +		/* Now we start writing ... */
> > +		r = et8ek8_i2c_buffered_write_regs(client, regs, cnt);
> > +
> > +		/* ... and then check that everything was OK */
> > +		if (r < 0) {
> > +			dev_err(&client->dev, "i2c transfer error!\n");
> > +			return r;
> > +		}
> > +
> > +		/*
> > +		 * If we ran into a sleep statement when going through
> > +		 * the list, this is where we snooze for the required time
> > +		 */
> > +		if (next->type == ET8EK8_REG_DELAY) {
> > +			msleep(next->val);
> > +			/*
> > +			 * ZZZ ...
> > +			 * Update list pointers and cnt and start over ...
> > +			 */
> > +			next++;
> > +			regs = next;
> > +			cnt = 0;
> > +		}
> > +	} while (next->type != ET8EK8_REG_TERM);
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * Write to a 8/16-bit register.
> > + * Returns zero if successful, or non-zero otherwise.
> > + */
> > +static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length,
> > +				u16 reg, u32 val)
> > +{
> > +	int r;
> > +	struct i2c_msg msg;
> > +	unsigned char data[6];
> > +
> > +	if (!client->adapter)
> > +		return -ENODEV;
> > +	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
> > +		return -EINVAL;
> > +
> > +	et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data);
> > +
> > +	r = i2c_transfer(client->adapter, &msg, 1);
> > +	if (r < 0)
> > +		dev_err(&client->dev,
> > +			"wrote 0x%x to offset 0x%x error %d\n", val, reg, r);
> > +	else
> > +		r = 0; /* on success i2c_transfer() returns messages trasfered */
> > +
> > +	return r;
> > +}
> > +
> > +static struct et8ek8_reglist *et8ek8_reglist_find_type(
> > +		struct et8ek8_meta_reglist *meta,
> > +		u16 type)
> > +{
> > +	struct et8ek8_reglist **next = &meta->reglist[0].ptr;
> > +
> > +	while (*next) {
> > +		if ((*next)->type == type)
> > +			return *next;
> > +
> > +		next++;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static int et8ek8_i2c_reglist_find_write(struct i2c_client *client,
> > +					 struct et8ek8_meta_reglist *meta,
> > +					 u16 type)
> > +{
> > +	struct et8ek8_reglist *reglist;
> > +
> > +	reglist = et8ek8_reglist_find_type(meta, type);
> > +	if (!reglist)
> > +		return -EINVAL;
> > +
> > +	return et8ek8_i2c_write_regs(client, reglist->regs);
> > +}
> > +
> > +static struct et8ek8_reglist **et8ek8_reglist_first(
> > +		struct et8ek8_meta_reglist *meta)
> > +{
> > +	return &meta->reglist[0].ptr;
> > +}
> > +
> > +static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist,
> > +				   struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	fmt->width = reglist->mode.window_width;
> > +	fmt->height = reglist->mode.window_height;
> > +
> > +	if (reglist->mode.pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
> > +		fmt->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
> > +	else
> > +		fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +}
> > +
> > +static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt(
> > +		struct et8ek8_meta_reglist *meta,
> > +		struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
> > +	struct et8ek8_reglist *best_match = NULL;
> > +	struct et8ek8_reglist *best_other = NULL;
> > +	struct v4l2_mbus_framefmt format;
> > +	unsigned int max_dist_match = (unsigned int)-1;
> > +	unsigned int max_dist_other = (unsigned int)-1;
> > +
> > +	/*
> > +	 * Find the mode with the closest image size. The distance between
> > +	 * image sizes is the size in pixels of the non-overlapping regions
> > +	 * between the requested size and the frame-specified size.
> > +	 *
> > +	 * Store both the closest mode that matches the requested format, and
> > +	 * the closest mode for all other formats. The best match is returned
> > +	 * if found, otherwise the best mode with a non-matching format is
> > +	 * returned.
> > +	 */
> > +	for (; *list; list++) {
> > +		unsigned int dist;
> > +
> > +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> > +			continue;
> > +
> > +		et8ek8_reglist_to_mbus(*list, &format);
> > +
> > +		dist = min(fmt->width, format.width)
> > +		     * min(fmt->height, format.height);
> > +		dist = format.width * format.height
> > +		     + fmt->width * fmt->height - 2 * dist;
> > +
> > +
> > +		if (fmt->code == format.code) {
> > +			if (dist < max_dist_match || !best_match) {
> > +				best_match = *list;
> > +				max_dist_match = dist;
> > +			}
> > +		} else {
> > +			if (dist < max_dist_other || !best_other) {
> > +				best_other = *list;
> > +				max_dist_other = dist;
> > +			}
> > +		}
> > +	}
> > +
> > +	return best_match ? best_match : best_other;
> > +}
> > +
> > +#define TIMEPERFRAME_AVG_FPS(t)						\
> > +	(((t).denominator + ((t).numerator >> 1)) / (t).numerator)
> > +
> > +static struct et8ek8_reglist *et8ek8_reglist_find_mode_ival(
> > +		struct et8ek8_meta_reglist *meta,
> > +		struct et8ek8_reglist *current_reglist,
> > +		struct v4l2_fract *timeperframe)
> > +{
> > +	int fps = TIMEPERFRAME_AVG_FPS(*timeperframe);
> > +	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
> > +	struct et8ek8_mode *current_mode = &current_reglist->mode;
> > +
> > +	for (; *list; list++) {
> > +		struct et8ek8_mode *mode = &(*list)->mode;
> > +
> > +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> > +			continue;
> > +
> > +		if (mode->window_width != current_mode->window_width ||
> > +		    mode->window_height != current_mode->window_height)
> > +			continue;
> > +
> > +		if (TIMEPERFRAME_AVG_FPS(mode->timeperframe) == fps)
> > +			return *list;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static int et8ek8_reglist_cmp(const void *a, const void *b)
> > +{
> > +	const struct et8ek8_reglist **list1 = (const struct et8ek8_reglist **)a,
> > +		**list2 = (const struct et8ek8_reglist **)b;
> > +
> > +	/* Put real modes in the beginning. */
> > +	if ((*list1)->type == ET8EK8_REGLIST_MODE &&
> > +	    (*list2)->type != ET8EK8_REGLIST_MODE)
> > +		return -1;
> > +	if ((*list1)->type != ET8EK8_REGLIST_MODE &&
> > +	    (*list2)->type == ET8EK8_REGLIST_MODE)
> > +		return 1;
> > +
> > +	/* Descending width. */
> > +	if ((*list1)->mode.window_width > (*list2)->mode.window_width)
> > +		return -1;
> > +	if ((*list1)->mode.window_width < (*list2)->mode.window_width)
> > +		return 1;
> > +
> > +	if ((*list1)->mode.window_height > (*list2)->mode.window_height)
> > +		return -1;
> > +	if ((*list1)->mode.window_height < (*list2)->mode.window_height)
> > +		return 1;
> > +
> > +	return 0;
> > +}
> > +
> > +static int et8ek8_reglist_import(struct i2c_client *client,
> > +				 struct et8ek8_meta_reglist *meta)
> > +{
> > +	int nlists = 0, i;
> > +
> > +	dev_info(&client->dev, "meta_reglist version %s\n", meta->version);
> > +
> > +	while (meta->reglist[nlists].ptr)
> > +		nlists++;
> > +
> > +	if (!nlists)
> > +		return -EINVAL;
> > +
> > +	sort(&meta->reglist[0].ptr, nlists, sizeof(meta->reglist[0].ptr),
> > +	     et8ek8_reglist_cmp, NULL);
> > +
> > +	i = nlists;
> > +	nlists = 0;
> > +
> > +	while (i--) {
> > +		struct et8ek8_reglist *list;
> > +
> > +		list = meta->reglist[nlists].ptr;
> > +
> > +		dev_dbg(&client->dev,
> > +		       "%s: type %d\tw %d\th %d\tfmt %x\tival %d/%d\tptr %p\n",
> > +		       __func__,
> > +		       list->type,
> > +		       list->mode.window_width, list->mode.window_height,
> > +		       list->mode.pixel_format,
> > +		       list->mode.timeperframe.numerator,
> > +		       list->mode.timeperframe.denominator,
> > +		       (void *)meta->reglist[nlists].ptr);
> > +
> > +		nlists++;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +typedef unsigned int fixpoint8; /* .8 fixed point format. */
> > +
> > +/*
> > + * Return time of one row in microseconds
> > + * If the sensor is not set to any mode, return zero.
> > + */
> > +fixpoint8 et8ek8_get_row_time(struct et8ek8_sensor *sensor)
> > +{
> > +	unsigned int clock;	/* Pixel clock in Hz>>10 fixed point */
> > +	fixpoint8 rt;	/* Row time in .8 fixed point */
> > +
> > +	if (!sensor->current_reglist)
> > +		return 0;
> > +
> > +	clock = sensor->current_reglist->mode.pixel_clock;
> > +	clock = (clock + (1 << 9)) >> 10;
> > +	rt = sensor->current_reglist->mode.width * (1000000 >> 2);
> > +	rt = (rt + (clock >> 1)) / clock;
> > +
> > +	return rt;
> > +}
> > +
> > +/*
> > + * Convert exposure time `us' to rows. Modify `us' to make it to
> > + * correspond to the actual exposure time.
> > + */
> > +static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us)
> 
> Should a driver do something like this to begin with?
> 
> The smiapp driver does use the native unit of exposure (lines) for the
> control and I think the et8ek8 driver should do so as well.
> 
> The HBLANK, VBLANK and PIXEL_RATE controls are used to provide the user with
> enough information to perform the conversion (if necessary).
> 
> > +{
> > +	unsigned int rows;	/* Exposure value as written to HW (ie. rows) */
> > +	fixpoint8 rt;	/* Row time in .8 fixed point */
> > +
> > +	/* Assume that the maximum exposure time is at most ~8 s,
> > +	 * and the maximum width (with blanking) ~8000 pixels.
> > +	 * The formula here is in principle as simple as
> > +	 *    rows = exptime / 1e6 / width * pixel_clock
> > +	 * but to get accurate results while coping with value ranges,
> > +	 * have to do some fixed point math.
> > +	 */
> > +
> > +	rt = et8ek8_get_row_time(sensor);
> > +	rows = ((*us << 8) + (rt >> 1)) / rt;
> > +
> > +	if (rows > sensor->current_reglist->mode.max_exp)
> > +		rows = sensor->current_reglist->mode.max_exp;
> > +
> > +	/* Set the exposure time to the rounded value */
> > +	*us = (rt * rows + (1 << 7)) >> 8;
> > +
> > +	return rows;
> > +}
> > +
> > +/*
> > + * Convert exposure time in rows to microseconds
> > + */
> > +static int et8ek8_exposure_rows_to_us(struct et8ek8_sensor *sensor, int rows)
> > +{
> > +	return (et8ek8_get_row_time(sensor) * rows + (1 << 7)) >> 8;
> > +}
> > +
> > +/* Called to change the V4L2 gain control value. This function
> > + * rounds and clamps the given value and updates the V4L2 control value.
> > + * If power is on, also updates the sensor analog and digital gains.
> > + * gain is in 0.1 EV (exposure value) units.
> > + */
> > +static int et8ek8_set_gain(struct et8ek8_sensor *sensor, s32 gain)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > +	struct et8ek8_gain new;
> > +	int r;
> > +
> > +	new = et8ek8_gain_table[gain];
> > +
> > +	/* FIXME: optimise I2C writes! */
> > +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> > +				0x124a, new.analog >> 8);
> > +	if (r)
> > +		return r;
> > +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> > +				0x1249, new.analog & 0xff);
> > +	if (r)
> > +		return r;
> > +
> > +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> > +				0x124d, new.digital >> 8);
> > +	if (r)
> > +		return r;
> > +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> > +				0x124c, new.digital & 0xff);
> > +
> > +	return r;
> > +}
> > +
> > +static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > +	int cbh_mode, cbv_mode, tp_mode, din_sw, r1420, rval;
> > +
> > +	/* Values for normal mode */
> > +	cbh_mode = 0;
> > +	cbv_mode = 0;
> > +	tp_mode  = 0;
> > +	din_sw   = 0x00;
> > +	r1420    = 0xF0;
> > +
> > +	if (mode) {
> > +		/* Test pattern mode */
> > +		if (mode < 5) {
> > +			cbh_mode = 1;
> > +			cbv_mode = 1;
> > +			tp_mode  = mode + 3;
> > +		} else {
> > +			cbh_mode = 0;
> > +			cbv_mode = 0;
> > +			tp_mode  = mode - 4 + 3;
> > +		}
> > +
> > +		din_sw   = 0x01;
> > +		r1420    = 0xE0;
> > +	}
> > +
> > +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x111B,
> > +				    tp_mode << 4);
> > +	if (rval)
> > +		return rval;
> > +
> > +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1121,
> > +				    cbh_mode << 7);
> > +	if (rval)
> > +		return rval;
> > +
> > +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124,
> > +				    cbv_mode << 7);
> > +	if (rval)
> > +		return rval;		
> > +
> > +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x112C, din_sw);
> > +	if (rval)
> > +		return rval;
> > +
> > +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420);
> > +	return rval;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 controls
> > + */
> > +
> > +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +	struct et8ek8_sensor *sensor =
> > +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> > +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > +	int uninitialized_var(rows);
> > +
> > +	if (ctrl->id == V4L2_CID_EXPOSURE)
> > +		rows = et8ek8_exposure_us_to_rows(sensor, (u32 *)&ctrl->val);
> > +
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_GAIN:
> > +		return et8ek8_set_gain(sensor, ctrl->val);
> > +
> > +	case V4L2_CID_EXPOSURE:
> > +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> > +					    swab16(rows));
> > +
> > +	case V4L2_CID_TEST_PATTERN:
> > +		return et8ek8_set_test_pattern(sensor, ctrl->val);
> > +
> > +	case V4L2_CID_PIXEL_RATE:
> > +		/* For v4l2_ctrl_s_ctrl_int64() used internally. */
> > +		return 0;
> > +
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = {
> > +	.s_ctrl = et8ek8_set_ctrl,
> > +};
> > +
> > +static const char * const et8ek8_test_pattern_menu[] = {
> > +	"Normal",
> > +	"Vertical colorbar",
> > +	"Horizontal colorbar",
> > +	"Scale",
> > +	"Ramp",
> > +	"Small vertical colorbar",
> > +	"Small horizontal colorbar",
> > +	"Small scale",
> > +	"Small ramp",
> > +};
> > +
> > +static int et8ek8_init_controls(struct et8ek8_sensor *sensor)
> > +{
> > +	u32 min, max;
> > +
> > +	v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4);
> > +
> > +	/* V4L2_CID_GAIN */
> > +	v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> > +			  V4L2_CID_GAIN, 0, ARRAY_SIZE(et8ek8_gain_table) - 1,
> > +			  1, 0);
> > +
> > +	/* V4L2_CID_EXPOSURE */
> > +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> > +	max = et8ek8_exposure_rows_to_us(sensor,
> > +				sensor->current_reglist->mode.max_exp);
> > +	sensor->exposure =
> > +		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> > +				  V4L2_CID_EXPOSURE, min, max, min, max);
> > +
> > +	/* V4L2_CID_PIXEL_RATE */
> > +	sensor->pixel_rate =
> > +		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> > +		V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
> > +
> > +	/* V4L2_CID_TEST_PATTERN */
> > +	v4l2_ctrl_new_std_menu_items(&sensor->ctrl_handler,
> > +				     &et8ek8_ctrl_ops, V4L2_CID_TEST_PATTERN,
> > +				     ARRAY_SIZE(et8ek8_test_pattern_menu) - 1,
> > +				     0, 0, et8ek8_test_pattern_menu);
> > +
> > +	if (sensor->ctrl_handler.error)
> > +		return sensor->ctrl_handler.error;
> > +
> > +	sensor->subdev.ctrl_handler = &sensor->ctrl_handler;
> > +
> > +	return 0;
> > +}
> > +
> > +static void et8ek8_update_controls(struct et8ek8_sensor *sensor)
> > +{
> > +	struct v4l2_ctrl *ctrl = sensor->exposure;
> > +	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
> > +	u32 min, max, pixel_rate;
> > +	static const int S = 8;
> > +
> > +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> > +	max = et8ek8_exposure_rows_to_us(sensor, mode->max_exp);
> > +
> > +	/*
> > +	 * Calculate average pixel clock per line. Assume buffers can spread
> > +	 * the data over horizontal blanking time. Rounding upwards.
> > +	 * Formula taken from stock Nokia N900 kernel.
> > +	 */
> > +	pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width;
> > +	pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width;
> > +
> > +	v4l2_ctrl_lock(ctrl);
> > +	ctrl->minimum = min;
> > +	ctrl->maximum = max;
> > +	ctrl->step = min;
> > +	ctrl->default_value = max;
> > +	ctrl->val = max;
> > +	ctrl->cur.val = max;
> > +	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
> > +	v4l2_ctrl_unlock(ctrl);
> > +}
> > +
> > +static int et8ek8_configure(struct et8ek8_sensor *sensor)
> > +{
> > +	struct v4l2_subdev *subdev = &sensor->subdev;
> > +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> > +	int rval;
> > +
> > +	rval = et8ek8_i2c_write_regs(client, sensor->current_reglist->regs);
> > +	if (rval)
> > +		goto fail;
> > +
> > +	/* Controls set while the power to the sensor is turned off are saved
> > +	 * but not applied to the hardware. Now that we're about to start
> > +	 * streaming apply all the current values to the hardware.
> > +	 */
> > +	rval = v4l2_ctrl_handler_setup(&sensor->ctrl_handler);
> > +	if (rval)
> > +		goto fail;
> > +
> > +	return 0;
> > +
> > +fail:
> > +	dev_err(&client->dev, "sensor configuration failed\n");
> > +
> > +	return rval;
> > +}
> > +
> > +static int et8ek8_stream_on(struct et8ek8_sensor *sensor)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > +
> > +	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0xb0);
> > +}
> > +
> > +static int et8ek8_stream_off(struct et8ek8_sensor *sensor)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > +
> > +	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0x30);
> > +}
> > +
> > +static int et8ek8_s_stream(struct v4l2_subdev *subdev, int streaming)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	int ret;
> > +
> > +	if (!streaming)
> > +		return et8ek8_stream_off(sensor);
> > +
> > +	ret = et8ek8_configure(sensor);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	return et8ek8_stream_on(sensor);
> > +}
> > +
> > +/* --------------------------------------------------------------------------
> > + * V4L2 subdev operations
> > + */
> > +
> > +static int et8ek8_power_off(struct et8ek8_sensor *sensor)
> > +{
> > +	gpiod_set_value(sensor->reset, 0);
> > +	udelay(1);
> > +
> > +	clk_disable_unprepare(sensor->ext_clk);
> > +
> > +	return regulator_disable(sensor->vana);
> > +}
> > +
> > +static int et8ek8_power_on(struct et8ek8_sensor *sensor)
> > +{
> > +	struct v4l2_subdev *subdev = &sensor->subdev;
> > +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> > +	unsigned int xclk_freq;
> > +	int val, rval;
> > +
> > +	rval = regulator_enable(sensor->vana);
> > +	if (rval) {
> > +		dev_err(&client->dev, "failed to enable vana regulator\n");
> > +		return rval;
> > +	}
> > +
> > +	if (sensor->current_reglist)
> > +		xclk_freq = sensor->current_reglist->mode.ext_clock;
> > +	else
> > +		xclk_freq = sensor->xclk_freq;
> > +
> > +	rval = clk_set_rate(sensor->ext_clk, xclk_freq);
> > +	if (rval < 0) {
> > +		dev_err(&client->dev, "unable to set extclk clock freq to %u\n",
> > +			xclk_freq);
> > +		goto out;
> > +	}
> > +	rval = clk_prepare_enable(sensor->ext_clk);
> > +	if (rval < 0) {
> > +		dev_err(&client->dev, "failed to enable extclk\n");
> > +		goto out;
> > +	}
> > +
> > +	if (rval)
> > +		goto out;
> > +
> > +	udelay(10); /* I wish this is a good value */
> > +
> > +	gpiod_set_value(sensor->reset, 1);
> > +
> > +	msleep(5000 * 1000 / xclk_freq + 1); /* Wait 5000 cycles */
> > +
> > +	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
> > +					     ET8EK8_REGLIST_POWERON);
> > +	if (rval)
> > +		goto out;
> > +
> > +#ifdef USE_CRC
> > +	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, 0x1263, &val);
> > +	if (rval)
> > +		goto out;
> > +#if USE_CRC /* TODO get crc setting from DT */
> > +	val |= BIT(4);
> > +#else
> > +	val &= ~BIT(4);
> > +#endif
> > +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1263, val);
> > +	if (rval)
> > +		goto out;
> > +#endif
> > +
> > +out:
> > +	if (rval)
> > +		et8ek8_power_off(sensor);
> > +
> > +	return rval;
> > +}
> > +
> > +/* --------------------------------------------------------------------------
> > + * V4L2 subdev video operations
> > + */
> > +#define MAX_FMTS 4
> > +static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev,
> > +				 struct v4l2_subdev_pad_config *cfg,
> > +				 struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +	struct et8ek8_reglist **list =
> > +			et8ek8_reglist_first(&meta_reglist);
> > +	u32 pixelformat[MAX_FMTS];
> > +	int npixelformat = 0;
> > +
> > +	if (code->index >= MAX_FMTS)
> > +		return -EINVAL;
> > +
> > +	for (; *list; list++) {
> > +		struct et8ek8_mode *mode = &(*list)->mode;
> > +		int i;
> > +
> > +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> > +			continue;
> > +
> > +		for (i = 0; i < npixelformat; i++) {
> > +			if (pixelformat[i] == mode->pixel_format)
> > +				break;
> > +		}
> > +		if (i != npixelformat)
> > +			continue;
> > +
> > +		if (code->index == npixelformat) {
> > +			if (mode->pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
> > +				code->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
> > +			else
> > +				code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +			return 0;
> > +		}
> > +
> > +		pixelformat[npixelformat] = mode->pixel_format;
> > +		npixelformat++;
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static int et8ek8_enum_frame_size(struct v4l2_subdev *subdev,
> > +				  struct v4l2_subdev_pad_config *cfg,
> > +				  struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > +	struct et8ek8_reglist **list =
> > +			et8ek8_reglist_first(&meta_reglist);
> > +	struct v4l2_mbus_framefmt format;
> > +	int cmp_width = INT_MAX;
> > +	int cmp_height = INT_MAX;
> > +	int index = fse->index;
> > +
> > +	for (; *list; list++) {
> > +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> > +			continue;
> > +
> > +		et8ek8_reglist_to_mbus(*list, &format);
> > +		if (fse->code != format.code)
> > +			continue;
> > +
> > +		/* Assume that the modes are grouped by frame size. */
> > +		if (format.width == cmp_width && format.height == cmp_height)
> > +			continue;
> > +
> > +		cmp_width = format.width;
> > +		cmp_height = format.height;
> > +
> > +		if (index-- == 0) {
> > +			fse->min_width = format.width;
> > +			fse->min_height = format.height;
> > +			fse->max_width = format.width;
> > +			fse->max_height = format.height;
> > +			return 0;
> > +		}
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static int et8ek8_enum_frame_ival(struct v4l2_subdev *subdev,
> > +				  struct v4l2_subdev_pad_config *cfg,
> > +				  struct v4l2_subdev_frame_interval_enum *fie)
> > +{
> > +	struct et8ek8_reglist **list =
> > +			et8ek8_reglist_first(&meta_reglist);
> > +	struct v4l2_mbus_framefmt format;
> > +	int index = fie->index;
> > +
> > +	for (; *list; list++) {
> > +		struct et8ek8_mode *mode = &(*list)->mode;
> > +
> > +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> > +			continue;
> > +
> > +		et8ek8_reglist_to_mbus(*list, &format);
> > +		if (fie->code != format.code)
> > +			continue;
> > +
> > +		if (fie->width != format.width || fie->height != format.height)
> > +			continue;
> > +
> > +		if (index-- == 0) {
> > +			fie->interval = mode->timeperframe;
> > +			return 0;
> > +		}
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static struct v4l2_mbus_framefmt *
> > +__et8ek8_get_pad_format(struct et8ek8_sensor *sensor,
> > +			struct v4l2_subdev_pad_config *cfg,
> > +			unsigned int pad, enum v4l2_subdev_format_whence which)
> > +{
> > +	switch (which) {
> > +	case V4L2_SUBDEV_FORMAT_TRY:
> > +		return v4l2_subdev_get_try_format(&sensor->subdev, cfg, pad);
> > +	case V4L2_SUBDEV_FORMAT_ACTIVE:
> > +		return &sensor->format;
> > +	default:
> > +		return NULL;
> > +	}
> > +}
> > +
> > +static int et8ek8_get_pad_format(struct v4l2_subdev *subdev,
> > +				 struct v4l2_subdev_pad_config *cfg,
> > +				 struct v4l2_subdev_format *fmt)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	struct v4l2_mbus_framefmt *format;
> > +
> > +	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
> > +	if (!format)
> > +		return -EINVAL;
> > +
> > +	fmt->format = *format;
> > +
> > +	return 0;
> > +}
> > +
> > +static int et8ek8_set_pad_format(struct v4l2_subdev *subdev,
> > +				 struct v4l2_subdev_pad_config *cfg,
> > +				 struct v4l2_subdev_format *fmt)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	struct v4l2_mbus_framefmt *format;
> > +	struct et8ek8_reglist *reglist;
> > +
> > +	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
> > +	if (!format)
> > +		return -EINVAL;
> > +
> > +	reglist = et8ek8_reglist_find_mode_fmt(&meta_reglist, &fmt->format);
> > +	et8ek8_reglist_to_mbus(reglist, &fmt->format);
> > +	*format = fmt->format;
> > +
> > +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > +		sensor->current_reglist = reglist;
> > +		et8ek8_update_controls(sensor);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int et8ek8_get_frame_interval(struct v4l2_subdev *subdev,
> > +				     struct v4l2_subdev_frame_interval *fi)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +
> > +	memset(fi, 0, sizeof(*fi));
> > +	fi->interval = sensor->current_reglist->mode.timeperframe;
> > +
> > +	return 0;
> > +}
> > +
> > +static int et8ek8_set_frame_interval(struct v4l2_subdev *subdev,
> > +				     struct v4l2_subdev_frame_interval *fi)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	struct et8ek8_reglist *reglist;
> > +
> > +	reglist = et8ek8_reglist_find_mode_ival(&meta_reglist,
> > +						sensor->current_reglist,
> > +						&fi->interval);
> > +
> > +	if (!reglist)
> > +		return -EINVAL;
> > +
> > +	if (sensor->current_reglist->mode.ext_clock != reglist->mode.ext_clock)
> > +		return -EINVAL;
> > +
> > +	sensor->current_reglist = reglist;
> > +	et8ek8_update_controls(sensor);
> > +
> > +	return 0;
> > +}
> > +
> > +static int et8ek8_g_priv_mem(struct v4l2_subdev *subdev)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> > +	unsigned int length = ET8EK8_PRIV_MEM_SIZE;
> > +	unsigned int offset = 0;
> > +	u8 *ptr  = sensor->priv_mem;
> > +	int rval = 0;
> > +
> > +	/* Read the EEPROM window-by-window, each window 8 bytes */
> > +	do {
> > +		u8 buffer[PRIV_MEM_WIN_SIZE];
> > +		struct i2c_msg msg;
> > +		int bytes, i;
> > +		int ofs;
> > +
> > +		/* Set the current window */
> > +		rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x0001,
> > +					    0xe0 | (offset >> 3));
> > +		if (rval < 0)
> > +			return rval;
> > +
> > +		/* Wait for status bit */
> > +		for (i = 0; i < 1000; ++i) {
> > +			u32 status;
> > +
> > +			rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> > +						   0x0003, &status);
> > +			if (rval < 0)
> > +				return rval;
> > +			if (!(status & 0x08))
> > +				break;
> > +			usleep_range(1000, 2000);
> > +		};
> > +
> > +		if (i == 1000)
> > +			return -EIO;
> > +
> > +		/* Read window, 8 bytes at once, and copy to user space */
> > +		ofs = offset & 0x07;	/* Offset within this window */
> > +		bytes = length + ofs > 8 ? 8-ofs : length;
> > +		msg.addr = client->addr;
> > +		msg.flags = 0;
> > +		msg.len = 2;
> > +		msg.buf = buffer;
> > +		ofs += PRIV_MEM_START_REG;
> > +		buffer[0] = (u8)(ofs >> 8);
> > +		buffer[1] = (u8)(ofs & 0xFF);
> > +
> > +		rval = i2c_transfer(client->adapter, &msg, 1);
> > +		if (rval < 0)
> > +			return rval;
> > +
> > +		mdelay(ET8EK8_I2C_DELAY);
> > +		msg.addr = client->addr;
> > +		msg.len = bytes;
> > +		msg.flags = I2C_M_RD;
> > +		msg.buf = buffer;
> > +		memset(buffer, 0, sizeof(buffer));
> > +
> > +		rval = i2c_transfer(client->adapter, &msg, 1);
> > +		if (rval < 0)
> > +			return rval;
> > +
> > +		rval = 0;
> > +		memcpy(ptr, buffer, bytes);
> > +
> > +		length -= bytes;
> > +		offset += bytes;
> > +		ptr += bytes;
> > +	} while (length > 0);
> > +
> > +	return rval;
> > +}
> > +
> > +static int et8ek8_dev_init(struct v4l2_subdev *subdev)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> > +	int rval, rev_l, rev_h;
> > +
> > +	rval = et8ek8_power_on(sensor);
> > +	if (rval) {
> > +		dev_err(&client->dev, "could not power on\n");
> > +		return rval;
> > +	}
> > +
> > +	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> > +				   REG_REVISION_NUMBER_L, &rev_l);
> > +	if (!rval)
> > +		rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> > +					   REG_REVISION_NUMBER_H, &rev_h);
> > +	if (rval) {
> > +		dev_err(&client->dev, "no et8ek8 sensor detected\n");
> > +		goto out_poweroff;
> > +	}
> > +
> > +	sensor->version = (rev_h << 8) + rev_l;
> > +	if (sensor->version != ET8EK8_REV_1 && sensor->version != ET8EK8_REV_2)
> > +		dev_info(&client->dev,
> > +			 "unknown version 0x%x detected, continuing anyway\n",
> > +			 sensor->version);
> > +
> > +	rval = et8ek8_reglist_import(client, &meta_reglist);
> > +	if (rval) {
> > +		dev_err(&client->dev,
> > +			"invalid register list %s, import failed\n",
> > +			ET8EK8_NAME);
> > +		goto out_poweroff;
> > +	}
> > +
> > +	sensor->current_reglist = et8ek8_reglist_find_type(&meta_reglist,
> > +							   ET8EK8_REGLIST_MODE);
> > +	if (!sensor->current_reglist) {
> > +		dev_err(&client->dev,
> > +			"invalid register list %s, no mode found\n",
> > +			ET8EK8_NAME);
> > +		rval = -ENODEV;
> > +		goto out_poweroff;
> > +	}
> > +
> > +	et8ek8_reglist_to_mbus(sensor->current_reglist, &sensor->format);
> > +
> > +	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
> > +					     ET8EK8_REGLIST_POWERON);
> > +	if (rval) {
> > +		dev_err(&client->dev,
> > +			"invalid register list %s, no POWERON mode found\n",
> > +			ET8EK8_NAME);
> > +		goto out_poweroff;
> > +	}
> > +	rval = et8ek8_stream_on(sensor); /* Needed to be able to read EEPROM */
> > +	if (rval)
> > +		goto out_poweroff;
> > +	rval = et8ek8_g_priv_mem(subdev);
> > +	if (rval)
> > +		dev_warn(&client->dev,
> > +			"can not read OTP (EEPROM) memory from sensor\n");
> > +	rval = et8ek8_stream_off(sensor);
> > +	if (rval)
> > +		goto out_poweroff;
> > +
> > +	rval = et8ek8_power_off(sensor);
> > +	if (rval)
> > +		goto out_poweroff;
> > +
> > +	return 0;
> > +
> > +out_poweroff:
> > +	et8ek8_power_off(sensor);
> > +
> > +	return rval;
> > +}
> > +
> > +/* --------------------------------------------------------------------------
> > + * sysfs attributes
> > + */
> > +static ssize_t
> > +et8ek8_priv_mem_read(struct device *dev, struct device_attribute *attr,
> > +		     char *buf)
> > +{
> > +	struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +
> > +#if PAGE_SIZE < ET8EK8_PRIV_MEM_SIZE
> > +#error PAGE_SIZE too small!
> > +#endif
> > +
> > +	memcpy(buf, sensor->priv_mem, ET8EK8_PRIV_MEM_SIZE);
> > +
> > +	return ET8EK8_PRIV_MEM_SIZE;
> > +}
> > +static DEVICE_ATTR(priv_mem, S_IRUGO, et8ek8_priv_mem_read, NULL);
> > +
> > +/* --------------------------------------------------------------------------
> > + * V4L2 subdev core operations
> > + */
> > +
> > +static int
> > +et8ek8_registered(struct v4l2_subdev *subdev)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> > +	struct v4l2_mbus_framefmt *format;
> > +	int rval;
> > +
> > +	dev_dbg(&client->dev, "registered!");
> > +
> > +	rval = device_create_file(&client->dev, &dev_attr_priv_mem);
> > +	if (rval) {
> > +		dev_err(&client->dev, "could not register sysfs entry\n");
> > +		return rval;
> > +	}
> > +
> > +	rval = et8ek8_dev_init(subdev);
> > +	if (rval)
> > +		goto err_file;
> > +
> > +	rval = et8ek8_init_controls(sensor);
> > +	if (rval) {
> > +		dev_err(&client->dev, "controls initialization failed\n");
> > +		goto err_file;
> > +	}
> > +
> > +	format = __et8ek8_get_pad_format(sensor, NULL, 0,
> > +					 V4L2_SUBDEV_FORMAT_ACTIVE);
> > +	return 0;
> > +
> > +err_file:
> > +	device_remove_file(&client->dev, &dev_attr_priv_mem);
> > +
> > +	return rval;
> > +}
> > +
> > +static int __et8ek8_set_power(struct et8ek8_sensor *sensor, bool on)
> > +{
> > +	return on ? et8ek8_power_on(sensor) : et8ek8_power_off(sensor);
> > +}
> > +
> > +static int et8ek8_set_power(struct v4l2_subdev *subdev, int on)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +	int ret = 0;
> > +
> > +	mutex_lock(&sensor->power_lock);
> > +
> > +	/* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > +	 * update the power state.
> > +	 */
> > +	if (sensor->power_count == !on) {
> > +		ret = __et8ek8_set_power(sensor, !!on);
> > +		if (ret < 0)
> > +			goto done;
> > +	}
> > +
> > +	/* Update the power count. */
> > +	sensor->power_count += on ? 1 : -1;
> > +	WARN_ON(sensor->power_count < 0);
> > +
> > +done:
> > +	mutex_unlock(&sensor->power_lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int et8ek8_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> > +{
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(sd);
> > +	struct v4l2_mbus_framefmt *format;
> > +	struct et8ek8_reglist *reglist;
> > +
> > +	reglist = et8ek8_reglist_find_type(&meta_reglist, ET8EK8_REGLIST_MODE);
> > +	format = __et8ek8_get_pad_format(sensor, fh->pad, 0,
> > +					 V4L2_SUBDEV_FORMAT_TRY);
> > +	et8ek8_reglist_to_mbus(reglist, format);
> > +
> > +	return et8ek8_set_power(sd, true);
> > +}
> > +
> > +static int et8ek8_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> > +{
> > +	return et8ek8_set_power(sd, false);
> > +}
> > +
> > +static const struct v4l2_subdev_video_ops et8ek8_video_ops = {
> > +	.s_stream = et8ek8_s_stream,
> > +	.g_frame_interval = et8ek8_get_frame_interval,
> > +	.s_frame_interval = et8ek8_set_frame_interval,
> > +};
> > +
> > +static const struct v4l2_subdev_core_ops et8ek8_core_ops = {
> > +	.s_power = et8ek8_set_power,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops et8ek8_pad_ops = {
> > +	.enum_mbus_code = et8ek8_enum_mbus_code,
> > +	.enum_frame_size = et8ek8_enum_frame_size,
> > +	.enum_frame_interval = et8ek8_enum_frame_ival,
> > +	.get_fmt = et8ek8_get_pad_format,
> > +	.set_fmt = et8ek8_set_pad_format,
> > +};
> > +
> > +static const struct v4l2_subdev_ops et8ek8_ops = {
> > +	.core = &et8ek8_core_ops,
> > +	.video = &et8ek8_video_ops,
> > +	.pad = &et8ek8_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = {
> > +	.registered = et8ek8_registered,
> > +	.open = et8ek8_open,
> > +	.close = et8ek8_close,
> > +};
> > +
> > +/* --------------------------------------------------------------------------
> > + * I2C driver
> > + */
> > +#ifdef CONFIG_PM
> > +
> > +static int et8ek8_suspend(struct device *dev)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +
> > +	if (!sensor->power_count)
> > +		return 0;
> > +
> > +	return __et8ek8_set_power(sensor, false);
> > +}
> > +
> > +static int et8ek8_resume(struct device *dev)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +
> > +	if (!sensor->power_count)
> > +		return 0;
> > +
> > +	return __et8ek8_set_power(sensor, true);
> > +}
> > +
> > +#else
> > +
> > +#define et8ek8_suspend NULL
> > +#define et8ek8_resume NULL
> > +
> > +#endif /* CONFIG_PM */
> > +
> > +static int et8ek8_probe(struct i2c_client *client,
> > +			const struct i2c_device_id *devid)
> > +{
> > +	struct et8ek8_sensor *sensor;
> > +	struct device *dev = &client->dev;
> > +	int ret;
> > +
> > +	sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
> > +	if (!sensor)
> > +		return -ENOMEM;
> > +
> > +	sensor->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> > +	if (IS_ERR(sensor->reset)) {
> > +		dev_dbg(&client->dev, "could not request reset gpio\n");
> > +		return PTR_ERR(sensor->reset);
> > +	}
> > +
> > +	sensor->vana = devm_regulator_get(dev, "vana");
> > +	if (IS_ERR(sensor->vana)) {
> > +		dev_err(&client->dev, "could not get regulator for vana\n");
> > +		return PTR_ERR(sensor->vana);
> > +	}
> > +
> > +	sensor->ext_clk = devm_clk_get(dev, NULL);
> > +	if (IS_ERR(sensor->ext_clk)) {
> > +		dev_err(&client->dev, "could not get clock\n");
> > +		return PTR_ERR(sensor->ext_clk);
> > +	}
> > +
> > +	ret = of_property_read_u32(dev->of_node, "clock-frequency",
> > +				   &sensor->xclk_freq);
> > +	if (ret) {
> > +		dev_warn(dev, "can't get clock-frequency\n");
> > +		return ret;
> > +	}
> > +
> > +	mutex_init(&sensor->power_lock);
> 
> mutex_destroy() should be called on the mutex if probe fails after this and
> in remove().
> 
> > +
> > +	v4l2_i2c_subdev_init(&sensor->subdev, client, &et8ek8_ops);
> > +	sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +	sensor->subdev.internal_ops = &et8ek8_internal_ops;
> > +
> > +	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +	ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad);
> > +	if (ret < 0) {
> > +		dev_err(&client->dev, "media entity init failed!\n");
> > +		return ret;
> > +	}
> > +
> > +	ret = v4l2_async_register_subdev(&sensor->subdev);
> > +	if (ret < 0) {
> > +		media_entity_cleanup(&sensor->subdev.entity);
> > +		return ret;
> > +	}
> > +
> > +	dev_dbg(dev, "initialized!\n");
> > +
> > +	return 0;
> > +}
> > +
> > +static int __exit et8ek8_remove(struct i2c_client *client)
> > +{
> > +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +
> > +	if (sensor->power_count) {
> > +		gpiod_set_value(sensor->reset, 0);
> > +		clk_disable_unprepare(sensor->ext_clk);
> 
> How about the regulator? Could you call et8ek8_power_off() instead?
> 
> > +		sensor->power_count = 0;
> > +	}
> > +
> > +	v4l2_device_unregister_subdev(&sensor->subdev);
> > +	device_remove_file(&client->dev, &dev_attr_priv_mem);
> > +	v4l2_ctrl_handler_free(&sensor->ctrl_handler);
> > +	media_entity_cleanup(&sensor->subdev.entity);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id et8ek8_of_table[] = {
> > +	{ .compatible = "toshiba,et8ek8" },
> > +	{ },
> > +};
> > +
> > +static const struct i2c_device_id et8ek8_id_table[] = {
> > +	{ ET8EK8_NAME, 0 },
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, et8ek8_id_table);
> > +
> > +static const struct dev_pm_ops et8ek8_pm_ops = {
> > +	.suspend	= et8ek8_suspend,
> > +	.resume		= et8ek8_resume,
> > +};
> > +
> > +static struct i2c_driver et8ek8_i2c_driver = {
> > +	.driver		= {
> > +		.name	= ET8EK8_NAME,
> > +		.pm	= &et8ek8_pm_ops,
> > +		.of_match_table	= et8ek8_of_table,
> > +	},
> > +	.probe		= et8ek8_probe,
> > +	.remove		= __exit_p(et8ek8_remove),
> > +	.id_table	= et8ek8_id_table,
> > +};
> > +
> > +module_i2c_driver(et8ek8_i2c_driver);
> > +
> > +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
> > +MODULE_DESCRIPTION("Toshiba ET8EK8 camera sensor driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/media/i2c/et8ek8/et8ek8_mode.c b/drivers/media/i2c/et8ek8/et8ek8_mode.c
> > new file mode 100644
> > index 0000000..956fc60
> > --- /dev/null
> > +++ b/drivers/media/i2c/et8ek8/et8ek8_mode.c
> > @@ -0,0 +1,587 @@
> > +/*
> > + * et8ek8_mode.c
> > + *
> > + * Copyright (C) 2008 Nokia Corporation
> > + *
> > + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> > + *          Tuukka Toivonen <tuukka.o.toivonen@nokia.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License
> > + * version 2 as published by the Free Software Foundation.
> > + *
> > + * This program is distributed in the hope that it will be useful, but
> > + * WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + * General Public License for more details.
> > + */
> > +
> > +#include "et8ek8_reg.h"
> > +
> > +/*
> > + * Stingray sensor mode settings for Scooby
> > + */
> > +
> > +/* Mode1_poweron_Mode2_16VGA_2592x1968_12.07fps */
> > +static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = {
> > +/* (without the +1)
> > + * SPCK       = 80 MHz
> > + * CCP2       = 640 MHz
> > + * VCO        = 640 MHz
> > + * VCOUNT     = 84 (2016)
> > + * HCOUNT     = 137 (3288)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 200
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 7
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 0
> > + */
> > +	.type = ET8EK8_REGLIST_POWERON,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3288,
> > +		.height = 2016,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 2592,
> > +		.window_height = 1968,
> > +		.pixel_clock = 80000000,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 1207
> > +		},
> > +		.max_exp = 2012,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		/* Need to set firstly */
> > +		{ ET8EK8_REG_8BIT, 0x126C, 0xCC },
> > +		/* Strobe and Data of CCP2 delay are minimized. */
> > +		{ ET8EK8_REG_8BIT, 0x1269, 0x00 },
> > +		/* Refined value of Min H_COUNT  */
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> > +		/* Frequency of SPCK setting (SPCK=MRCK) */
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> > +		{ ET8EK8_REG_8BIT, 0x1241, 0x94 },
> > +		{ ET8EK8_REG_8BIT, 0x1242, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x124B, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1255, 0xFF },
> > +		{ ET8EK8_REG_8BIT, 0x1256, 0x9F },
> > +		{ ET8EK8_REG_8BIT, 0x1258, 0x00 },
> > +		/* From parallel out to serial out */
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 },
> > +		/* From w/ embeded data to w/o embeded data */
> > +		{ ET8EK8_REG_8BIT, 0x125E, 0xC0 },
> > +		/* CCP2 out is from STOP to ACTIVE */
> > +		{ ET8EK8_REG_8BIT, 0x1263, 0x98 },
> > +		{ ET8EK8_REG_8BIT, 0x1268, 0xC6 },
> > +		{ ET8EK8_REG_8BIT, 0x1434, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1163, 0x44 },
> > +		{ ET8EK8_REG_8BIT, 0x1166, 0x29 },
> > +		{ ET8EK8_REG_8BIT, 0x1140, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x1011, 0x24 },
> > +		{ ET8EK8_REG_8BIT, 0x1151, 0x80 },
> > +		{ ET8EK8_REG_8BIT, 0x1152, 0x23 },
> > +		/* Initial setting for improvement2 of lower frequency noise */
> > +		{ ET8EK8_REG_8BIT, 0x1014, 0x05 },
> > +		{ ET8EK8_REG_8BIT, 0x1033, 0x06 },
> > +		{ ET8EK8_REG_8BIT, 0x1034, 0x79 },
> > +		{ ET8EK8_REG_8BIT, 0x1423, 0x3F },
> > +		{ ET8EK8_REG_8BIT, 0x1424, 0x3F },
> > +		{ ET8EK8_REG_8BIT, 0x1426, 0x00 },
> > +		/* Switch of Preset-White-balance (0d:disable / 1d:enable) */
> > +		{ ET8EK8_REG_8BIT, 0x1439, 0x00 },
> > +		/* Switch of blemish correction (0d:disable / 1d:enable) */
> > +		{ ET8EK8_REG_8BIT, 0x161F, 0x60 },
> > +		/* Switch of auto noise correction (0d:disable / 1d:enable) */
> > +		{ ET8EK8_REG_8BIT, 0x1634, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1646, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1648, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x113E, 0x01 },
> > +		{ ET8EK8_REG_8BIT, 0x113F, 0x22 },
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode1_16VGA_2592x1968_13.12fps_DPCM10-8 */
> > +static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = {
> > +/* (without the +1)
> > + * SPCK       = 80 MHz
> > + * CCP2       = 560 MHz
> > + * VCO        = 560 MHz
> > + * VCOUNT     = 84 (2016)
> > + * HCOUNT     = 128 (3072)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 175
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 6
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 0
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3072,
> > +		.height = 2016,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 2592,
> > +		.window_height = 1968,
> > +		.pixel_clock = 80000000,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 1292
> > +		},
> > +		.max_exp = 2012,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x57 },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x06 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x80 }, /* <-changed to v14 7E->80 */
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode3_4VGA_1296x984_29.99fps_DPCM10-8 */
> > +static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = {
> > +/* (without the +1)
> > + * SPCK       = 96.5333333333333 MHz
> > + * CCP2       = 579.2 MHz
> > + * VCO        = 579.2 MHz
> > + * VCOUNT     = 84 (2016)
> > + * HCOUNT     = 133 (3192)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 181
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 5
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 0
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3192,
> > +		.height = 1008,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 1296,
> > +		.window_height = 984,
> > +		.pixel_clock = 96533333,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 3000
> > +		},
> > +		.max_exp = 1004,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x5A },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode4_SVGA_864x656_29.88fps */
> > +static struct et8ek8_reglist mode4_svga_864x656_29_88fps = {
> > +/* (without the +1)
> > + * SPCK       = 80 MHz
> > + * CCP2       = 320 MHz
> > + * VCO        = 640 MHz
> > + * VCOUNT     = 84 (2016)
> > + * HCOUNT     = 166 (3984)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 200
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 7
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 1
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3984,
> > +		.height = 672,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 864,
> > +		.window_height = 656,
> > +		.pixel_clock = 80000000,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 2988
> > +		},
> > +		.max_exp = 668,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x62 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x62 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0xA6 },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode5_VGA_648x492_29.93fps */
> > +static struct et8ek8_reglist mode5_vga_648x492_29_93fps = {
> > +/* (without the +1)
> > + * SPCK       = 80 MHz
> > + * CCP2       = 320 MHz
> > + * VCO        = 640 MHz
> > + * VCOUNT     = 84 (2016)
> > + * HCOUNT     = 221 (5304)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 200
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 7
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 1
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 5304,
> > +		.height = 504,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 648,
> > +		.window_height = 492,
> > +		.pixel_clock = 80000000,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 2993
> > +		},
> > +		.max_exp = 500,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode2_16VGA_2592x1968_3.99fps */
> > +static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = {
> > +/* (without the +1)
> > + * SPCK       = 80 MHz
> > + * CCP2       = 640 MHz
> > + * VCO        = 640 MHz
> > + * VCOUNT     = 254 (6096)
> > + * HCOUNT     = 137 (3288)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 200
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 7
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 0
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3288,
> > +		.height = 6096,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 2592,
> > +		.window_height = 1968,
> > +		.pixel_clock = 80000000,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 399
> > +		},
> > +		.max_exp = 6092,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0xFE },
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode_648x492_5fps */
> > +static struct et8ek8_reglist mode_648x492_5fps = {
> > +/* (without the +1)
> > + * SPCK       = 13.3333333333333 MHz
> > + * CCP2       = 53.3333333333333 MHz
> > + * VCO        = 640 MHz
> > + * VCOUNT     = 84 (2016)
> > + * HCOUNT     = 221 (5304)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 200
> > + * VCO_DIV    = 5
> > + * SPCK_DIV   = 7
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 1
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 5304,
> > +		.height = 504,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 648,
> > +		.window_height = 492,
> > +		.pixel_clock = 13333333,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 499
> > +		},
> > +		.max_exp = 500,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x57 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode3_4VGA_1296x984_5fps */
> > +static struct et8ek8_reglist mode3_4vga_1296x984_5fps = {
> > +/* (without the +1)
> > + * SPCK       = 49.4 MHz
> > + * CCP2       = 395.2 MHz
> > + * VCO        = 790.4 MHz
> > + * VCOUNT     = 250 (6000)
> > + * HCOUNT     = 137 (3288)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 247
> > + * VCO_DIV    = 1
> > + * SPCK_DIV   = 7
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 0
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3288,
> > +		.height = 3000,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 1296,
> > +		.window_height = 984,
> > +		.pixel_clock = 49400000,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 501
> > +		},
> > +		.max_exp = 2996,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x7B },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x17 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0xFA },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +/* Mode_4VGA_1296x984_25fps_DPCM10-8 */
> > +static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = {
> > +/* (without the +1)
> > + * SPCK       = 84.2666666666667 MHz
> > + * CCP2       = 505.6 MHz
> > + * VCO        = 505.6 MHz
> > + * VCOUNT     = 88 (2112)
> > + * HCOUNT     = 133 (3192)
> > + * CKREF_DIV  = 2
> > + * CKVAR_DIV  = 158
> > + * VCO_DIV    = 0
> > + * SPCK_DIV   = 5
> > + * MRCK_DIV   = 7
> > + * LVDSCK_DIV = 0
> > + */
> > +	.type = ET8EK8_REGLIST_MODE,
> > +	.mode = {
> > +		.sensor_width = 2592,
> > +		.sensor_height = 1968,
> > +		.sensor_window_origin_x = 0,
> > +		.sensor_window_origin_y = 0,
> > +		.sensor_window_width = 2592,
> > +		.sensor_window_height = 1968,
> > +		.width = 3192,
> > +		.height = 1056,
> > +		.window_origin_x = 0,
> > +		.window_origin_y = 0,
> > +		.window_width = 1296,
> > +		.window_height = 984,
> > +		.pixel_clock = 84266667,
> > +		.ext_clock = 9600000,
> > +		.timeperframe = {
> > +			.numerator = 100,
> > +			.denominator = 2500
> > +		},
> > +		.max_exp = 1052,
> > +		/* .max_gain = 0, */
> > +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> > +		.sensitivity = 65536
> > +	},
> > +	.regs = {
> > +		{ ET8EK8_REG_8BIT, 0x1239, 0x4F },
> > +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> > +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> > +		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
> > +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> > +		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
> > +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x1222, 0x58 },
> > +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> > +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> > +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 },
> > +		{ ET8EK8_REG_TERM, 0, 0}
> > +	}
> > +};
> > +
> > +struct et8ek8_meta_reglist meta_reglist = {
> > +	.version = "V14 03-June-2008",
> > +	.reglist = {
> > +		{ .ptr = &mode1_poweron_mode2_16vga_2592x1968_12_07fps },
> > +		{ .ptr = &mode1_16vga_2592x1968_13_12fps_dpcm10_8 },
> > +		{ .ptr = &mode3_4vga_1296x984_29_99fps_dpcm10_8 },
> > +		{ .ptr = &mode4_svga_864x656_29_88fps },
> > +		{ .ptr = &mode5_vga_648x492_29_93fps },
> > +		{ .ptr = &mode2_16vga_2592x1968_3_99fps },
> > +		{ .ptr = &mode_648x492_5fps },
> > +		{ .ptr = &mode3_4vga_1296x984_5fps },
> > +		{ .ptr = &mode_4vga_1296x984_25fps_dpcm10_8 },
> > +		{ .ptr = NULL }
> > +	}
> > +};
> > diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> > new file mode 100644
> > index 0000000..9970bff
> > --- /dev/null
> > +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> > @@ -0,0 +1,96 @@
> > +/*
> > + * et8ek8.h
> 
> et8ek8_reg.h
> 
> > + *
> > + * Copyright (C) 2008 Nokia Corporation
> > + *
> > + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> > + *          Tuukka Toivonen <tuukkat76@gmail.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License
> > + * version 2 as published by the Free Software Foundation.
> > + *
> > + * This program is distributed in the hope that it will be useful, but
> > + * WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + * General Public License for more details.
> > + */
> > +
> > +#ifndef ET8EK8REGS_H
> > +#define ET8EK8REGS_H
> > +
> > +#include <linux/i2c.h>
> > +#include <linux/types.h>
> > +#include <linux/videodev2.h>
> > +#include <linux/v4l2-subdev.h>
> > +
> > +struct v4l2_mbus_framefmt;
> > +struct v4l2_subdev_pad_mbus_code_enum;
> > +
> > +struct et8ek8_mode {
> > +	/* Physical sensor resolution and current image window */
> > +	u16 sensor_width;
> > +	u16 sensor_height;
> > +	u16 sensor_window_origin_x;
> > +	u16 sensor_window_origin_y;
> > +	u16 sensor_window_width;
> > +	u16 sensor_window_height;
> > +
> > +	/* Image data coming from sensor (after scaling) */
> > +	u16 width;
> > +	u16 height;
> > +	u16 window_origin_x;
> > +	u16 window_origin_y;
> > +	u16 window_width;
> > +	u16 window_height;
> > +
> > +	u32 pixel_clock;		/* in Hz */
> > +	u32 ext_clock;			/* in Hz */
> > +	struct v4l2_fract timeperframe;
> > +	u32 max_exp;			/* Maximum exposure value */
> > +	u32 pixel_format;		/* V4L2_PIX_FMT_xxx */
> > +	u32 sensitivity;		/* 16.16 fixed point */
> > +};
> > +
> > +#define ET8EK8_REG_8BIT			1
> > +#define ET8EK8_REG_16BIT		2
> > +#define ET8EK8_REG_DELAY		100
> > +#define ET8EK8_REG_TERM			0xff
> > +struct et8ek8_reg {
> > +	u16 type;
> > +	u16 reg;			/* 16-bit offset */
> > +	u32 val;			/* 8/16/32-bit value */
> > +};
> > +
> > +/* Possible struct smia_reglist types. */
> > +#define ET8EK8_REGLIST_STANDBY		0
> > +#define ET8EK8_REGLIST_POWERON		1
> > +#define ET8EK8_REGLIST_RESUME		2
> > +#define ET8EK8_REGLIST_STREAMON		3
> > +#define ET8EK8_REGLIST_STREAMOFF	4
> > +#define ET8EK8_REGLIST_DISABLED		5
> > +
> > +#define ET8EK8_REGLIST_MODE		10
> > +
> > +#define ET8EK8_REGLIST_LSC_ENABLE	100
> > +#define ET8EK8_REGLIST_LSC_DISABLE	101
> > +#define ET8EK8_REGLIST_ANR_ENABLE	102
> > +#define ET8EK8_REGLIST_ANR_DISABLE	103
> > +
> > +struct et8ek8_reglist {
> > +	u32 type;
> > +	struct et8ek8_mode mode;
> > +	struct et8ek8_reg regs[];
> > +};
> > +
> > +#define ET8EK8_MAX_LEN			32
> > +struct et8ek8_meta_reglist {
> > +	char version[ET8EK8_MAX_LEN];
> > +	union {
> > +		struct et8ek8_reglist *ptr;
> > +	} reglist[];
> > +};
> > +
> > +extern struct et8ek8_meta_reglist meta_reglist;
> > +
> > +#endif /* ET8EK8REGS */
> > 
>
Pavel Machek Oct. 23, 2016, 8:40 p.m. UTC | #3
Hi!

> Thanks, this answered half of my questions already. ;-)
> 
> Do all the modes work for you currently btw.?

Aha, went through my notes. This is what it does in 5MP mode, even on
v4.9:

pavel@n900:/my/fcam-dev$ ./camera.py
['-r']
['-l', '"et8ek8 3-003e":0 -> "video-bus-switch":1 [1]']
['-l', '"video-bus-switch":0 -> "OMAP3 ISP CCP2":0 [1]']
['-l', '"OMAP3 ISP CCP2":1 -> "OMAP3 ISP CCDC":0 [1]']
['-l', '"OMAP3 ISP CCDC":1 -> "OMAP3 ISP CCDC output":0 [1]']
['-V', '"et8ek8 3-003e":0 [SGRBG10 2592x1968]']
['-V', '"OMAP3 ISP CCP2":0 [SGRBG10 2592x1968]']
['-V', '"OMAP3 ISP CCP2":1 [SGRBG10 2592x1968]']
['-V', '"OMAP3 ISP CCDC":1 [SGRBG10 2592x1968]']
['-V', '"OMAP3 ISP CCDC":2 [SGRBG10 2592x1968]']
Device /dev/video2 opened.
Device `OMAP3 ISP CCDC output' on `media' is a video capture (without
mplanes) device.
Video format set: SGRBG10 (30314142) 2592x1968 (stride 5184) field
none buffer size 10202112
Video format: SGRBG10 (30314142) 2592x1968 (stride 5184) field none
buffer size 10202112
4 buffers requested.
length: 10202112 offset: 0 timestamp type/source: mono/EoF
Buffer 0/0 mapped at address 0xb63a0000.
length: 10202112 offset: 10203136 timestamp type/source: mono/EoF
Buffer 1/0 mapped at address 0xb59e5000.
length: 10202112 offset: 20406272 timestamp type/source: mono/EoF
Buffer 2/0 mapped at address 0xb502a000.
length: 10202112 offset: 30609408 timestamp type/source: mono/EoF
Buffer 3/0 mapped at address 0xb466f000.
0 (0) [E] any 0 10202112 B 0.000000 2792.366987 0.001 fps ts mono/EoF
Unable to queue buffer: Input/output error (5).
Unable to requeue buffer: Input/output error (5).
Unable to release buffers: Device or resource busy (16).
pavel@n900:/my/fcam-dev$

(gitlab.com fcam-dev branch good)

Kernel will say

[ 2689.598358] stream on success
[ 2702.426635] Streamon
[ 2702.426727] check_format checking px 808534338 808534338, h 984
984, w 1296 1296, bpline 2592 2592, size 2550528 2550528 field 1 1
[ 2702.426818] configuring for 1296(2592)x984
[ 2702.434722] stream on success
[ 2792.276184] Streamon
[ 2792.276306] check_format checking px 808534338 808534338, h 1968
1968, w 2592 2592, bpline 5184 5184, size 10202112 10202112 field 1 1
[ 2792.276367] configuring for 2592(5184)x1968
[ 2792.284240] stream on success
[ 2792.368164] omap3isp 480bc000.isp: CCDC won't become idle!
[ 2793.901550] omap3isp 480bc000.isp: Unable to stop OMAP3 ISP CCDC
pavel@n900:/my/fcam-dev$

in this case.

Best regards,
								Pavel
Pavel Machek Oct. 23, 2016, 8:47 p.m. UTC | #4
On Sun 2016-10-23 23:19:54, Sakari Ailus wrote:
> Hi Pavel,
> 
> Thanks, this answered half of my questions already. ;-)
> 
> Do all the modes work for you currently btw.?

I pushed current kernel sources to kernel.org:

git push
git@gitolite.kernel.org:pub/scm/linux/kernel/git/pavel/linux-n900.git
camera-v4.9:camera-v4.9

									Pavel
Sakari Ailus Oct. 31, 2016, 10:54 p.m. UTC | #5
Hi Pavel,

On Sun, Oct 23, 2016 at 10:33:15PM +0200, Pavel Machek wrote:
> Hi!
> 
> > Thanks, this answered half of my questions already. ;-)
> 
> :-).
> 
> I'll have to go through the patches, et8ek8 driver is probably not
> enough to get useful video. platform/video-bus-switch.c is needed for
> camera switching, then some omap3isp patches to bind flash and
> autofocus into the subdevice.
> 
> Then, device tree support on n900 can be added.

I briefly discussed with with Sebastian.

Do you think the elusive support for the secondary camera is worth keeping
out the main camera from the DT in mainline? As long as there's a reasonable
way to get it working, I'd just merge that. If someone ever gets the
secondary camera working properly and nicely with the video bus switch,
that's cool, we'll somehow deal with the problem then. But frankly I don't
think it's very useful even if we get there: the quality is really bad.

> > Do all the modes work for you currently btw.?
> 
> I don't think I got 5MP mode to work. Even 2.5MP mode is tricky (needs
> a lot of continuous memory).

The OMAP 3 ISP has got an MMU, getting some contiguous memory is not really
a problem when you have a 4 GiB empty space to use.

> Anyway, I have to start somewhere, and I believe this is a good
> starting place; I'd like to get the code cleaned up and merged, then
> move to the next parts.

I wonder if something else could be the problem. I think the data rate is
higher in the 5 MP mode, and that might be the reason. I don't remember how
similar is the clock tree in the 3430 to the 3630. Could it be that the ISP
clock is lower than it should be for some reason, for instance?
Sakari Ailus Oct. 31, 2016, 10:58 p.m. UTC | #6
Hi Pavel,

(Cc Laurent.)

On Sun, Oct 23, 2016 at 10:40:01PM +0200, Pavel Machek wrote:
> Hi!
> 
> > Thanks, this answered half of my questions already. ;-)
> > 
> > Do all the modes work for you currently btw.?
> 
> Aha, went through my notes. This is what it does in 5MP mode, even on
> v4.9:
> 
> pavel@n900:/my/fcam-dev$ ./camera.py
> ['-r']
> ['-l', '"et8ek8 3-003e":0 -> "video-bus-switch":1 [1]']
> ['-l', '"video-bus-switch":0 -> "OMAP3 ISP CCP2":0 [1]']
> ['-l', '"OMAP3 ISP CCP2":1 -> "OMAP3 ISP CCDC":0 [1]']
> ['-l', '"OMAP3 ISP CCDC":1 -> "OMAP3 ISP CCDC output":0 [1]']
> ['-V', '"et8ek8 3-003e":0 [SGRBG10 2592x1968]']
> ['-V', '"OMAP3 ISP CCP2":0 [SGRBG10 2592x1968]']
> ['-V', '"OMAP3 ISP CCP2":1 [SGRBG10 2592x1968]']
> ['-V', '"OMAP3 ISP CCDC":1 [SGRBG10 2592x1968]']
> ['-V', '"OMAP3 ISP CCDC":2 [SGRBG10 2592x1968]']
> Device /dev/video2 opened.
> Device `OMAP3 ISP CCDC output' on `media' is a video capture (without
> mplanes) device.
> Video format set: SGRBG10 (30314142) 2592x1968 (stride 5184) field
> none buffer size 10202112
> Video format: SGRBG10 (30314142) 2592x1968 (stride 5184) field none
> buffer size 10202112
> 4 buffers requested.
> length: 10202112 offset: 0 timestamp type/source: mono/EoF
> Buffer 0/0 mapped at address 0xb63a0000.
> length: 10202112 offset: 10203136 timestamp type/source: mono/EoF
> Buffer 1/0 mapped at address 0xb59e5000.
> length: 10202112 offset: 20406272 timestamp type/source: mono/EoF
> Buffer 2/0 mapped at address 0xb502a000.
> length: 10202112 offset: 30609408 timestamp type/source: mono/EoF
> Buffer 3/0 mapped at address 0xb466f000.
> 0 (0) [E] any 0 10202112 B 0.000000 2792.366987 0.001 fps ts mono/EoF
> Unable to queue buffer: Input/output error (5).
> Unable to requeue buffer: Input/output error (5).
> Unable to release buffers: Device or resource busy (16).
> pavel@n900:/my/fcam-dev$
> 
> (gitlab.com fcam-dev branch good)
> 
> Kernel will say
> 
> [ 2689.598358] stream on success
> [ 2702.426635] Streamon
> [ 2702.426727] check_format checking px 808534338 808534338, h 984
> 984, w 1296 1296, bpline 2592 2592, size 2550528 2550528 field 1 1
> [ 2702.426818] configuring for 1296(2592)x984
> [ 2702.434722] stream on success
> [ 2792.276184] Streamon
> [ 2792.276306] check_format checking px 808534338 808534338, h 1968
> 1968, w 2592 2592, bpline 5184 5184, size 10202112 10202112 field 1 1
> [ 2792.276367] configuring for 2592(5184)x1968
> [ 2792.284240] stream on success
> [ 2792.368164] omap3isp 480bc000.isp: CCDC won't become idle!

This is Bad(tm).

It means that the driver waited for the CCDC to become idle to reprogram it,
but it didn't happen. This could be a problem in the number of lines
configured, or some polarity settings between the CCP2 receiver and the
CCDC. I suspect the latter, but I could be totally wrong here as well since
it was more than five years I worked on these things. :-I

> [ 2793.901550] omap3isp 480bc000.isp: Unable to stop OMAP3 ISP CCDC

And this is probably directly caused by the same problem. :-(
Ivaylo Dimitrov Nov. 1, 2016, 6:36 a.m. UTC | #7
Hi,

On  1.11.2016 00:54, Sakari Ailus wrote:
> Hi Pavel,
>
> On Sun, Oct 23, 2016 at 10:33:15PM +0200, Pavel Machek wrote:
>> Hi!
>>
>>> Thanks, this answered half of my questions already. ;-)
>>
>> :-).
>>
>> I'll have to go through the patches, et8ek8 driver is probably not
>> enough to get useful video. platform/video-bus-switch.c is needed for
>> camera switching, then some omap3isp patches to bind flash and
>> autofocus into the subdevice.
>>
>> Then, device tree support on n900 can be added.
>
> I briefly discussed with with Sebastian.
>
> Do you think the elusive support for the secondary camera is worth keeping
> out the main camera from the DT in mainline? As long as there's a reasonable
> way to get it working, I'd just merge that. If someone ever gets the
> secondary camera working properly and nicely with the video bus switch,
> that's cool, we'll somehow deal with the problem then. But frankly I don't
> think it's very useful even if we get there: the quality is really bad.
>

Yes, lets merge what we have till now, it will be way easier to improve 
on it once it is part of the mainline.

BTW, I have (had) patched VBS working almost without problems, when it 
comes to it I'll dig it.

>>> Do all the modes work for you currently btw.?
>>
>> I don't think I got 5MP mode to work. Even 2.5MP mode is tricky (needs
>> a lot of continuous memory).
>
> The OMAP 3 ISP has got an MMU, getting some contiguous memory is not really
> a problem when you have a 4 GiB empty space to use.
>
>> Anyway, I have to start somewhere, and I believe this is a good
>> starting place; I'd like to get the code cleaned up and merged, then
>> move to the next parts.
>
> I wonder if something else could be the problem. I think the data rate is
> higher in the 5 MP mode, and that might be the reason. I don't remember how
> similar is the clock tree in the 3430 to the 3630. Could it be that the ISP
> clock is lower than it should be for some reason, for instance?
>

IIRC I checked what Nokia kernel does, and according to my vague 
memories the frequency was the same. Still, it seems the problem is in 
ISP, it has some very fragile calculations. Yet again, having main 
camera merged will ease the problem hunting.

Regards,
Ivo
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pavel Machek Nov. 1, 2016, 3:39 p.m. UTC | #8
Hi!

> > I'll have to go through the patches, et8ek8 driver is probably not
> > enough to get useful video. platform/video-bus-switch.c is needed for
> > camera switching, then some omap3isp patches to bind flash and
> > autofocus into the subdevice.
> > 
> > Then, device tree support on n900 can be added.
> 
> I briefly discussed with with Sebastian.
> 
> Do you think the elusive support for the secondary camera is worth keeping
> out the main camera from the DT in mainline? As long as there's a reasonable
> way to get it working, I'd just merge that. If someone ever gets the
> secondary camera working properly and nicely with the video bus switch,
> that's cool, we'll somehow deal with the problem then. But frankly I don't
> think it's very useful even if we get there: the quality is really
> bad.

Well, I am a little bit worried that /dev/video* entries will
renumber themself when the the front camera support is merged,
breaking userspace.

But the first step is still the same: get et8ek8 support merged :-).

> > > Do all the modes work for you currently btw.?
> > 
> > I don't think I got 5MP mode to work. Even 2.5MP mode is tricky (needs
> > a lot of continuous memory).
> 
> The OMAP 3 ISP has got an MMU, getting some contiguous memory is not really
> a problem when you have a 4 GiB empty space to use.

Ok, maybe it is something else. 2.5MP mode seems to work better when
there is free memory.

> > Anyway, I have to start somewhere, and I believe this is a good
> > starting place; I'd like to get the code cleaned up and merged, then
> > move to the next parts.
> 
> I wonder if something else could be the problem. I think the data rate is
> higher in the 5 MP mode, and that might be the reason. I don't remember how
> similar is the clock tree in the 3430 to the 3630. Could it be that the ISP
> clock is lower than it should be for some reason, for instance?

No idea, really. I'd like to get the support merged, and then debug
the code when we have common code base in the mainline.

Best regards,
								Pavel
Sakari Ailus Nov. 1, 2016, 8:08 p.m. UTC | #9
Hi Pavel,

On Tue, Nov 01, 2016 at 04:39:21PM +0100, Pavel Machek wrote:
> Hi!
> 
> > > I'll have to go through the patches, et8ek8 driver is probably not
> > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > camera switching, then some omap3isp patches to bind flash and
> > > autofocus into the subdevice.
> > > 
> > > Then, device tree support on n900 can be added.
> > 
> > I briefly discussed with with Sebastian.
> > 
> > Do you think the elusive support for the secondary camera is worth keeping
> > out the main camera from the DT in mainline? As long as there's a reasonable
> > way to get it working, I'd just merge that. If someone ever gets the
> > secondary camera working properly and nicely with the video bus switch,
> > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > think it's very useful even if we get there: the quality is really
> > bad.
> 
> Well, I am a little bit worried that /dev/video* entries will
> renumber themself when the the front camera support is merged,
> breaking userspace.
> 
> But the first step is still the same: get et8ek8 support merged :-).

Do you happen to have a patch for the DT part as well? People could more
easily test this...

> > > > Do all the modes work for you currently btw.?
> > > 
> > > I don't think I got 5MP mode to work. Even 2.5MP mode is tricky (needs
> > > a lot of continuous memory).
> > 
> > The OMAP 3 ISP has got an MMU, getting some contiguous memory is not really
> > a problem when you have a 4 GiB empty space to use.
> 
> Ok, maybe it is something else. 2.5MP mode seems to work better when
> there is free memory.

That's very odd. Do you use MMAP or USERPTR buffers btw.? I remember the
cache was different on 3430, that could be an issue as well (VIVT AFAIR, so
flushing requires making sure there are no other mappings or flushing the
entire cache).

> > > Anyway, I have to start somewhere, and I believe this is a good
> > > starting place; I'd like to get the code cleaned up and merged, then
> > > move to the next parts.
> > 
> > I wonder if something else could be the problem. I think the data rate is
> > higher in the 5 MP mode, and that might be the reason. I don't remember how
> > similar is the clock tree in the 3430 to the 3630. Could it be that the ISP
> > clock is lower than it should be for some reason, for instance?
> 
> No idea, really. I'd like to get the support merged, and then debug
> the code when we have common code base in the mainline.

Yes. It's much easier then. Which is why it'd be very nice to have the DT
content, too.
Sakari Ailus Nov. 1, 2016, 8:11 p.m. UTC | #10
Hi Ivaylo,

On Tue, Nov 01, 2016 at 08:36:57AM +0200, Ivaylo Dimitrov wrote:
> Hi,
> 
> On  1.11.2016 00:54, Sakari Ailus wrote:
> >Hi Pavel,
> >
> >On Sun, Oct 23, 2016 at 10:33:15PM +0200, Pavel Machek wrote:
> >>Hi!
> >>
> >>>Thanks, this answered half of my questions already. ;-)
> >>
> >>:-).
> >>
> >>I'll have to go through the patches, et8ek8 driver is probably not
> >>enough to get useful video. platform/video-bus-switch.c is needed for
> >>camera switching, then some omap3isp patches to bind flash and
> >>autofocus into the subdevice.
> >>
> >>Then, device tree support on n900 can be added.
> >
> >I briefly discussed with with Sebastian.
> >
> >Do you think the elusive support for the secondary camera is worth keeping
> >out the main camera from the DT in mainline? As long as there's a reasonable
> >way to get it working, I'd just merge that. If someone ever gets the
> >secondary camera working properly and nicely with the video bus switch,
> >that's cool, we'll somehow deal with the problem then. But frankly I don't
> >think it's very useful even if we get there: the quality is really bad.
> >
> 
> Yes, lets merge what we have till now, it will be way easier to improve on
> it once it is part of the mainline.
> 
> BTW, I have (had) patched VBS working almost without problems, when it comes
> to it I'll dig it.

I wonder if I'm the only one who wonders what VBS is here. Don't tell me its
the old MS thing. :-) ;-)
Ivaylo Dimitrov Nov. 1, 2016, 10:14 p.m. UTC | #11
On  1.11.2016 22:11, Sakari Ailus wrote:
> Hi Ivaylo,
>
> On Tue, Nov 01, 2016 at 08:36:57AM +0200, Ivaylo Dimitrov wrote:
>> Hi,
>>
>> On  1.11.2016 00:54, Sakari Ailus wrote:
>>> Hi Pavel,
>>>
>>> On Sun, Oct 23, 2016 at 10:33:15PM +0200, Pavel Machek wrote:
>>>> Hi!
>>>>
>>>>> Thanks, this answered half of my questions already. ;-)
>>>>
>>>> :-).
>>>>
>>>> I'll have to go through the patches, et8ek8 driver is probably not
>>>> enough to get useful video. platform/video-bus-switch.c is needed for
>>>> camera switching, then some omap3isp patches to bind flash and
>>>> autofocus into the subdevice.
>>>>
>>>> Then, device tree support on n900 can be added.
>>>
>>> I briefly discussed with with Sebastian.
>>>
>>> Do you think the elusive support for the secondary camera is worth keeping
>>> out the main camera from the DT in mainline? As long as there's a reasonable
>>> way to get it working, I'd just merge that. If someone ever gets the
>>> secondary camera working properly and nicely with the video bus switch,
>>> that's cool, we'll somehow deal with the problem then. But frankly I don't
>>> think it's very useful even if we get there: the quality is really bad.
>>>
>>
>> Yes, lets merge what we have till now, it will be way easier to improve on
>> it once it is part of the mainline.
>>
>> BTW, I have (had) patched VBS working almost without problems, when it comes
>> to it I'll dig it.
>
> I wonder if I'm the only one who wonders what VBS is here. Don't tell me its
> the old MS thing. :-) ;-)
>

Oh, sorry, I thought that V(ideo) B(us) S(witch) is clear in the context :)
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Laurent Pinchart Nov. 2, 2016, 12:45 a.m. UTC | #12
Hello,

On Tuesday 01 Nov 2016 00:58:45 Sakari Ailus wrote:
> On Sun, Oct 23, 2016 at 10:40:01PM +0200, Pavel Machek wrote:
> > Hi!
> > 
> > > Thanks, this answered half of my questions already. ;-)
> > > 
> > > Do all the modes work for you currently btw.?
> > 
> > Aha, went through my notes. This is what it does in 5MP mode, even on
> > v4.9:
> > 
> > pavel@n900:/my/fcam-dev$ ./camera.py
> > ['-r']
> > ['-l', '"et8ek8 3-003e":0 -> "video-bus-switch":1 [1]']
> > ['-l', '"video-bus-switch":0 -> "OMAP3 ISP CCP2":0 [1]']
> > ['-l', '"OMAP3 ISP CCP2":1 -> "OMAP3 ISP CCDC":0 [1]']
> > ['-l', '"OMAP3 ISP CCDC":1 -> "OMAP3 ISP CCDC output":0 [1]']
> > ['-V', '"et8ek8 3-003e":0 [SGRBG10 2592x1968]']
> > ['-V', '"OMAP3 ISP CCP2":0 [SGRBG10 2592x1968]']
> > ['-V', '"OMAP3 ISP CCP2":1 [SGRBG10 2592x1968]']
> > ['-V', '"OMAP3 ISP CCDC":1 [SGRBG10 2592x1968]']
> > ['-V', '"OMAP3 ISP CCDC":2 [SGRBG10 2592x1968]']
> > Device /dev/video2 opened.
> > Device `OMAP3 ISP CCDC output' on `media' is a video capture (without
> > mplanes) device.
> > Video format set: SGRBG10 (30314142) 2592x1968 (stride 5184) field
> > none buffer size 10202112
> > Video format: SGRBG10 (30314142) 2592x1968 (stride 5184) field none
> > buffer size 10202112
> > 4 buffers requested.
> > length: 10202112 offset: 0 timestamp type/source: mono/EoF
> > Buffer 0/0 mapped at address 0xb63a0000.
> > length: 10202112 offset: 10203136 timestamp type/source: mono/EoF
> > Buffer 1/0 mapped at address 0xb59e5000.
> > length: 10202112 offset: 20406272 timestamp type/source: mono/EoF
> > Buffer 2/0 mapped at address 0xb502a000.
> > length: 10202112 offset: 30609408 timestamp type/source: mono/EoF
> > Buffer 3/0 mapped at address 0xb466f000.
> > 0 (0) [E] any 0 10202112 B 0.000000 2792.366987 0.001 fps ts mono/EoF
> > Unable to queue buffer: Input/output error (5).
> > Unable to requeue buffer: Input/output error (5).
> > Unable to release buffers: Device or resource busy (16).
> > pavel@n900:/my/fcam-dev$
> > 
> > (gitlab.com fcam-dev branch good)
> > 
> > Kernel will say
> > 
> > [ 2689.598358] stream on success
> > [ 2702.426635] Streamon
> > [ 2702.426727] check_format checking px 808534338 808534338, h 984
> > 984, w 1296 1296, bpline 2592 2592, size 2550528 2550528 field 1 1
> > [ 2702.426818] configuring for 1296(2592)x984
> > [ 2702.434722] stream on success
> > [ 2792.276184] Streamon
> > [ 2792.276306] check_format checking px 808534338 808534338, h 1968
> > 1968, w 2592 2592, bpline 5184 5184, size 10202112 10202112 field 1 1
> > [ 2792.276367] configuring for 2592(5184)x1968
> > [ 2792.284240] stream on success
> > [ 2792.368164] omap3isp 480bc000.isp: CCDC won't become idle!
> 
> This is Bad(tm).
> 
> It means that the driver waited for the CCDC to become idle to reprogram it,
> but it didn't happen. This could be a problem in the number of lines
> configured, or some polarity settings between the CCP2 receiver and the
> CCDC.

Is that polarity even configurable ?

> I suspect the latter, but I could be totally wrong here as well since
> it was more than five years I worked on these things. :-I

The OMAP3 ISP CCDC is very fragile when the input signals don't match exactly 
what it expects. This is partly caused by the driver implementation, I believe 
we could do better, but it's been a long time since I looked at that code.

One possible debugging option is to check how much data the CCDC has written 
to memory (if any). If I remember correctly the SBL_CCDC_WR_* registers can 
help there, the target address they store should be incremented as data is 
written to memory.

> > [ 2793.901550] omap3isp 480bc000.isp: Unable to stop OMAP3 ISP CCDC
> 
> And this is probably directly caused by the same problem. :-(
Pavel Machek Nov. 2, 2016, 8:15 a.m. UTC | #13
Hi!

> >>I'll have to go through the patches, et8ek8 driver is probably not
> >>enough to get useful video. platform/video-bus-switch.c is needed for
> >>camera switching, then some omap3isp patches to bind flash and
> >>autofocus into the subdevice.
> >>
> >>Then, device tree support on n900 can be added.
> >
> >I briefly discussed with with Sebastian.
> >
> >Do you think the elusive support for the secondary camera is worth keeping
> >out the main camera from the DT in mainline? As long as there's a reasonable
> >way to get it working, I'd just merge that. If someone ever gets the
> >secondary camera working properly and nicely with the video bus switch,
> >that's cool, we'll somehow deal with the problem then. But frankly I don't
> >think it's very useful even if we get there: the quality is really bad.
> >
> 
> Yes, lets merge what we have till now, it will be way easier to improve on
> it once it is part of the mainline.
> 
> BTW, I have (had) patched VBS working almost without problems, when it comes
> to it I'll dig it.

Do you have a version that switches on runtime?

Best regards,
									Pavel
Ivaylo Dimitrov Nov. 2, 2016, 8:16 a.m. UTC | #14
On  2.11.2016 10:15, Pavel Machek wrote:
> Hi!
>
>>>> I'll have to go through the patches, et8ek8 driver is probably not
>>>> enough to get useful video. platform/video-bus-switch.c is needed for
>>>> camera switching, then some omap3isp patches to bind flash and
>>>> autofocus into the subdevice.
>>>>
>>>> Then, device tree support on n900 can be added.
>>>
>>> I briefly discussed with with Sebastian.
>>>
>>> Do you think the elusive support for the secondary camera is worth keeping
>>> out the main camera from the DT in mainline? As long as there's a reasonable
>>> way to get it working, I'd just merge that. If someone ever gets the
>>> secondary camera working properly and nicely with the video bus switch,
>>> that's cool, we'll somehow deal with the problem then. But frankly I don't
>>> think it's very useful even if we get there: the quality is really bad.
>>>
>>
>> Yes, lets merge what we have till now, it will be way easier to improve on
>> it once it is part of the mainline.
>>
>> BTW, I have (had) patched VBS working almost without problems, when it comes
>> to it I'll dig it.
>
> Do you have a version that switches on runtime?
>
> Best regards,
> 									Pavel
>

IIRC yes, but I might be wrong, it was a while I was playing with it.

Ivo
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pavel Machek Nov. 3, 2016, 8:14 a.m. UTC | #15
Hi!

> > > > I'll have to go through the patches, et8ek8 driver is probably not
> > > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > > camera switching, then some omap3isp patches to bind flash and
> > > > autofocus into the subdevice.
> > > > 
> > > > Then, device tree support on n900 can be added.
> > > 
> > > I briefly discussed with with Sebastian.
> > > 
> > > Do you think the elusive support for the secondary camera is worth keeping
> > > out the main camera from the DT in mainline? As long as there's a reasonable
> > > way to get it working, I'd just merge that. If someone ever gets the
> > > secondary camera working properly and nicely with the video bus switch,
> > > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > > think it's very useful even if we get there: the quality is really
> > > bad.
> > 
> > Well, I am a little bit worried that /dev/video* entries will
> > renumber themself when the the front camera support is merged,
> > breaking userspace.
> > 
> > But the first step is still the same: get et8ek8 support merged :-).
> 
> Do you happen to have a patch for the DT part as well? People could more
> easily test this...

If you want complete/working tree for testing, it is at

https://git.kernel.org/cgit/linux/kernel/git/pavel/linux-n900.git/?h=camera-v4.9

If you want userspace to go with that, there's fcam-dev. It is on
gitlab:

https://gitlab.com/pavelm/fcam-dev


> > > > > Do all the modes work for you currently btw.?
> > > > 
> > > > I don't think I got 5MP mode to work. Even 2.5MP mode is tricky (needs
> > > > a lot of continuous memory).
> > > 
> > > The OMAP 3 ISP has got an MMU, getting some contiguous memory is not really
> > > a problem when you have a 4 GiB empty space to use.
> > 
> > Ok, maybe it is something else. 2.5MP mode seems to work better when
> > there is free memory.
> 
> That's very odd. Do you use MMAP or USERPTR buffers btw.? I remember the
> cache was different on 3430, that could be an issue as well (VIVT AFAIR, so
> flushing requires making sure there are no other mappings or flushing the
> entire cache).

The userland code I'm using does

 struct v4l2_requestbuffers req;
 memset(&req, 0, sizeof(req));
 req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 req.memory = V4L2_MEMORY_MMAP;
 req.count  = 8;
 printf("Reqbufs\n");
 if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
 ...
		

so I guess answer to your question is "MMAP". The v4l interface is at

https://gitlab.com/pavelm/fcam-dev/blob/master/src/N900/V4L2Sensor.cpp

.
Best regards,
									Pavel
Sebastian Reichel Nov. 3, 2016, 10:48 p.m. UTC | #16
Hi,

On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
> > > Thanks, this answered half of my questions already. ;-)
> > :-).
> > 
> > I'll have to go through the patches, et8ek8 driver is probably not
> > enough to get useful video. platform/video-bus-switch.c is needed for
> > camera switching, then some omap3isp patches to bind flash and
> > autofocus into the subdevice.
> > 
> > Then, device tree support on n900 can be added.
> 
> I briefly discussed with with Sebastian.
> 
> Do you think the elusive support for the secondary camera is worth keeping
> out the main camera from the DT in mainline? As long as there's a reasonable
> way to get it working, I'd just merge that. If someone ever gets the
> secondary camera working properly and nicely with the video bus switch,
> that's cool, we'll somehow deal with the problem then. But frankly I don't
> think it's very useful even if we get there: the quality is really bad.

If we want to keep open the option to add proper support for the
second camera, we could also add the bus switch and not add the
front camera node in DT. Then adding the front camera does not
require DT or userspace API changes. It would need an additional
DT quirk in arch/arm/mach-omap2/board-generic.c for RX51, which
adds the CCP2 bus settings from the camera node to the bus
switch node to keep isp_of_parse_node happy. That should be
easy to implement and not add much delay in upstreaming.

For actually getting both cameras available with runtime-switching
the proper solution would probably involve moving the parsing of
the bus-settings to the sensor driver and providing a callback.
This callback can be called by omap3isp when it wants to configure
the phy (which is basically when it starts streaming). That seems
to be the only place needing the buscfg anyways.

Then the video-bus-switch could do something like this (pseudocode):

static void get_buscfg(struct *this, struct *buscfg) {
    if (selected_cam == 0)
        return this->sensor_a->get_buscfg(buscfg);
    else
        return this->sensor_b->get_buscfg(buscfg);
}

Regarding the usefulness: I noticed, that the Neo900 people also
plan to have the bus-switch [0]. It's still the same crappy front-cam,
though. Nevertheless it might be useful for testing. It has nice
test-image capabilities, which might be useful for regression
testing once everything is in place.

[0] http://neo900.org/stuff/block-diagrams/neo900/neo900.html

-- Sebastian
Sakari Ailus Nov. 3, 2016, 11:05 p.m. UTC | #17
Hi Sebastian,

On Thu, Nov 03, 2016 at 11:48:43PM +0100, Sebastian Reichel wrote:
> Hi,
> 
> On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
> > > > Thanks, this answered half of my questions already. ;-)
> > > :-).
> > > 
> > > I'll have to go through the patches, et8ek8 driver is probably not
> > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > camera switching, then some omap3isp patches to bind flash and
> > > autofocus into the subdevice.
> > > 
> > > Then, device tree support on n900 can be added.
> > 
> > I briefly discussed with with Sebastian.
> > 
> > Do you think the elusive support for the secondary camera is worth keeping
> > out the main camera from the DT in mainline? As long as there's a reasonable
> > way to get it working, I'd just merge that. If someone ever gets the
> > secondary camera working properly and nicely with the video bus switch,
> > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > think it's very useful even if we get there: the quality is really bad.
> 
> If we want to keep open the option to add proper support for the
> second camera, we could also add the bus switch and not add the
> front camera node in DT. Then adding the front camera does not
> require DT or userspace API changes. It would need an additional
> DT quirk in arch/arm/mach-omap2/board-generic.c for RX51, which
> adds the CCP2 bus settings from the camera node to the bus
> switch node to keep isp_of_parse_node happy. That should be
> easy to implement and not add much delay in upstreaming.

By adding the video bus switch we have a little bit more complex system as a
whole. The V4L2 async does not currently support this. There's more here:

<URL:http://www.spinics.net/lists/linux-media/msg107262.html>

What I thought was that once we have everything that's required in place, we
can just change what's in DT. But the software needs to continue to work
with the old DT content.

> For actually getting both cameras available with runtime-switching
> the proper solution would probably involve moving the parsing of
> the bus-settings to the sensor driver and providing a callback.
> This callback can be called by omap3isp when it wants to configure
> the phy (which is basically when it starts streaming). That seems
> to be the only place needing the buscfg anyways.
> 
> Then the video-bus-switch could do something like this (pseudocode):
> 
> static void get_buscfg(struct *this, struct *buscfg) {
>     if (selected_cam == 0)
>         return this->sensor_a->get_buscfg(buscfg);
>     else
>         return this->sensor_b->get_buscfg(buscfg);
> }
> 
> Regarding the usefulness: I noticed, that the Neo900 people also
> plan to have the bus-switch [0]. It's still the same crappy front-cam,
> though. Nevertheless it might be useful for testing. It has nice
> test-image capabilities, which might be useful for regression
> testing once everything is in place.
> 
> [0] http://neo900.org/stuff/block-diagrams/neo900/neo900.html

Seriously? I suppose there should be no need for that anymore, is there?

I think they wanted to save one GPIO in order to shave off 0,0001 cents from
the manufacturing costs or something like that. And the result is...
painful. :-I
Ivaylo Dimitrov Nov. 3, 2016, 11:40 p.m. UTC | #18
Hi,

On  4.11.2016 01:05, Sakari Ailus wrote:
> Hi Sebastian,
>
> On Thu, Nov 03, 2016 at 11:48:43PM +0100, Sebastian Reichel wrote:
>> Hi,
>>
>> On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
>>>>> Thanks, this answered half of my questions already. ;-)
>>>> :-).
>>>>
>>>> I'll have to go through the patches, et8ek8 driver is probably not
>>>> enough to get useful video. platform/video-bus-switch.c is needed for
>>>> camera switching, then some omap3isp patches to bind flash and
>>>> autofocus into the subdevice.
>>>>
>>>> Then, device tree support on n900 can be added.
>>>
>>> I briefly discussed with with Sebastian.
>>>
>>> Do you think the elusive support for the secondary camera is worth keeping
>>> out the main camera from the DT in mainline? As long as there's a reasonable
>>> way to get it working, I'd just merge that. If someone ever gets the
>>> secondary camera working properly and nicely with the video bus switch,
>>> that's cool, we'll somehow deal with the problem then. But frankly I don't
>>> think it's very useful even if we get there: the quality is really bad.
>>
>> If we want to keep open the option to add proper support for the
>> second camera, we could also add the bus switch and not add the
>> front camera node in DT. Then adding the front camera does not
>> require DT or userspace API changes. It would need an additional
>> DT quirk in arch/arm/mach-omap2/board-generic.c for RX51, which
>> adds the CCP2 bus settings from the camera node to the bus
>> switch node to keep isp_of_parse_node happy. That should be
>> easy to implement and not add much delay in upstreaming.
>
> By adding the video bus switch we have a little bit more complex system as a
> whole. The V4L2 async does not currently support this. There's more here:
>
> <URL:http://www.spinics.net/lists/linux-media/msg107262.html>
>
> What I thought was that once we have everything that's required in place, we
> can just change what's in DT. But the software needs to continue to work
> with the old DT content.
>
>> For actually getting both cameras available with runtime-switching
>> the proper solution would probably involve moving the parsing of
>> the bus-settings to the sensor driver and providing a callback.
>> This callback can be called by omap3isp when it wants to configure
>> the phy (which is basically when it starts streaming). That seems
>> to be the only place needing the buscfg anyways.
>>
>> Then the video-bus-switch could do something like this (pseudocode):
>>
>> static void get_buscfg(struct *this, struct *buscfg) {
>>     if (selected_cam == 0)
>>         return this->sensor_a->get_buscfg(buscfg);
>>     else
>>         return this->sensor_b->get_buscfg(buscfg);
>> }
>>
>> Regarding the usefulness: I noticed, that the Neo900 people also
>> plan to have the bus-switch [0]. It's still the same crappy front-cam,
>> though. Nevertheless it might be useful for testing. It has nice
>> test-image capabilities, which might be useful for regression
>> testing once everything is in place.
>>
>> [0] http://neo900.org/stuff/block-diagrams/neo900/neo900.html
>
> Seriously? I suppose there should be no need for that anymore, is there?
>
> I think they wanted to save one GPIO in order to shave off 0,0001 cents from
> the manufacturing costs or something like that. And the result is...
> painful. :-I
>

No, the reason is that hey want to keep Neo900 as close as possible to 
N900, HW wise
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sebastian Reichel Nov. 4, 2016, 12:05 a.m. UTC | #19
Hi,

On Fri, Nov 04, 2016 at 01:05:01AM +0200, Sakari Ailus wrote:
> On Thu, Nov 03, 2016 at 11:48:43PM +0100, Sebastian Reichel wrote:
> > On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
> > > > > Thanks, this answered half of my questions already. ;-)
> > > > :-).
> > > > 
> > > > I'll have to go through the patches, et8ek8 driver is probably not
> > > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > > camera switching, then some omap3isp patches to bind flash and
> > > > autofocus into the subdevice.
> > > > 
> > > > Then, device tree support on n900 can be added.
> > > 
> > > I briefly discussed with with Sebastian.
> > > 
> > > Do you think the elusive support for the secondary camera is worth keeping
> > > out the main camera from the DT in mainline? As long as there's a reasonable
> > > way to get it working, I'd just merge that. If someone ever gets the
> > > secondary camera working properly and nicely with the video bus switch,
> > > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > > think it's very useful even if we get there: the quality is really bad.
> > 
> > If we want to keep open the option to add proper support for the
> > second camera, we could also add the bus switch and not add the
> > front camera node in DT. Then adding the front camera does not
> > require DT or userspace API changes. It would need an additional
> > DT quirk in arch/arm/mach-omap2/board-generic.c for RX51, which
> > adds the CCP2 bus settings from the camera node to the bus
> > switch node to keep isp_of_parse_node happy. That should be
> > easy to implement and not add much delay in upstreaming.
> 
> By adding the video bus switch we have a little bit more complex system as a
> whole. The V4L2 async does not currently support this. There's more here:
> 
> <URL:http://www.spinics.net/lists/linux-media/msg107262.html>

I'm not sure what part relevant for video-bus-switch is currently
not supported?

video-bus-switch registers its own async notifier and only registers
itself as subdevices to omap3isp, once its own subdevices have been
registered successfully.

> What I thought was that once we have everything that's required in
> place, we can just change what's in DT. But the software needs to
> continue to work with the old DT content.

Right, so DT is not a problem. But adding the switch would change
the media-graph, which is exposed to userspace.

> > For actually getting both cameras available with runtime-switching
> > the proper solution would probably involve moving the parsing of
> > the bus-settings to the sensor driver and providing a callback.
> > This callback can be called by omap3isp when it wants to configure
> > the phy (which is basically when it starts streaming). That seems
> > to be the only place needing the buscfg anyways.
> > 
> > Then the video-bus-switch could do something like this (pseudocode):
> > 
> > static void get_buscfg(struct *this, struct *buscfg) {
> >     if (selected_cam == 0)
> >         return this->sensor_a->get_buscfg(buscfg);
> >     else
> >         return this->sensor_b->get_buscfg(buscfg);
> > }
> > 
> > Regarding the usefulness: I noticed, that the Neo900 people also
> > plan to have the bus-switch [0]. It's still the same crappy front-cam,
> > though. Nevertheless it might be useful for testing. It has nice
> > test-image capabilities, which might be useful for regression
> > testing once everything is in place.
> > 
> > [0] http://neo900.org/stuff/block-diagrams/neo900/neo900.html
> 
> Seriously? I suppose there should be no need for that anymore, is there?
> 
> I think they wanted to save one GPIO in order to shave off 0,0001 cents from
> the manufacturing costs or something like that. And the result is...
> painful. :-I

CSI1/CCP2 is more than a single I/O pin, isn't it? Or do you
reference to the GPIO dual use to enable frontcam and switch
between the cameras? That is indeed a really ugly solution :(

-- Sebastian
Sakari Ailus Nov. 14, 2016, 9:58 p.m. UTC | #20
Hi Sebastian,

On Fri, Nov 04, 2016 at 01:05:25AM +0100, Sebastian Reichel wrote:
> Hi,
> 
> On Fri, Nov 04, 2016 at 01:05:01AM +0200, Sakari Ailus wrote:
> > On Thu, Nov 03, 2016 at 11:48:43PM +0100, Sebastian Reichel wrote:
> > > On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
> > > > > > Thanks, this answered half of my questions already. ;-)
> > > > > :-).
> > > > > 
> > > > > I'll have to go through the patches, et8ek8 driver is probably not
> > > > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > > > camera switching, then some omap3isp patches to bind flash and
> > > > > autofocus into the subdevice.
> > > > > 
> > > > > Then, device tree support on n900 can be added.
> > > > 
> > > > I briefly discussed with with Sebastian.
> > > > 
> > > > Do you think the elusive support for the secondary camera is worth keeping
> > > > out the main camera from the DT in mainline? As long as there's a reasonable
> > > > way to get it working, I'd just merge that. If someone ever gets the
> > > > secondary camera working properly and nicely with the video bus switch,
> > > > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > > > think it's very useful even if we get there: the quality is really bad.
> > > 
> > > If we want to keep open the option to add proper support for the
> > > second camera, we could also add the bus switch and not add the
> > > front camera node in DT. Then adding the front camera does not
> > > require DT or userspace API changes. It would need an additional
> > > DT quirk in arch/arm/mach-omap2/board-generic.c for RX51, which
> > > adds the CCP2 bus settings from the camera node to the bus
> > > switch node to keep isp_of_parse_node happy. That should be
> > > easy to implement and not add much delay in upstreaming.
> > 
> > By adding the video bus switch we have a little bit more complex system as a
> > whole. The V4L2 async does not currently support this. There's more here:
> > 
> > <URL:http://www.spinics.net/lists/linux-media/msg107262.html>
> 
> I'm not sure what part relevant for video-bus-switch is currently
> not supported?
> 
> video-bus-switch registers its own async notifier and only registers
> itself as subdevices to omap3isp, once its own subdevices have been
> registered successfully.

Do you happen to have patches for this?

I still think we should clean up the V4L2 async framework though.

> 
> > What I thought was that once we have everything that's required in
> > place, we can just change what's in DT. But the software needs to
> > continue to work with the old DT content.
> 
> Right, so DT is not a problem. But adding the switch would change
> the media-graph, which is exposed to userspace.

Well, yes, indeed. We'll have those cases coming anyway, as support for
multiple streams over a single link is added. In some cases more sub-devices
will be needed to expose all the necessary configurability to the user.

> 
> > > For actually getting both cameras available with runtime-switching
> > > the proper solution would probably involve moving the parsing of
> > > the bus-settings to the sensor driver and providing a callback.
> > > This callback can be called by omap3isp when it wants to configure
> > > the phy (which is basically when it starts streaming). That seems
> > > to be the only place needing the buscfg anyways.
> > > 
> > > Then the video-bus-switch could do something like this (pseudocode):
> > > 
> > > static void get_buscfg(struct *this, struct *buscfg) {
> > >     if (selected_cam == 0)
> > >         return this->sensor_a->get_buscfg(buscfg);
> > >     else
> > >         return this->sensor_b->get_buscfg(buscfg);
> > > }
> > > 
> > > Regarding the usefulness: I noticed, that the Neo900 people also
> > > plan to have the bus-switch [0]. It's still the same crappy front-cam,
> > > though. Nevertheless it might be useful for testing. It has nice
> > > test-image capabilities, which might be useful for regression
> > > testing once everything is in place.
> > > 
> > > [0] http://neo900.org/stuff/block-diagrams/neo900/neo900.html
> > 
> > Seriously? I suppose there should be no need for that anymore, is there?
> > 
> > I think they wanted to save one GPIO in order to shave off 0,0001 cents from
> > the manufacturing costs or something like that. And the result is...
> > painful. :-I
> 
> CSI1/CCP2 is more than a single I/O pin, isn't it? Or do you
> reference to the GPIO dual use to enable frontcam and switch
> between the cameras? That is indeed a really ugly solution :(

The GPIO, yes. It was a really bad idea...
Sebastian Reichel Nov. 15, 2016, 12:53 a.m. UTC | #21
Hi Sakari,

On Mon, Nov 14, 2016 at 11:58:28PM +0200, Sakari Ailus wrote:
> [...]
>
> On Fri, Nov 04, 2016 at 01:05:25AM +0100, Sebastian Reichel wrote:
> > I'm not sure what part relevant for video-bus-switch is currently
> > not supported?
> > 
> > video-bus-switch registers its own async notifier and only registers
> > itself as subdevices to omap3isp, once its own subdevices have been
> > registered successfully.
> 
> Do you happen to have patches for this?
> I still think we should clean up the V4L2 async framework though.

http://git.kernel.org/cgit/linux/kernel/git/sre/linux-n900.git/tree/drivers/media/platform/video-bus-switch.c?h=n900-camera-ivo

It was inside of the RFC series Ivo sent in April.

> [...]

-- Sebastian
Pavel Machek Nov. 15, 2016, 10:54 a.m. UTC | #22
Hi!

> On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
> > > > Thanks, this answered half of my questions already. ;-)
> > > :-).
> > > 
> > > I'll have to go through the patches, et8ek8 driver is probably not
> > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > camera switching, then some omap3isp patches to bind flash and
> > > autofocus into the subdevice.
> > > 
> > > Then, device tree support on n900 can be added.
> > 
> > I briefly discussed with with Sebastian.
> > 
> > Do you think the elusive support for the secondary camera is worth keeping
> > out the main camera from the DT in mainline? As long as there's a reasonable
> > way to get it working, I'd just merge that. If someone ever gets the
> > secondary camera working properly and nicely with the video bus switch,
> > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > think it's very useful even if we get there: the quality is really bad.
> 
> If we want to keep open the option to add proper support for the
> second camera, we could also add the bus switch and not add the
> front camera node in DT. Then adding the front camera does not

Now that we have ack on the device tree parts, could you merge the
et8ek8 driver (or provide review comments?)?

Yes, there are more parts missing for useful camera support on n900,
but the chip driver is neccessary part and it should be ready.

Thanks,
								Pavel
Sakari Ailus Nov. 15, 2016, 10:55 p.m. UTC | #23
Hi Pavel,

On Tue, Nov 15, 2016 at 11:54:25AM +0100, Pavel Machek wrote:
> Hi!
> 
> > On Tue, Nov 01, 2016 at 12:54:08AM +0200, Sakari Ailus wrote:
> > > > > Thanks, this answered half of my questions already. ;-)
> > > > :-).
> > > > 
> > > > I'll have to go through the patches, et8ek8 driver is probably not
> > > > enough to get useful video. platform/video-bus-switch.c is needed for
> > > > camera switching, then some omap3isp patches to bind flash and
> > > > autofocus into the subdevice.
> > > > 
> > > > Then, device tree support on n900 can be added.
> > > 
> > > I briefly discussed with with Sebastian.
> > > 
> > > Do you think the elusive support for the secondary camera is worth keeping
> > > out the main camera from the DT in mainline? As long as there's a reasonable
> > > way to get it working, I'd just merge that. If someone ever gets the
> > > secondary camera working properly and nicely with the video bus switch,
> > > that's cool, we'll somehow deal with the problem then. But frankly I don't
> > > think it's very useful even if we get there: the quality is really bad.
> > 
> > If we want to keep open the option to add proper support for the
> > second camera, we could also add the bus switch and not add the
> > front camera node in DT. Then adding the front camera does not
> 
> Now that we have ack on the device tree parts, could you merge the
> et8ek8 driver (or provide review comments?)?
> 
> Yes, there are more parts missing for useful camera support on n900,
> but the chip driver is neccessary part and it should be ready.

Sure, I was somehow expecting we could perhaps merge the rest of the
necessary patches sooner than it now appears.

Let me check the latest et8ek8 patch.
Sakari Ailus Nov. 19, 2016, 11:29 p.m. UTC | #24
Hi Pavel,

Just a few more comments...

Please check my other review as well. I believe you may have missed the
comments in between in that one.

On Sun, Oct 23, 2016 at 10:03:55PM +0200, Pavel Machek wrote:
> 
> Add driver for et8ek8 sensor, found in Nokia N900 main camera. Can be
> used for taking photos in 2.5MP resolution with fcam-dev.
> 
> Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> ---
> From v4 I did cleanups to coding style and removed various oddities.
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 2669b4b..6d01e15 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -667,6 +667,7 @@ config VIDEO_S5K5BAF
>  	  camera sensor with an embedded SoC image signal processor.
>  
>  source "drivers/media/i2c/smiapp/Kconfig"
> +source "drivers/media/i2c/et8ek8/Kconfig"
>  
>  config VIDEO_S5C73M3
>  	tristate "Samsung S5C73M3 sensor support"
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 92773b2..5bc7bbe 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -2,6 +2,7 @@ msp3400-objs	:=	msp3400-driver.o msp3400-kthreads.o
>  obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
>  
>  obj-$(CONFIG_VIDEO_SMIAPP)	+= smiapp/
> +obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8/
>  obj-$(CONFIG_VIDEO_CX25840) += cx25840/
>  obj-$(CONFIG_VIDEO_M5MOLS)	+= m5mols/
>  obj-y				+= soc_camera/
> diff --git a/drivers/media/i2c/et8ek8/Kconfig b/drivers/media/i2c/et8ek8/Kconfig
> new file mode 100644
> index 0000000..1439936
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/Kconfig
> @@ -0,0 +1,6 @@
> +config VIDEO_ET8EK8
> +	tristate "ET8EK8 camera sensor support"
> +	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> +	---help---
> +	  This is a driver for the Toshiba ET8EK8 5 MP camera sensor.
> +	  It is used for example in Nokia N900 (RX-51).
> diff --git a/drivers/media/i2c/et8ek8/Makefile b/drivers/media/i2c/et8ek8/Makefile
> new file mode 100644
> index 0000000..66d1b7d
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/Makefile
> @@ -0,0 +1,2 @@
> +et8ek8-objs			+= et8ek8_mode.o et8ek8_driver.o
> +obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8.o
> diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> new file mode 100644
> index 0000000..0301e81
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> @@ -0,0 +1,1588 @@
> +/*
> + * et8ek8_driver.c
> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukkat76@gmail.com>
> + *
> + * Based on code from Toni Leinonen <toni.leinonen@offcode.fi>.
> + *
> + * This driver is based on the Micron MT9T012 camera imager driver
> + * (C) Texas Instruments.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/sort.h>
> +#include <linux/v4l2-mediabus.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "et8ek8_reg.h"
> +
> +#define ET8EK8_NAME		"et8ek8"
> +#define ET8EK8_PRIV_MEM_SIZE	128
> +#define ET8EK8_MAX_MSG		48
> +
> +struct et8ek8_sensor {
> +	struct v4l2_subdev subdev;
> +	struct media_pad pad;
> +	struct v4l2_mbus_framefmt format;
> +	struct gpio_desc *reset;
> +	struct regulator *vana;
> +	struct clk *ext_clk;
> +	u32 xclk_freq;
> +
> +	u16 version;
> +
> +	struct v4l2_ctrl_handler ctrl_handler;
> +	struct v4l2_ctrl *exposure;
> +	struct v4l2_ctrl *pixel_rate;
> +	struct et8ek8_reglist *current_reglist;
> +
> +	u8 priv_mem[ET8EK8_PRIV_MEM_SIZE];
> +
> +	struct mutex power_lock;
> +	int power_count;
> +};
> +
> +#define to_et8ek8_sensor(sd)	container_of(sd, struct et8ek8_sensor, subdev)
> +
> +enum et8ek8_versions {
> +	ET8EK8_REV_1 = 0x0001,
> +	ET8EK8_REV_2,
> +};
> +
> +/*
> + * This table describes what should be written to the sensor register
> + * for each gain value. The gain(index in the table) is in terms of
> + * 0.1EV, i.e. 10 indexes in the table give 2 time more gain [0] in
> + * the *analog gain, [1] in the digital gain
> + *
> + * Analog gain [dB] = 20*log10(regvalue/32); 0x20..0x100
> + */
> +static struct et8ek8_gain {
> +	u16 analog;
> +	u16 digital;
> +} const et8ek8_gain_table[] = {
> +	{ 32,    0},  /* x1 */
> +	{ 34,    0},
> +	{ 37,    0},
> +	{ 39,    0},
> +	{ 42,    0},
> +	{ 45,    0},
> +	{ 49,    0},
> +	{ 52,    0},
> +	{ 56,    0},
> +	{ 60,    0},
> +	{ 64,    0},  /* x2 */
> +	{ 69,    0},
> +	{ 74,    0},
> +	{ 79,    0},
> +	{ 84,    0},
> +	{ 91,    0},
> +	{ 97,    0},
> +	{104,    0},
> +	{111,    0},
> +	{119,    0},
> +	{128,    0},  /* x4 */
> +	{137,    0},
> +	{147,    0},
> +	{158,    0},
> +	{169,    0},
> +	{181,    0},
> +	{194,    0},
> +	{208,    0},
> +	{223,    0},
> +	{239,    0},
> +	{256,    0},  /* x8 */
> +	{256,   73},
> +	{256,  152},
> +	{256,  236},
> +	{256,  327},
> +	{256,  424},
> +	{256,  528},
> +	{256,  639},
> +	{256,  758},
> +	{256,  886},
> +	{256, 1023},  /* x16 */
> +};
> +
> +/* Register definitions */
> +#define REG_REVISION_NUMBER_L	0x1200
> +#define REG_REVISION_NUMBER_H	0x1201
> +
> +#define PRIV_MEM_START_REG	0x0008
> +#define PRIV_MEM_WIN_SIZE	8
> +
> +#define ET8EK8_I2C_DELAY	3	/* msec delay b/w accesses */
> +
> +#define USE_CRC			1
> +
> +/*
> + * Register access helpers
> + *
> + * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
> + * Returns zero if successful, or non-zero otherwise.
> + */
> +static int et8ek8_i2c_read_reg(struct i2c_client *client, u16 data_length,
> +			       u16 reg, u32 *val)
> +{
> +	int r;
> +	struct i2c_msg msg;
> +	unsigned char data[4];
> +
> +	if (!client->adapter)
> +		return -ENODEV;
> +	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
> +		return -EINVAL;
> +
> +	msg.addr = client->addr;
> +	msg.flags = 0;
> +	msg.len = 2;
> +	msg.buf = data;
> +
> +	/* high byte goes out first */
> +	data[0] = (u8) (reg >> 8);
> +	data[1] = (u8) (reg & 0xff);
> +	r = i2c_transfer(client->adapter, &msg, 1);
> +	if (r < 0)
> +		goto err;
> +
> +	msg.len = data_length;
> +	msg.flags = I2C_M_RD;
> +	r = i2c_transfer(client->adapter, &msg, 1);
> +	if (r < 0)
> +		goto err;
> +
> +	*val = 0;
> +	/* high byte comes first */
> +	if (data_length == ET8EK8_REG_8BIT)
> +		*val = data[0];
> +	else
> +		*val = (data[0] << 8) + data[1];
> +
> +	return 0;
> +
> +err:
> +	dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r);
> +
> +	return r;
> +}
> +
> +static void et8ek8_i2c_create_msg(struct i2c_client *client, u16 len, u16 reg,
> +				  u32 val, struct i2c_msg *msg,
> +				  unsigned char *buf)
> +{
> +	msg->addr = client->addr;
> +	msg->flags = 0; /* Write */
> +	msg->len = 2 + len;
> +	msg->buf = buf;
> +
> +	/* high byte goes out first */
> +	buf[0] = (u8) (reg >> 8);
> +	buf[1] = (u8) (reg & 0xff);
> +
> +	switch (len) {
> +	case ET8EK8_REG_8BIT:
> +		buf[2] = (u8) (val) & 0xff;
> +		break;
> +	case ET8EK8_REG_16BIT:
> +		buf[2] = (u8) (val >> 8) & 0xff;
> +		buf[3] = (u8) (val & 0xff);
> +		break;
> +	default:
> +		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
> +			  __func__);
> +	}
> +}
> +
> +/*
> + * A buffered write method that puts the wanted register write
> + * commands in a message list and passes the list to the i2c framework
> + */
> +static int et8ek8_i2c_buffered_write_regs(struct i2c_client *client,
> +					  const struct et8ek8_reg *wnext,
> +					  int cnt)
> +{
> +	struct i2c_msg msg[ET8EK8_MAX_MSG];
> +	unsigned char data[ET8EK8_MAX_MSG][6];
> +	int wcnt = 0;
> +	u16 reg, data_length;
> +	u32 val;
> +
> +	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
> +		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
> +		return -EINVAL;
> +	}
> +
> +	/* Create new write messages for all writes */
> +	while (wcnt < cnt) {
> +		data_length = wnext->type;
> +		reg = wnext->reg;
> +		val = wnext->val;
> +		wnext++;
> +
> +		et8ek8_i2c_create_msg(client, data_length, reg,
> +				    val, &msg[wcnt], &data[wcnt][0]);
> +
> +		/* Update write count */
> +		wcnt++;
> +	}
> +
> +	/* Now we send everything ... */
> +	return i2c_transfer(client->adapter, msg, wcnt);
> +}
> +
> +/*
> + * Write a list of registers to i2c device.
> + *
> + * The list of registers is terminated by ET8EK8_REG_TERM.
> + * Returns zero if successful, or non-zero otherwise.
> + */
> +static int et8ek8_i2c_write_regs(struct i2c_client *client,
> +				 const struct et8ek8_reg *regs)
> +{
> +	int r, cnt = 0;
> +	const struct et8ek8_reg *next;
> +
> +	if (!client->adapter)
> +		return -ENODEV;
> +
> +	if (!regs)
> +		return -EINVAL;
> +
> +	/* Initialize list pointers to the start of the list */
> +	next = regs;
> +
> +	do {
> +		/*
> +		 * We have to go through the list to figure out how
> +		 * many regular writes we have in a row
> +		 */
> +		while (next->type != ET8EK8_REG_TERM &&
> +		       next->type != ET8EK8_REG_DELAY) {
> +			/*
> +			 * Here we check that the actual length fields
> +			 * are valid
> +			 */
> +			if (WARN(next->type != ET8EK8_REG_8BIT &&
> +				 next->type != ET8EK8_REG_16BIT,
> +				 "Invalid type = %d", next->type)) {
> +				return -EINVAL;
> +			}
> +			/*
> +			 * Increment count of successive writes and
> +			 * read pointer
> +			 */
> +			cnt++;
> +			next++;
> +		}
> +
> +		/* Now we start writing ... */
> +		r = et8ek8_i2c_buffered_write_regs(client, regs, cnt);
> +
> +		/* ... and then check that everything was OK */
> +		if (r < 0) {
> +			dev_err(&client->dev, "i2c transfer error!\n");
> +			return r;
> +		}
> +
> +		/*
> +		 * If we ran into a sleep statement when going through
> +		 * the list, this is where we snooze for the required time
> +		 */
> +		if (next->type == ET8EK8_REG_DELAY) {
> +			msleep(next->val);
> +			/*
> +			 * ZZZ ...
> +			 * Update list pointers and cnt and start over ...
> +			 */
> +			next++;
> +			regs = next;
> +			cnt = 0;
> +		}
> +	} while (next->type != ET8EK8_REG_TERM);
> +
> +	return 0;
> +}
> +
> +/*
> + * Write to a 8/16-bit register.
> + * Returns zero if successful, or non-zero otherwise.
> + */
> +static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length,
> +				u16 reg, u32 val)
> +{
> +	int r;
> +	struct i2c_msg msg;
> +	unsigned char data[6];
> +
> +	if (!client->adapter)
> +		return -ENODEV;
> +	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
> +		return -EINVAL;
> +
> +	et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data);
> +
> +	r = i2c_transfer(client->adapter, &msg, 1);
> +	if (r < 0)
> +		dev_err(&client->dev,
> +			"wrote 0x%x to offset 0x%x error %d\n", val, reg, r);
> +	else
> +		r = 0; /* on success i2c_transfer() returns messages trasfered */
> +
> +	return r;
> +}
> +
> +static struct et8ek8_reglist *et8ek8_reglist_find_type(
> +		struct et8ek8_meta_reglist *meta,
> +		u16 type)
> +{
> +	struct et8ek8_reglist **next = &meta->reglist[0].ptr;
> +
> +	while (*next) {
> +		if ((*next)->type == type)
> +			return *next;
> +
> +		next++;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int et8ek8_i2c_reglist_find_write(struct i2c_client *client,
> +					 struct et8ek8_meta_reglist *meta,
> +					 u16 type)
> +{
> +	struct et8ek8_reglist *reglist;
> +
> +	reglist = et8ek8_reglist_find_type(meta, type);
> +	if (!reglist)
> +		return -EINVAL;
> +
> +	return et8ek8_i2c_write_regs(client, reglist->regs);
> +}
> +
> +static struct et8ek8_reglist **et8ek8_reglist_first(
> +		struct et8ek8_meta_reglist *meta)
> +{
> +	return &meta->reglist[0].ptr;
> +}
> +
> +static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist,
> +				   struct v4l2_mbus_framefmt *fmt)
> +{
> +	fmt->width = reglist->mode.window_width;
> +	fmt->height = reglist->mode.window_height;
> +
> +	if (reglist->mode.pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)

The driver doesn't really need to deal with pixel formats. Could you use
media bus formats instead, and rename the fields accordingly?

The reason why it did use pixel formats was that (V4L2) media bus formats
did not exist when the driver was written. :-)

> +		fmt->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
> +	else
> +		fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +}
> +
> +static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt(
> +		struct et8ek8_meta_reglist *meta,
> +		struct v4l2_mbus_framefmt *fmt)
> +{
> +	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
> +	struct et8ek8_reglist *best_match = NULL;
> +	struct et8ek8_reglist *best_other = NULL;
> +	struct v4l2_mbus_framefmt format;
> +	unsigned int max_dist_match = (unsigned int)-1;
> +	unsigned int max_dist_other = (unsigned int)-1;
> +
> +	/*
> +	 * Find the mode with the closest image size. The distance between
> +	 * image sizes is the size in pixels of the non-overlapping regions
> +	 * between the requested size and the frame-specified size.
> +	 *
> +	 * Store both the closest mode that matches the requested format, and
> +	 * the closest mode for all other formats. The best match is returned
> +	 * if found, otherwise the best mode with a non-matching format is
> +	 * returned.
> +	 */
> +	for (; *list; list++) {
> +		unsigned int dist;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		et8ek8_reglist_to_mbus(*list, &format);
> +
> +		dist = min(fmt->width, format.width)
> +		     * min(fmt->height, format.height);
> +		dist = format.width * format.height
> +		     + fmt->width * fmt->height - 2 * dist;
> +
> +
> +		if (fmt->code == format.code) {
> +			if (dist < max_dist_match || !best_match) {
> +				best_match = *list;
> +				max_dist_match = dist;
> +			}
> +		} else {
> +			if (dist < max_dist_other || !best_other) {
> +				best_other = *list;
> +				max_dist_other = dist;
> +			}
> +		}
> +	}
> +
> +	return best_match ? best_match : best_other;
> +}
> +
> +#define TIMEPERFRAME_AVG_FPS(t)						\
> +	(((t).denominator + ((t).numerator >> 1)) / (t).numerator)
> +
> +static struct et8ek8_reglist *et8ek8_reglist_find_mode_ival(
> +		struct et8ek8_meta_reglist *meta,
> +		struct et8ek8_reglist *current_reglist,
> +		struct v4l2_fract *timeperframe)
> +{
> +	int fps = TIMEPERFRAME_AVG_FPS(*timeperframe);
> +	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
> +	struct et8ek8_mode *current_mode = &current_reglist->mode;
> +
> +	for (; *list; list++) {
> +		struct et8ek8_mode *mode = &(*list)->mode;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		if (mode->window_width != current_mode->window_width ||
> +		    mode->window_height != current_mode->window_height)
> +			continue;
> +
> +		if (TIMEPERFRAME_AVG_FPS(mode->timeperframe) == fps)
> +			return *list;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int et8ek8_reglist_cmp(const void *a, const void *b)
> +{
> +	const struct et8ek8_reglist **list1 = (const struct et8ek8_reglist **)a,
> +		**list2 = (const struct et8ek8_reglist **)b;
> +
> +	/* Put real modes in the beginning. */
> +	if ((*list1)->type == ET8EK8_REGLIST_MODE &&
> +	    (*list2)->type != ET8EK8_REGLIST_MODE)
> +		return -1;
> +	if ((*list1)->type != ET8EK8_REGLIST_MODE &&
> +	    (*list2)->type == ET8EK8_REGLIST_MODE)
> +		return 1;
> +
> +	/* Descending width. */
> +	if ((*list1)->mode.window_width > (*list2)->mode.window_width)
> +		return -1;
> +	if ((*list1)->mode.window_width < (*list2)->mode.window_width)
> +		return 1;
> +
> +	if ((*list1)->mode.window_height > (*list2)->mode.window_height)
> +		return -1;
> +	if ((*list1)->mode.window_height < (*list2)->mode.window_height)
> +		return 1;
> +
> +	return 0;
> +}
> +
> +static int et8ek8_reglist_import(struct i2c_client *client,
> +				 struct et8ek8_meta_reglist *meta)
> +{
> +	int nlists = 0, i;
> +
> +	dev_info(&client->dev, "meta_reglist version %s\n", meta->version);
> +
> +	while (meta->reglist[nlists].ptr)
> +		nlists++;
> +
> +	if (!nlists)
> +		return -EINVAL;
> +
> +	sort(&meta->reglist[0].ptr, nlists, sizeof(meta->reglist[0].ptr),
> +	     et8ek8_reglist_cmp, NULL);
> +
> +	i = nlists;
> +	nlists = 0;
> +
> +	while (i--) {
> +		struct et8ek8_reglist *list;
> +
> +		list = meta->reglist[nlists].ptr;
> +
> +		dev_dbg(&client->dev,
> +		       "%s: type %d\tw %d\th %d\tfmt %x\tival %d/%d\tptr %p\n",
> +		       __func__,
> +		       list->type,
> +		       list->mode.window_width, list->mode.window_height,
> +		       list->mode.pixel_format,
> +		       list->mode.timeperframe.numerator,
> +		       list->mode.timeperframe.denominator,
> +		       (void *)meta->reglist[nlists].ptr);
> +
> +		nlists++;
> +	}
> +
> +	return 0;
> +}
> +
> +typedef unsigned int fixpoint8; /* .8 fixed point format. */
> +
> +/*
> + * Return time of one row in microseconds
> + * If the sensor is not set to any mode, return zero.
> + */
> +fixpoint8 et8ek8_get_row_time(struct et8ek8_sensor *sensor)
> +{
> +	unsigned int clock;	/* Pixel clock in Hz>>10 fixed point */
> +	fixpoint8 rt;	/* Row time in .8 fixed point */
> +
> +	if (!sensor->current_reglist)
> +		return 0;
> +
> +	clock = sensor->current_reglist->mode.pixel_clock;
> +	clock = (clock + (1 << 9)) >> 10;
> +	rt = sensor->current_reglist->mode.width * (1000000 >> 2);
> +	rt = (rt + (clock >> 1)) / clock;
> +
> +	return rt;
> +}
> +
> +/*
> + * Convert exposure time `us' to rows. Modify `us' to make it to
> + * correspond to the actual exposure time.
> + */
> +static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us)
> +{
> +	unsigned int rows;	/* Exposure value as written to HW (ie. rows) */
> +	fixpoint8 rt;	/* Row time in .8 fixed point */
> +
> +	/* Assume that the maximum exposure time is at most ~8 s,
> +	 * and the maximum width (with blanking) ~8000 pixels.
> +	 * The formula here is in principle as simple as
> +	 *    rows = exptime / 1e6 / width * pixel_clock
> +	 * but to get accurate results while coping with value ranges,
> +	 * have to do some fixed point math.
> +	 */
> +
> +	rt = et8ek8_get_row_time(sensor);
> +	rows = ((*us << 8) + (rt >> 1)) / rt;
> +
> +	if (rows > sensor->current_reglist->mode.max_exp)
> +		rows = sensor->current_reglist->mode.max_exp;
> +
> +	/* Set the exposure time to the rounded value */
> +	*us = (rt * rows + (1 << 7)) >> 8;
> +
> +	return rows;
> +}
> +
> +/*
> + * Convert exposure time in rows to microseconds
> + */
> +static int et8ek8_exposure_rows_to_us(struct et8ek8_sensor *sensor, int rows)
> +{
> +	return (et8ek8_get_row_time(sensor) * rows + (1 << 7)) >> 8;
> +}
> +
> +/* Called to change the V4L2 gain control value. This function
> + * rounds and clamps the given value and updates the V4L2 control value.
> + * If power is on, also updates the sensor analog and digital gains.
> + * gain is in 0.1 EV (exposure value) units.
> + */
> +static int et8ek8_set_gain(struct et8ek8_sensor *sensor, s32 gain)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +	struct et8ek8_gain new;
> +	int r;
> +
> +	new = et8ek8_gain_table[gain];
> +
> +	/* FIXME: optimise I2C writes! */
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x124a, new.analog >> 8);
> +	if (r)
> +		return r;
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x1249, new.analog & 0xff);
> +	if (r)
> +		return r;
> +
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x124d, new.digital >> 8);
> +	if (r)
> +		return r;
> +	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
> +				0x124c, new.digital & 0xff);
> +
> +	return r;
> +}
> +
> +static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +	int cbh_mode, cbv_mode, tp_mode, din_sw, r1420, rval;
> +
> +	/* Values for normal mode */
> +	cbh_mode = 0;
> +	cbv_mode = 0;
> +	tp_mode  = 0;
> +	din_sw   = 0x00;
> +	r1420    = 0xF0;
> +
> +	if (mode) {
> +		/* Test pattern mode */
> +		if (mode < 5) {
> +			cbh_mode = 1;
> +			cbv_mode = 1;
> +			tp_mode  = mode + 3;
> +		} else {
> +			cbh_mode = 0;
> +			cbv_mode = 0;
> +			tp_mode  = mode - 4 + 3;
> +		}
> +
> +		din_sw   = 0x01;
> +		r1420    = 0xE0;
> +	}
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x111B,
> +				    tp_mode << 4);
> +	if (rval)
> +		return rval;
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1121,
> +				    cbh_mode << 7);
> +	if (rval)
> +		return rval;
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124,
> +				    cbv_mode << 7);
> +	if (rval)
> +		return rval;		
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x112C, din_sw);
> +	if (rval)
> +		return rval;
> +
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420);
> +	return rval;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 controls
> + */
> +
> +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct et8ek8_sensor *sensor =
> +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +	int uninitialized_var(rows);
> +
> +	if (ctrl->id == V4L2_CID_EXPOSURE)
> +		rows = et8ek8_exposure_us_to_rows(sensor, (u32 *)&ctrl->val);
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_GAIN:
> +		return et8ek8_set_gain(sensor, ctrl->val);
> +
> +	case V4L2_CID_EXPOSURE:
> +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> +					    swab16(rows));
> +
> +	case V4L2_CID_TEST_PATTERN:
> +		return et8ek8_set_test_pattern(sensor, ctrl->val);
> +
> +	case V4L2_CID_PIXEL_RATE:
> +		/* For v4l2_ctrl_s_ctrl_int64() used internally. */
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = {
> +	.s_ctrl = et8ek8_set_ctrl,
> +};
> +
> +static const char * const et8ek8_test_pattern_menu[] = {
> +	"Normal",
> +	"Vertical colorbar",
> +	"Horizontal colorbar",
> +	"Scale",
> +	"Ramp",
> +	"Small vertical colorbar",
> +	"Small horizontal colorbar",
> +	"Small scale",
> +	"Small ramp",
> +};
> +
> +static int et8ek8_init_controls(struct et8ek8_sensor *sensor)
> +{
> +	u32 min, max;
> +
> +	v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4);
> +
> +	/* V4L2_CID_GAIN */
> +	v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> +			  V4L2_CID_GAIN, 0, ARRAY_SIZE(et8ek8_gain_table) - 1,
> +			  1, 0);
> +
> +	/* V4L2_CID_EXPOSURE */
> +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> +	max = et8ek8_exposure_rows_to_us(sensor,
> +				sensor->current_reglist->mode.max_exp);

Haven't I suggested to use lines instead? I vaguely remember doing so...
this would remove quite some code from the driver.

> +	sensor->exposure =
> +		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> +				  V4L2_CID_EXPOSURE, min, max, min, max);
> +
> +	/* V4L2_CID_PIXEL_RATE */
> +	sensor->pixel_rate =
> +		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
> +		V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
> +
> +	/* V4L2_CID_TEST_PATTERN */
> +	v4l2_ctrl_new_std_menu_items(&sensor->ctrl_handler,
> +				     &et8ek8_ctrl_ops, V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(et8ek8_test_pattern_menu) - 1,
> +				     0, 0, et8ek8_test_pattern_menu);
> +
> +	if (sensor->ctrl_handler.error)
> +		return sensor->ctrl_handler.error;
> +
> +	sensor->subdev.ctrl_handler = &sensor->ctrl_handler;
> +
> +	return 0;
> +}
> +
> +static void et8ek8_update_controls(struct et8ek8_sensor *sensor)
> +{
> +	struct v4l2_ctrl *ctrl = sensor->exposure;
> +	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
> +	u32 min, max, pixel_rate;
> +	static const int S = 8;
> +
> +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> +	max = et8ek8_exposure_rows_to_us(sensor, mode->max_exp);
> +
> +	/*
> +	 * Calculate average pixel clock per line. Assume buffers can spread
> +	 * the data over horizontal blanking time. Rounding upwards.
> +	 * Formula taken from stock Nokia N900 kernel.
> +	 */
> +	pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width;
> +	pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width;
> +
> +	v4l2_ctrl_lock(ctrl);
> +	ctrl->minimum = min;
> +	ctrl->maximum = max;
> +	ctrl->step = min;
> +	ctrl->default_value = max;
> +	ctrl->val = max;
> +	ctrl->cur.val = max;
> +	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
> +	v4l2_ctrl_unlock(ctrl);
> +}
> +
> +static int et8ek8_configure(struct et8ek8_sensor *sensor)
> +{
> +	struct v4l2_subdev *subdev = &sensor->subdev;
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	int rval;
> +
> +	rval = et8ek8_i2c_write_regs(client, sensor->current_reglist->regs);
> +	if (rval)
> +		goto fail;
> +
> +	/* Controls set while the power to the sensor is turned off are saved
> +	 * but not applied to the hardware. Now that we're about to start
> +	 * streaming apply all the current values to the hardware.
> +	 */
> +	rval = v4l2_ctrl_handler_setup(&sensor->ctrl_handler);
> +	if (rval)
> +		goto fail;
> +
> +	return 0;
> +
> +fail:
> +	dev_err(&client->dev, "sensor configuration failed\n");
> +
> +	return rval;
> +}
> +
> +static int et8ek8_stream_on(struct et8ek8_sensor *sensor)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +
> +	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0xb0);
> +}
> +
> +static int et8ek8_stream_off(struct et8ek8_sensor *sensor)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +
> +	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0x30);
> +}
> +
> +static int et8ek8_s_stream(struct v4l2_subdev *subdev, int streaming)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	int ret;
> +
> +	if (!streaming)
> +		return et8ek8_stream_off(sensor);
> +
> +	ret = et8ek8_configure(sensor);
> +	if (ret < 0)
> +		return ret;
> +
> +	return et8ek8_stream_on(sensor);
> +}
> +
> +/* --------------------------------------------------------------------------
> + * V4L2 subdev operations
> + */
> +
> +static int et8ek8_power_off(struct et8ek8_sensor *sensor)
> +{
> +	gpiod_set_value(sensor->reset, 0);
> +	udelay(1);
> +
> +	clk_disable_unprepare(sensor->ext_clk);
> +
> +	return regulator_disable(sensor->vana);
> +}
> +
> +static int et8ek8_power_on(struct et8ek8_sensor *sensor)
> +{
> +	struct v4l2_subdev *subdev = &sensor->subdev;
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	unsigned int xclk_freq;
> +	int val, rval;
> +
> +	rval = regulator_enable(sensor->vana);
> +	if (rval) {
> +		dev_err(&client->dev, "failed to enable vana regulator\n");
> +		return rval;
> +	}
> +
> +	if (sensor->current_reglist)
> +		xclk_freq = sensor->current_reglist->mode.ext_clock;
> +	else
> +		xclk_freq = sensor->xclk_freq;
> +
> +	rval = clk_set_rate(sensor->ext_clk, xclk_freq);
> +	if (rval < 0) {
> +		dev_err(&client->dev, "unable to set extclk clock freq to %u\n",
> +			xclk_freq);
> +		goto out;
> +	}
> +	rval = clk_prepare_enable(sensor->ext_clk);
> +	if (rval < 0) {
> +		dev_err(&client->dev, "failed to enable extclk\n");
> +		goto out;
> +	}
> +
> +	if (rval)
> +		goto out;
> +
> +	udelay(10); /* I wish this is a good value */
> +
> +	gpiod_set_value(sensor->reset, 1);
> +
> +	msleep(5000 * 1000 / xclk_freq + 1); /* Wait 5000 cycles */
> +
> +	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
> +					     ET8EK8_REGLIST_POWERON);
> +	if (rval)
> +		goto out;
> +
> +#ifdef USE_CRC
> +	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, 0x1263, &val);
> +	if (rval)
> +		goto out;
> +#if USE_CRC /* TODO get crc setting from DT */
> +	val |= BIT(4);
> +#else
> +	val &= ~BIT(4);
> +#endif
> +	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1263, val);
> +	if (rval)
> +		goto out;
> +#endif
> +
> +out:
> +	if (rval)
> +		et8ek8_power_off(sensor);
> +
> +	return rval;
> +}
> +
> +/* --------------------------------------------------------------------------
> + * V4L2 subdev video operations
> + */
> +#define MAX_FMTS 4
> +static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev,
> +				 struct v4l2_subdev_pad_config *cfg,
> +				 struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct et8ek8_reglist **list =
> +			et8ek8_reglist_first(&meta_reglist);
> +	u32 pixelformat[MAX_FMTS];
> +	int npixelformat = 0;
> +
> +	if (code->index >= MAX_FMTS)
> +		return -EINVAL;
> +
> +	for (; *list; list++) {
> +		struct et8ek8_mode *mode = &(*list)->mode;
> +		int i;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		for (i = 0; i < npixelformat; i++) {
> +			if (pixelformat[i] == mode->pixel_format)
> +				break;
> +		}
> +		if (i != npixelformat)
> +			continue;
> +
> +		if (code->index == npixelformat) {
> +			if (mode->pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
> +				code->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
> +			else
> +				code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +			return 0;
> +		}
> +
> +		pixelformat[npixelformat] = mode->pixel_format;
> +		npixelformat++;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int et8ek8_enum_frame_size(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_pad_config *cfg,
> +				  struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	struct et8ek8_reglist **list =
> +			et8ek8_reglist_first(&meta_reglist);
> +	struct v4l2_mbus_framefmt format;
> +	int cmp_width = INT_MAX;
> +	int cmp_height = INT_MAX;
> +	int index = fse->index;
> +
> +	for (; *list; list++) {
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		et8ek8_reglist_to_mbus(*list, &format);
> +		if (fse->code != format.code)
> +			continue;
> +
> +		/* Assume that the modes are grouped by frame size. */
> +		if (format.width == cmp_width && format.height == cmp_height)
> +			continue;
> +
> +		cmp_width = format.width;
> +		cmp_height = format.height;
> +
> +		if (index-- == 0) {
> +			fse->min_width = format.width;
> +			fse->min_height = format.height;
> +			fse->max_width = format.width;
> +			fse->max_height = format.height;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int et8ek8_enum_frame_ival(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_pad_config *cfg,
> +				  struct v4l2_subdev_frame_interval_enum *fie)
> +{
> +	struct et8ek8_reglist **list =
> +			et8ek8_reglist_first(&meta_reglist);
> +	struct v4l2_mbus_framefmt format;
> +	int index = fie->index;
> +
> +	for (; *list; list++) {
> +		struct et8ek8_mode *mode = &(*list)->mode;
> +
> +		if ((*list)->type != ET8EK8_REGLIST_MODE)
> +			continue;
> +
> +		et8ek8_reglist_to_mbus(*list, &format);
> +		if (fie->code != format.code)
> +			continue;
> +
> +		if (fie->width != format.width || fie->height != format.height)
> +			continue;
> +
> +		if (index-- == 0) {
> +			fie->interval = mode->timeperframe;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +__et8ek8_get_pad_format(struct et8ek8_sensor *sensor,
> +			struct v4l2_subdev_pad_config *cfg,
> +			unsigned int pad, enum v4l2_subdev_format_whence which)
> +{
> +	switch (which) {
> +	case V4L2_SUBDEV_FORMAT_TRY:
> +		return v4l2_subdev_get_try_format(&sensor->subdev, cfg, pad);
> +	case V4L2_SUBDEV_FORMAT_ACTIVE:
> +		return &sensor->format;
> +	default:
> +		return NULL;
> +	}
> +}
> +
> +static int et8ek8_get_pad_format(struct v4l2_subdev *subdev,
> +				 struct v4l2_subdev_pad_config *cfg,
> +				 struct v4l2_subdev_format *fmt)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct v4l2_mbus_framefmt *format;
> +
> +	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
> +	if (!format)
> +		return -EINVAL;
> +
> +	fmt->format = *format;
> +
> +	return 0;
> +}
> +
> +static int et8ek8_set_pad_format(struct v4l2_subdev *subdev,
> +				 struct v4l2_subdev_pad_config *cfg,
> +				 struct v4l2_subdev_format *fmt)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct v4l2_mbus_framefmt *format;
> +	struct et8ek8_reglist *reglist;
> +
> +	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
> +	if (!format)
> +		return -EINVAL;
> +
> +	reglist = et8ek8_reglist_find_mode_fmt(&meta_reglist, &fmt->format);
> +	et8ek8_reglist_to_mbus(reglist, &fmt->format);
> +	*format = fmt->format;
> +
> +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> +		sensor->current_reglist = reglist;
> +		et8ek8_update_controls(sensor);
> +	}
> +
> +	return 0;
> +}
> +
> +static int et8ek8_get_frame_interval(struct v4l2_subdev *subdev,
> +				     struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	memset(fi, 0, sizeof(*fi));
> +	fi->interval = sensor->current_reglist->mode.timeperframe;
> +
> +	return 0;
> +}
> +
> +static int et8ek8_set_frame_interval(struct v4l2_subdev *subdev,
> +				     struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct et8ek8_reglist *reglist;
> +
> +	reglist = et8ek8_reglist_find_mode_ival(&meta_reglist,
> +						sensor->current_reglist,
> +						&fi->interval);
> +
> +	if (!reglist)
> +		return -EINVAL;
> +
> +	if (sensor->current_reglist->mode.ext_clock != reglist->mode.ext_clock)
> +		return -EINVAL;
> +
> +	sensor->current_reglist = reglist;
> +	et8ek8_update_controls(sensor);
> +
> +	return 0;
> +}
> +
> +static int et8ek8_g_priv_mem(struct v4l2_subdev *subdev)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	unsigned int length = ET8EK8_PRIV_MEM_SIZE;
> +	unsigned int offset = 0;
> +	u8 *ptr  = sensor->priv_mem;
> +	int rval = 0;
> +
> +	/* Read the EEPROM window-by-window, each window 8 bytes */
> +	do {
> +		u8 buffer[PRIV_MEM_WIN_SIZE];
> +		struct i2c_msg msg;
> +		int bytes, i;
> +		int ofs;
> +
> +		/* Set the current window */
> +		rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x0001,
> +					    0xe0 | (offset >> 3));
> +		if (rval < 0)
> +			return rval;
> +
> +		/* Wait for status bit */
> +		for (i = 0; i < 1000; ++i) {
> +			u32 status;
> +
> +			rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> +						   0x0003, &status);
> +			if (rval < 0)
> +				return rval;
> +			if (!(status & 0x08))
> +				break;
> +			usleep_range(1000, 2000);
> +		};
> +
> +		if (i == 1000)
> +			return -EIO;
> +
> +		/* Read window, 8 bytes at once, and copy to user space */
> +		ofs = offset & 0x07;	/* Offset within this window */
> +		bytes = length + ofs > 8 ? 8-ofs : length;
> +		msg.addr = client->addr;
> +		msg.flags = 0;
> +		msg.len = 2;
> +		msg.buf = buffer;
> +		ofs += PRIV_MEM_START_REG;
> +		buffer[0] = (u8)(ofs >> 8);
> +		buffer[1] = (u8)(ofs & 0xFF);
> +
> +		rval = i2c_transfer(client->adapter, &msg, 1);
> +		if (rval < 0)
> +			return rval;
> +
> +		mdelay(ET8EK8_I2C_DELAY);
> +		msg.addr = client->addr;
> +		msg.len = bytes;
> +		msg.flags = I2C_M_RD;
> +		msg.buf = buffer;
> +		memset(buffer, 0, sizeof(buffer));
> +
> +		rval = i2c_transfer(client->adapter, &msg, 1);
> +		if (rval < 0)
> +			return rval;
> +
> +		rval = 0;
> +		memcpy(ptr, buffer, bytes);
> +
> +		length -= bytes;
> +		offset += bytes;
> +		ptr += bytes;
> +	} while (length > 0);
> +
> +	return rval;
> +}
> +
> +static int et8ek8_dev_init(struct v4l2_subdev *subdev)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	int rval, rev_l, rev_h;
> +
> +	rval = et8ek8_power_on(sensor);
> +	if (rval) {
> +		dev_err(&client->dev, "could not power on\n");
> +		return rval;
> +	}
> +
> +	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> +				   REG_REVISION_NUMBER_L, &rev_l);
> +	if (!rval)
> +		rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
> +					   REG_REVISION_NUMBER_H, &rev_h);
> +	if (rval) {
> +		dev_err(&client->dev, "no et8ek8 sensor detected\n");
> +		goto out_poweroff;
> +	}
> +
> +	sensor->version = (rev_h << 8) + rev_l;
> +	if (sensor->version != ET8EK8_REV_1 && sensor->version != ET8EK8_REV_2)
> +		dev_info(&client->dev,
> +			 "unknown version 0x%x detected, continuing anyway\n",
> +			 sensor->version);
> +
> +	rval = et8ek8_reglist_import(client, &meta_reglist);
> +	if (rval) {
> +		dev_err(&client->dev,
> +			"invalid register list %s, import failed\n",
> +			ET8EK8_NAME);
> +		goto out_poweroff;
> +	}
> +
> +	sensor->current_reglist = et8ek8_reglist_find_type(&meta_reglist,
> +							   ET8EK8_REGLIST_MODE);
> +	if (!sensor->current_reglist) {
> +		dev_err(&client->dev,
> +			"invalid register list %s, no mode found\n",
> +			ET8EK8_NAME);
> +		rval = -ENODEV;
> +		goto out_poweroff;
> +	}
> +
> +	et8ek8_reglist_to_mbus(sensor->current_reglist, &sensor->format);
> +
> +	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
> +					     ET8EK8_REGLIST_POWERON);
> +	if (rval) {
> +		dev_err(&client->dev,
> +			"invalid register list %s, no POWERON mode found\n",
> +			ET8EK8_NAME);
> +		goto out_poweroff;
> +	}
> +	rval = et8ek8_stream_on(sensor); /* Needed to be able to read EEPROM */
> +	if (rval)
> +		goto out_poweroff;
> +	rval = et8ek8_g_priv_mem(subdev);
> +	if (rval)
> +		dev_warn(&client->dev,
> +			"can not read OTP (EEPROM) memory from sensor\n");
> +	rval = et8ek8_stream_off(sensor);
> +	if (rval)
> +		goto out_poweroff;
> +
> +	rval = et8ek8_power_off(sensor);
> +	if (rval)
> +		goto out_poweroff;
> +
> +	return 0;
> +
> +out_poweroff:
> +	et8ek8_power_off(sensor);
> +
> +	return rval;
> +}
> +
> +/* --------------------------------------------------------------------------
> + * sysfs attributes
> + */
> +static ssize_t
> +et8ek8_priv_mem_read(struct device *dev, struct device_attribute *attr,
> +		     char *buf)
> +{
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +#if PAGE_SIZE < ET8EK8_PRIV_MEM_SIZE
> +#error PAGE_SIZE too small!
> +#endif
> +
> +	memcpy(buf, sensor->priv_mem, ET8EK8_PRIV_MEM_SIZE);
> +
> +	return ET8EK8_PRIV_MEM_SIZE;
> +}
> +static DEVICE_ATTR(priv_mem, S_IRUGO, et8ek8_priv_mem_read, NULL);
> +
> +/* --------------------------------------------------------------------------
> + * V4L2 subdev core operations
> + */
> +
> +static int
> +et8ek8_registered(struct v4l2_subdev *subdev)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	struct i2c_client *client = v4l2_get_subdevdata(subdev);
> +	struct v4l2_mbus_framefmt *format;
> +	int rval;
> +
> +	dev_dbg(&client->dev, "registered!");
> +
> +	rval = device_create_file(&client->dev, &dev_attr_priv_mem);
> +	if (rval) {
> +		dev_err(&client->dev, "could not register sysfs entry\n");
> +		return rval;
> +	}
> +
> +	rval = et8ek8_dev_init(subdev);
> +	if (rval)
> +		goto err_file;
> +
> +	rval = et8ek8_init_controls(sensor);
> +	if (rval) {
> +		dev_err(&client->dev, "controls initialization failed\n");
> +		goto err_file;
> +	}
> +
> +	format = __et8ek8_get_pad_format(sensor, NULL, 0,
> +					 V4L2_SUBDEV_FORMAT_ACTIVE);
> +	return 0;
> +
> +err_file:
> +	device_remove_file(&client->dev, &dev_attr_priv_mem);
> +
> +	return rval;
> +}
> +
> +static int __et8ek8_set_power(struct et8ek8_sensor *sensor, bool on)
> +{
> +	return on ? et8ek8_power_on(sensor) : et8ek8_power_off(sensor);
> +}
> +
> +static int et8ek8_set_power(struct v4l2_subdev *subdev, int on)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +	int ret = 0;
> +
> +	mutex_lock(&sensor->power_lock);
> +
> +	/* If the power count is modified from 0 to != 0 or from != 0 to 0,
> +	 * update the power state.
> +	 */
> +	if (sensor->power_count == !on) {
> +		ret = __et8ek8_set_power(sensor, !!on);
> +		if (ret < 0)
> +			goto done;
> +	}
> +
> +	/* Update the power count. */
> +	sensor->power_count += on ? 1 : -1;
> +	WARN_ON(sensor->power_count < 0);
> +
> +done:
> +	mutex_unlock(&sensor->power_lock);
> +
> +	return ret;
> +}
> +
> +static int et8ek8_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(sd);
> +	struct v4l2_mbus_framefmt *format;
> +	struct et8ek8_reglist *reglist;
> +
> +	reglist = et8ek8_reglist_find_type(&meta_reglist, ET8EK8_REGLIST_MODE);
> +	format = __et8ek8_get_pad_format(sensor, fh->pad, 0,
> +					 V4L2_SUBDEV_FORMAT_TRY);
> +	et8ek8_reglist_to_mbus(reglist, format);
> +
> +	return et8ek8_set_power(sd, true);
> +}
> +
> +static int et8ek8_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> +	return et8ek8_set_power(sd, false);
> +}
> +
> +static const struct v4l2_subdev_video_ops et8ek8_video_ops = {
> +	.s_stream = et8ek8_s_stream,
> +	.g_frame_interval = et8ek8_get_frame_interval,
> +	.s_frame_interval = et8ek8_set_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_core_ops et8ek8_core_ops = {
> +	.s_power = et8ek8_set_power,
> +};
> +
> +static const struct v4l2_subdev_pad_ops et8ek8_pad_ops = {
> +	.enum_mbus_code = et8ek8_enum_mbus_code,
> +	.enum_frame_size = et8ek8_enum_frame_size,
> +	.enum_frame_interval = et8ek8_enum_frame_ival,
> +	.get_fmt = et8ek8_get_pad_format,
> +	.set_fmt = et8ek8_set_pad_format,
> +};
> +
> +static const struct v4l2_subdev_ops et8ek8_ops = {
> +	.core = &et8ek8_core_ops,
> +	.video = &et8ek8_video_ops,
> +	.pad = &et8ek8_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = {
> +	.registered = et8ek8_registered,
> +	.open = et8ek8_open,
> +	.close = et8ek8_close,
> +};
> +
> +/* --------------------------------------------------------------------------
> + * I2C driver
> + */
> +#ifdef CONFIG_PM
> +
> +static int et8ek8_suspend(struct device *dev)

static int __maybe_unused ...

Please check the smiapp patches I just sent to the list. The smiapp driver
had similar issues.

> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	if (!sensor->power_count)
> +		return 0;
> +
> +	return __et8ek8_set_power(sensor, false);
> +}
> +
> +static int et8ek8_resume(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	if (!sensor->power_count)
> +		return 0;
> +
> +	return __et8ek8_set_power(sensor, true);
> +}
> +
> +#else
> +
> +#define et8ek8_suspend NULL
> +#define et8ek8_resume NULL
> +
> +#endif /* CONFIG_PM */
> +
> +static int et8ek8_probe(struct i2c_client *client,
> +			const struct i2c_device_id *devid)
> +{
> +	struct et8ek8_sensor *sensor;
> +	struct device *dev = &client->dev;
> +	int ret;
> +
> +	sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
> +	if (!sensor)
> +		return -ENOMEM;
> +
> +	sensor->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> +	if (IS_ERR(sensor->reset)) {
> +		dev_dbg(&client->dev, "could not request reset gpio\n");
> +		return PTR_ERR(sensor->reset);
> +	}
> +
> +	sensor->vana = devm_regulator_get(dev, "vana");
> +	if (IS_ERR(sensor->vana)) {
> +		dev_err(&client->dev, "could not get regulator for vana\n");
> +		return PTR_ERR(sensor->vana);
> +	}
> +
> +	sensor->ext_clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(sensor->ext_clk)) {
> +		dev_err(&client->dev, "could not get clock\n");
> +		return PTR_ERR(sensor->ext_clk);
> +	}
> +
> +	ret = of_property_read_u32(dev->of_node, "clock-frequency",
> +				   &sensor->xclk_freq);
> +	if (ret) {
> +		dev_warn(dev, "can't get clock-frequency\n");
> +		return ret;
> +	}
> +
> +	mutex_init(&sensor->power_lock);
> +
> +	v4l2_i2c_subdev_init(&sensor->subdev, client, &et8ek8_ops);
> +	sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sensor->subdev.internal_ops = &et8ek8_internal_ops;
> +
> +	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad);
> +	if (ret < 0) {
> +		dev_err(&client->dev, "media entity init failed!\n");
> +		return ret;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&sensor->subdev);
> +	if (ret < 0) {
> +		media_entity_cleanup(&sensor->subdev.entity);
> +		return ret;
> +	}
> +
> +	dev_dbg(dev, "initialized!\n");
> +
> +	return 0;
> +}
> +
> +static int __exit et8ek8_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> +
> +	if (sensor->power_count) {
> +		gpiod_set_value(sensor->reset, 0);
> +		clk_disable_unprepare(sensor->ext_clk);
> +		sensor->power_count = 0;
> +	}
> +

You're missing v4l2_async_unregister_subdev() here.

> +	v4l2_device_unregister_subdev(&sensor->subdev);
> +	device_remove_file(&client->dev, &dev_attr_priv_mem);
> +	v4l2_ctrl_handler_free(&sensor->ctrl_handler);
> +	media_entity_cleanup(&sensor->subdev.entity);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id et8ek8_of_table[] = {
> +	{ .compatible = "toshiba,et8ek8" },
> +	{ },
> +};
> +
> +static const struct i2c_device_id et8ek8_id_table[] = {
> +	{ ET8EK8_NAME, 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, et8ek8_id_table);
> +
> +static const struct dev_pm_ops et8ek8_pm_ops = {
> +	.suspend	= et8ek8_suspend,
> +	.resume		= et8ek8_resume,

How about using  SET_SYSTEM_SLEEP_PM_OPS() here?

> +};
> +
> +static struct i2c_driver et8ek8_i2c_driver = {
> +	.driver		= {
> +		.name	= ET8EK8_NAME,
> +		.pm	= &et8ek8_pm_ops,
> +		.of_match_table	= et8ek8_of_table,
> +	},
> +	.probe		= et8ek8_probe,
> +	.remove		= __exit_p(et8ek8_remove),
> +	.id_table	= et8ek8_id_table,
> +};
> +
> +module_i2c_driver(et8ek8_i2c_driver);
> +
> +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");

You should put your name here as well. :-)

It's been a long time I even tried to use it. :-i

> +MODULE_DESCRIPTION("Toshiba ET8EK8 camera sensor driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/i2c/et8ek8/et8ek8_mode.c b/drivers/media/i2c/et8ek8/et8ek8_mode.c
> new file mode 100644
> index 0000000..956fc60
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_mode.c
> @@ -0,0 +1,587 @@
> +/*
> + * et8ek8_mode.c
> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukka.o.toivonen@nokia.com>

Tuukka's e-mail is wrong here (the correct address is elsewhere in the
patch).

> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include "et8ek8_reg.h"
> +
> +/*
> + * Stingray sensor mode settings for Scooby
> + */
> +
> +/* Mode1_poweron_Mode2_16VGA_2592x1968_12.07fps */
> +static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 640 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 137 (3288)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_POWERON,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3288,
> +		.height = 2016,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 2592,
> +		.window_height = 1968,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 1207
> +		},
> +		.max_exp = 2012,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		/* Need to set firstly */
> +		{ ET8EK8_REG_8BIT, 0x126C, 0xCC },
> +		/* Strobe and Data of CCP2 delay are minimized. */
> +		{ ET8EK8_REG_8BIT, 0x1269, 0x00 },
> +		/* Refined value of Min H_COUNT  */
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		/* Frequency of SPCK setting (SPCK=MRCK) */
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x1241, 0x94 },
> +		{ ET8EK8_REG_8BIT, 0x1242, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x124B, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1255, 0xFF },
> +		{ ET8EK8_REG_8BIT, 0x1256, 0x9F },
> +		{ ET8EK8_REG_8BIT, 0x1258, 0x00 },
> +		/* From parallel out to serial out */
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 },
> +		/* From w/ embeded data to w/o embeded data */
> +		{ ET8EK8_REG_8BIT, 0x125E, 0xC0 },
> +		/* CCP2 out is from STOP to ACTIVE */
> +		{ ET8EK8_REG_8BIT, 0x1263, 0x98 },
> +		{ ET8EK8_REG_8BIT, 0x1268, 0xC6 },
> +		{ ET8EK8_REG_8BIT, 0x1434, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1163, 0x44 },
> +		{ ET8EK8_REG_8BIT, 0x1166, 0x29 },
> +		{ ET8EK8_REG_8BIT, 0x1140, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x1011, 0x24 },
> +		{ ET8EK8_REG_8BIT, 0x1151, 0x80 },
> +		{ ET8EK8_REG_8BIT, 0x1152, 0x23 },
> +		/* Initial setting for improvement2 of lower frequency noise */
> +		{ ET8EK8_REG_8BIT, 0x1014, 0x05 },
> +		{ ET8EK8_REG_8BIT, 0x1033, 0x06 },
> +		{ ET8EK8_REG_8BIT, 0x1034, 0x79 },
> +		{ ET8EK8_REG_8BIT, 0x1423, 0x3F },
> +		{ ET8EK8_REG_8BIT, 0x1424, 0x3F },
> +		{ ET8EK8_REG_8BIT, 0x1426, 0x00 },
> +		/* Switch of Preset-White-balance (0d:disable / 1d:enable) */
> +		{ ET8EK8_REG_8BIT, 0x1439, 0x00 },
> +		/* Switch of blemish correction (0d:disable / 1d:enable) */
> +		{ ET8EK8_REG_8BIT, 0x161F, 0x60 },
> +		/* Switch of auto noise correction (0d:disable / 1d:enable) */
> +		{ ET8EK8_REG_8BIT, 0x1634, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1646, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1648, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x113E, 0x01 },
> +		{ ET8EK8_REG_8BIT, 0x113F, 0x22 },
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode1_16VGA_2592x1968_13.12fps_DPCM10-8 */
> +static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 560 MHz
> + * VCO        = 560 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 128 (3072)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 175
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 6
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3072,
> +		.height = 2016,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 2592,
> +		.window_height = 1968,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 1292
> +		},
> +		.max_exp = 2012,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x57 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x06 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x80 }, /* <-changed to v14 7E->80 */
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode3_4VGA_1296x984_29.99fps_DPCM10-8 */
> +static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = {
> +/* (without the +1)
> + * SPCK       = 96.5333333333333 MHz
> + * CCP2       = 579.2 MHz
> + * VCO        = 579.2 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 133 (3192)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 181
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 5
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3192,
> +		.height = 1008,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 1296,
> +		.window_height = 984,
> +		.pixel_clock = 96533333,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 3000
> +		},
> +		.max_exp = 1004,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x5A },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode4_SVGA_864x656_29.88fps */
> +static struct et8ek8_reglist mode4_svga_864x656_29_88fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 320 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 166 (3984)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 1
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3984,
> +		.height = 672,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 864,
> +		.window_height = 656,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 2988
> +		},
> +		.max_exp = 668,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x62 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x62 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0xA6 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode5_VGA_648x492_29.93fps */
> +static struct et8ek8_reglist mode5_vga_648x492_29_93fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 320 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 221 (5304)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 1
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 5304,
> +		.height = 504,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 648,
> +		.window_height = 492,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 2993
> +		},
> +		.max_exp = 500,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode2_16VGA_2592x1968_3.99fps */
> +static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = {
> +/* (without the +1)
> + * SPCK       = 80 MHz
> + * CCP2       = 640 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 254 (6096)
> + * HCOUNT     = 137 (3288)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3288,
> +		.height = 6096,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 2592,
> +		.window_height = 1968,
> +		.pixel_clock = 80000000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 399
> +		},
> +		.max_exp = 6092,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0xFE },
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode_648x492_5fps */
> +static struct et8ek8_reglist mode_648x492_5fps = {
> +/* (without the +1)
> + * SPCK       = 13.3333333333333 MHz
> + * CCP2       = 53.3333333333333 MHz
> + * VCO        = 640 MHz
> + * VCOUNT     = 84 (2016)
> + * HCOUNT     = 221 (5304)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 200
> + * VCO_DIV    = 5
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 1
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 5304,
> +		.height = 504,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 648,
> +		.window_height = 492,
> +		.pixel_clock = 13333333,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 499
> +		},
> +		.max_exp = 500,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x57 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode3_4VGA_1296x984_5fps */
> +static struct et8ek8_reglist mode3_4vga_1296x984_5fps = {
> +/* (without the +1)
> + * SPCK       = 49.4 MHz
> + * CCP2       = 395.2 MHz
> + * VCO        = 790.4 MHz
> + * VCOUNT     = 250 (6000)
> + * HCOUNT     = 137 (3288)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 247
> + * VCO_DIV    = 1
> + * SPCK_DIV   = 7
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3288,
> +		.height = 3000,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 1296,
> +		.window_height = 984,
> +		.pixel_clock = 49400000,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 501
> +		},
> +		.max_exp = 2996,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x7B },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x17 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0xFA },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +/* Mode_4VGA_1296x984_25fps_DPCM10-8 */
> +static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = {
> +/* (without the +1)
> + * SPCK       = 84.2666666666667 MHz
> + * CCP2       = 505.6 MHz
> + * VCO        = 505.6 MHz
> + * VCOUNT     = 88 (2112)
> + * HCOUNT     = 133 (3192)
> + * CKREF_DIV  = 2
> + * CKVAR_DIV  = 158
> + * VCO_DIV    = 0
> + * SPCK_DIV   = 5
> + * MRCK_DIV   = 7
> + * LVDSCK_DIV = 0
> + */
> +	.type = ET8EK8_REGLIST_MODE,
> +	.mode = {
> +		.sensor_width = 2592,
> +		.sensor_height = 1968,
> +		.sensor_window_origin_x = 0,
> +		.sensor_window_origin_y = 0,
> +		.sensor_window_width = 2592,
> +		.sensor_window_height = 1968,
> +		.width = 3192,
> +		.height = 1056,
> +		.window_origin_x = 0,
> +		.window_origin_y = 0,
> +		.window_width = 1296,
> +		.window_height = 984,
> +		.pixel_clock = 84266667,
> +		.ext_clock = 9600000,
> +		.timeperframe = {
> +			.numerator = 100,
> +			.denominator = 2500
> +		},
> +		.max_exp = 1052,
> +		/* .max_gain = 0, */
> +		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
> +		.sensitivity = 65536
> +	},
> +	.regs = {
> +		{ ET8EK8_REG_8BIT, 0x1239, 0x4F },
> +		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
> +		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
> +		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
> +		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
> +		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x1222, 0x58 },
> +		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
> +		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
> +		{ ET8EK8_REG_8BIT, 0x125D, 0x83 },
> +		{ ET8EK8_REG_TERM, 0, 0}
> +	}
> +};
> +
> +struct et8ek8_meta_reglist meta_reglist = {
> +	.version = "V14 03-June-2008",
> +	.reglist = {
> +		{ .ptr = &mode1_poweron_mode2_16vga_2592x1968_12_07fps },
> +		{ .ptr = &mode1_16vga_2592x1968_13_12fps_dpcm10_8 },
> +		{ .ptr = &mode3_4vga_1296x984_29_99fps_dpcm10_8 },
> +		{ .ptr = &mode4_svga_864x656_29_88fps },
> +		{ .ptr = &mode5_vga_648x492_29_93fps },
> +		{ .ptr = &mode2_16vga_2592x1968_3_99fps },
> +		{ .ptr = &mode_648x492_5fps },
> +		{ .ptr = &mode3_4vga_1296x984_5fps },
> +		{ .ptr = &mode_4vga_1296x984_25fps_dpcm10_8 },
> +		{ .ptr = NULL }
> +	}
> +};
> diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> new file mode 100644
> index 0000000..9970bff
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> @@ -0,0 +1,96 @@
> +/*
> + * et8ek8.h
> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukkat76@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#ifndef ET8EK8REGS_H
> +#define ET8EK8REGS_H
> +
> +#include <linux/i2c.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/v4l2-subdev.h>
> +
> +struct v4l2_mbus_framefmt;
> +struct v4l2_subdev_pad_mbus_code_enum;
> +
> +struct et8ek8_mode {
> +	/* Physical sensor resolution and current image window */
> +	u16 sensor_width;
> +	u16 sensor_height;
> +	u16 sensor_window_origin_x;
> +	u16 sensor_window_origin_y;
> +	u16 sensor_window_width;
> +	u16 sensor_window_height;
> +
> +	/* Image data coming from sensor (after scaling) */
> +	u16 width;
> +	u16 height;
> +	u16 window_origin_x;
> +	u16 window_origin_y;
> +	u16 window_width;
> +	u16 window_height;
> +
> +	u32 pixel_clock;		/* in Hz */
> +	u32 ext_clock;			/* in Hz */
> +	struct v4l2_fract timeperframe;
> +	u32 max_exp;			/* Maximum exposure value */
> +	u32 pixel_format;		/* V4L2_PIX_FMT_xxx */
> +	u32 sensitivity;		/* 16.16 fixed point */
> +};
> +
> +#define ET8EK8_REG_8BIT			1
> +#define ET8EK8_REG_16BIT		2
> +#define ET8EK8_REG_DELAY		100
> +#define ET8EK8_REG_TERM			0xff
> +struct et8ek8_reg {
> +	u16 type;
> +	u16 reg;			/* 16-bit offset */
> +	u32 val;			/* 8/16/32-bit value */
> +};
> +
> +/* Possible struct smia_reglist types. */
> +#define ET8EK8_REGLIST_STANDBY		0
> +#define ET8EK8_REGLIST_POWERON		1
> +#define ET8EK8_REGLIST_RESUME		2
> +#define ET8EK8_REGLIST_STREAMON		3
> +#define ET8EK8_REGLIST_STREAMOFF	4
> +#define ET8EK8_REGLIST_DISABLED		5
> +
> +#define ET8EK8_REGLIST_MODE		10
> +
> +#define ET8EK8_REGLIST_LSC_ENABLE	100
> +#define ET8EK8_REGLIST_LSC_DISABLE	101
> +#define ET8EK8_REGLIST_ANR_ENABLE	102
> +#define ET8EK8_REGLIST_ANR_DISABLE	103
> +
> +struct et8ek8_reglist {
> +	u32 type;
> +	struct et8ek8_mode mode;
> +	struct et8ek8_reg regs[];
> +};
> +
> +#define ET8EK8_MAX_LEN			32
> +struct et8ek8_meta_reglist {
> +	char version[ET8EK8_MAX_LEN];
> +	union {
> +		struct et8ek8_reglist *ptr;
> +	} reglist[];
> +};
> +
> +extern struct et8ek8_meta_reglist meta_reglist;
> +
> +#endif /* ET8EK8REGS */
>
Pavel Machek Nov. 20, 2016, 10:02 a.m. UTC | #25
Hi!

> Just a few more comments...
> 
> Please check my other review as well. I believe you may have missed the
> comments in between in that one.

Sorry for that. Would you have a link for that email or a copy? (I
can't seem to find it.)

Thanks,
								Pavel
Pavel Machek Nov. 20, 2016, 3:31 p.m. UTC | #26
Hi!

> > +	/* V4L2_CID_EXPOSURE */
> > +	min = et8ek8_exposure_rows_to_us(sensor, 1);
> > +	max = et8ek8_exposure_rows_to_us(sensor,
> > +				sensor->current_reglist->mode.max_exp);
> 
> Haven't I suggested to use lines instead? I vaguely remember doing so...
> this would remove quite some code from the driver.

Lines ... lines ... no, I don't think I understand how to use lines
here. I guess I could switch units from us to rows here...?

Is it good idea? For userspace, microseconds are really a nice
interface, because ... well, that's what photographers are used to
think about (ISO 400, time 1/100). fcam also uses usec internally.

In the current camera code, I do autogain in small resolution, then
use same parameters (gain, time) at higher resolution. I guess I could
do the same with the non-microseconds interface, but then I'd have to
move the microsecond computation into userspace. And userspace is
not really good place to do that, as it does not know (and should not
have to know!) such low level details.

So... can we keep the interface as it is?

Thanks,
									Pavel
Pavel Machek Dec. 13, 2016, 9:05 p.m. UTC | #27
Hi!

I have finally found the old mail you were refering to. Let me go
through it.

> > +/*
> > + * Convert exposure time `us' to rows. Modify `us' to make it to
> > + * correspond to the actual exposure time.
> > + */
> > +static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us)
> 
> Should a driver do something like this to begin with?
> 
> The smiapp driver does use the native unit of exposure (lines) for the
> control and I think the et8ek8 driver should do so as well.
> 
> The HBLANK, VBLANK and PIXEL_RATE controls are used to provide the user with
> enough information to perform the conversion (if necessary).

Well... I believe exposure in usec is preffered format for userspace
to work with (because then it can change resolution and keep camera
settings) but I see kernel code is quite ugly. Let me see what I can do...

> > +	if (ret) {
> > +		dev_warn(dev, "can't get clock-frequency\n");
> > +		return ret;
> > +	}
> > +
> > +	mutex_init(&sensor->power_lock);
> 
> mutex_destroy() should be called on the mutex if probe fails after this and
> in remove().

Ok.

> > +static int __exit et8ek8_remove(struct i2c_client *client)
> > +{
> > +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > +
> > +	if (sensor->power_count) {
> > +		gpiod_set_value(sensor->reset, 0);
> > +		clk_disable_unprepare(sensor->ext_clk);
> 
> How about the regulator? Could you call et8ek8_power_off() instead?

Hmm. Actually if we hit this, it indicates something funny is going
on, no? I guess I'll add WARN_ON there...

> > +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> > @@ -0,0 +1,96 @@
> > +/*
> > + * et8ek8.h
> 
> et8ek8_reg.h

Ok.

Thanks for patience,
								Pavel
Sakari Ailus Dec. 18, 2016, 9:56 p.m. UTC | #28
Hi Pavel,

On Tue, Dec 13, 2016 at 10:05:06PM +0100, Pavel Machek wrote:
> Hi!
> 
> I have finally found the old mail you were refering to. Let me go
> through it.
> 
> > > +/*
> > > + * Convert exposure time `us' to rows. Modify `us' to make it to
> > > + * correspond to the actual exposure time.
> > > + */
> > > +static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us)
> > 
> > Should a driver do something like this to begin with?
> > 
> > The smiapp driver does use the native unit of exposure (lines) for the
> > control and I think the et8ek8 driver should do so as well.
> > 
> > The HBLANK, VBLANK and PIXEL_RATE controls are used to provide the user with
> > enough information to perform the conversion (if necessary).
> 
> Well... I believe exposure in usec is preffered format for userspace
> to work with (because then it can change resolution and keep camera
> settings) but I see kernel code is quite ugly. Let me see what I can do...

That's not so important IMO --- the granularity may matter and there's no
way you can properly communicate that to the user if you use a non-native
unit.

My preference is the native unit, but considering that you've got an
existing user space application and perhaps even more importantly, it's very
unlikely the device would be used elsewhere.

The smiapp driver uses lines. Up to you.

> 
> > > +	if (ret) {
> > > +		dev_warn(dev, "can't get clock-frequency\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	mutex_init(&sensor->power_lock);
> > 
> > mutex_destroy() should be called on the mutex if probe fails after this and
> > in remove().
> 
> Ok.
> 
> > > +static int __exit et8ek8_remove(struct i2c_client *client)
> > > +{
> > > +	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
> > > +	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
> > > +
> > > +	if (sensor->power_count) {
> > > +		gpiod_set_value(sensor->reset, 0);
> > > +		clk_disable_unprepare(sensor->ext_clk);
> > 
> > How about the regulator? Could you call et8ek8_power_off() instead?
> 
> Hmm. Actually if we hit this, it indicates something funny is going
> on, no? I guess I'll add WARN_ON there...

Yes. A WARN_ON() would be good.

> 
> > > +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> > > @@ -0,0 +1,96 @@
> > > +/*
> > > + * et8ek8.h
> > 
> > et8ek8_reg.h
> 
> Ok.
> 
> Thanks for patience,

Same to you! :-)
diff mbox

Patch

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 2669b4b..6d01e15 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -667,6 +667,7 @@  config VIDEO_S5K5BAF
 	  camera sensor with an embedded SoC image signal processor.
 
 source "drivers/media/i2c/smiapp/Kconfig"
+source "drivers/media/i2c/et8ek8/Kconfig"
 
 config VIDEO_S5C73M3
 	tristate "Samsung S5C73M3 sensor support"
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 92773b2..5bc7bbe 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -2,6 +2,7 @@  msp3400-objs	:=	msp3400-driver.o msp3400-kthreads.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
 
 obj-$(CONFIG_VIDEO_SMIAPP)	+= smiapp/
+obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8/
 obj-$(CONFIG_VIDEO_CX25840) += cx25840/
 obj-$(CONFIG_VIDEO_M5MOLS)	+= m5mols/
 obj-y				+= soc_camera/
diff --git a/drivers/media/i2c/et8ek8/Kconfig b/drivers/media/i2c/et8ek8/Kconfig
new file mode 100644
index 0000000..1439936
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/Kconfig
@@ -0,0 +1,6 @@ 
+config VIDEO_ET8EK8
+	tristate "ET8EK8 camera sensor support"
+	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	---help---
+	  This is a driver for the Toshiba ET8EK8 5 MP camera sensor.
+	  It is used for example in Nokia N900 (RX-51).
diff --git a/drivers/media/i2c/et8ek8/Makefile b/drivers/media/i2c/et8ek8/Makefile
new file mode 100644
index 0000000..66d1b7d
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/Makefile
@@ -0,0 +1,2 @@ 
+et8ek8-objs			+= et8ek8_mode.o et8ek8_driver.o
+obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8.o
diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
new file mode 100644
index 0000000..0301e81
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
@@ -0,0 +1,1588 @@ 
+/*
+ * et8ek8_driver.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *          Tuukka Toivonen <tuukkat76@gmail.com>
+ *
+ * Based on code from Toni Leinonen <toni.leinonen@offcode.fi>.
+ *
+ * This driver is based on the Micron MT9T012 camera imager driver
+ * (C) Texas Instruments.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/v4l2-mediabus.h>
+
+#include <media/media-entity.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "et8ek8_reg.h"
+
+#define ET8EK8_NAME		"et8ek8"
+#define ET8EK8_PRIV_MEM_SIZE	128
+#define ET8EK8_MAX_MSG		48
+
+struct et8ek8_sensor {
+	struct v4l2_subdev subdev;
+	struct media_pad pad;
+	struct v4l2_mbus_framefmt format;
+	struct gpio_desc *reset;
+	struct regulator *vana;
+	struct clk *ext_clk;
+	u32 xclk_freq;
+
+	u16 version;
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *pixel_rate;
+	struct et8ek8_reglist *current_reglist;
+
+	u8 priv_mem[ET8EK8_PRIV_MEM_SIZE];
+
+	struct mutex power_lock;
+	int power_count;
+};
+
+#define to_et8ek8_sensor(sd)	container_of(sd, struct et8ek8_sensor, subdev)
+
+enum et8ek8_versions {
+	ET8EK8_REV_1 = 0x0001,
+	ET8EK8_REV_2,
+};
+
+/*
+ * This table describes what should be written to the sensor register
+ * for each gain value. The gain(index in the table) is in terms of
+ * 0.1EV, i.e. 10 indexes in the table give 2 time more gain [0] in
+ * the *analog gain, [1] in the digital gain
+ *
+ * Analog gain [dB] = 20*log10(regvalue/32); 0x20..0x100
+ */
+static struct et8ek8_gain {
+	u16 analog;
+	u16 digital;
+} const et8ek8_gain_table[] = {
+	{ 32,    0},  /* x1 */
+	{ 34,    0},
+	{ 37,    0},
+	{ 39,    0},
+	{ 42,    0},
+	{ 45,    0},
+	{ 49,    0},
+	{ 52,    0},
+	{ 56,    0},
+	{ 60,    0},
+	{ 64,    0},  /* x2 */
+	{ 69,    0},
+	{ 74,    0},
+	{ 79,    0},
+	{ 84,    0},
+	{ 91,    0},
+	{ 97,    0},
+	{104,    0},
+	{111,    0},
+	{119,    0},
+	{128,    0},  /* x4 */
+	{137,    0},
+	{147,    0},
+	{158,    0},
+	{169,    0},
+	{181,    0},
+	{194,    0},
+	{208,    0},
+	{223,    0},
+	{239,    0},
+	{256,    0},  /* x8 */
+	{256,   73},
+	{256,  152},
+	{256,  236},
+	{256,  327},
+	{256,  424},
+	{256,  528},
+	{256,  639},
+	{256,  758},
+	{256,  886},
+	{256, 1023},  /* x16 */
+};
+
+/* Register definitions */
+#define REG_REVISION_NUMBER_L	0x1200
+#define REG_REVISION_NUMBER_H	0x1201
+
+#define PRIV_MEM_START_REG	0x0008
+#define PRIV_MEM_WIN_SIZE	8
+
+#define ET8EK8_I2C_DELAY	3	/* msec delay b/w accesses */
+
+#define USE_CRC			1
+
+/*
+ * Register access helpers
+ *
+ * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int et8ek8_i2c_read_reg(struct i2c_client *client, u16 data_length,
+			       u16 reg, u32 *val)
+{
+	int r;
+	struct i2c_msg msg;
+	unsigned char data[4];
+
+	if (!client->adapter)
+		return -ENODEV;
+	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
+		return -EINVAL;
+
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.len = 2;
+	msg.buf = data;
+
+	/* high byte goes out first */
+	data[0] = (u8) (reg >> 8);
+	data[1] = (u8) (reg & 0xff);
+	r = i2c_transfer(client->adapter, &msg, 1);
+	if (r < 0)
+		goto err;
+
+	msg.len = data_length;
+	msg.flags = I2C_M_RD;
+	r = i2c_transfer(client->adapter, &msg, 1);
+	if (r < 0)
+		goto err;
+
+	*val = 0;
+	/* high byte comes first */
+	if (data_length == ET8EK8_REG_8BIT)
+		*val = data[0];
+	else
+		*val = (data[0] << 8) + data[1];
+
+	return 0;
+
+err:
+	dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r);
+
+	return r;
+}
+
+static void et8ek8_i2c_create_msg(struct i2c_client *client, u16 len, u16 reg,
+				  u32 val, struct i2c_msg *msg,
+				  unsigned char *buf)
+{
+	msg->addr = client->addr;
+	msg->flags = 0; /* Write */
+	msg->len = 2 + len;
+	msg->buf = buf;
+
+	/* high byte goes out first */
+	buf[0] = (u8) (reg >> 8);
+	buf[1] = (u8) (reg & 0xff);
+
+	switch (len) {
+	case ET8EK8_REG_8BIT:
+		buf[2] = (u8) (val) & 0xff;
+		break;
+	case ET8EK8_REG_16BIT:
+		buf[2] = (u8) (val >> 8) & 0xff;
+		buf[3] = (u8) (val & 0xff);
+		break;
+	default:
+		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
+			  __func__);
+	}
+}
+
+/*
+ * A buffered write method that puts the wanted register write
+ * commands in a message list and passes the list to the i2c framework
+ */
+static int et8ek8_i2c_buffered_write_regs(struct i2c_client *client,
+					  const struct et8ek8_reg *wnext,
+					  int cnt)
+{
+	struct i2c_msg msg[ET8EK8_MAX_MSG];
+	unsigned char data[ET8EK8_MAX_MSG][6];
+	int wcnt = 0;
+	u16 reg, data_length;
+	u32 val;
+
+	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
+		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
+		return -EINVAL;
+	}
+
+	/* Create new write messages for all writes */
+	while (wcnt < cnt) {
+		data_length = wnext->type;
+		reg = wnext->reg;
+		val = wnext->val;
+		wnext++;
+
+		et8ek8_i2c_create_msg(client, data_length, reg,
+				    val, &msg[wcnt], &data[wcnt][0]);
+
+		/* Update write count */
+		wcnt++;
+	}
+
+	/* Now we send everything ... */
+	return i2c_transfer(client->adapter, msg, wcnt);
+}
+
+/*
+ * Write a list of registers to i2c device.
+ *
+ * The list of registers is terminated by ET8EK8_REG_TERM.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int et8ek8_i2c_write_regs(struct i2c_client *client,
+				 const struct et8ek8_reg *regs)
+{
+	int r, cnt = 0;
+	const struct et8ek8_reg *next;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	if (!regs)
+		return -EINVAL;
+
+	/* Initialize list pointers to the start of the list */
+	next = regs;
+
+	do {
+		/*
+		 * We have to go through the list to figure out how
+		 * many regular writes we have in a row
+		 */
+		while (next->type != ET8EK8_REG_TERM &&
+		       next->type != ET8EK8_REG_DELAY) {
+			/*
+			 * Here we check that the actual length fields
+			 * are valid
+			 */
+			if (WARN(next->type != ET8EK8_REG_8BIT &&
+				 next->type != ET8EK8_REG_16BIT,
+				 "Invalid type = %d", next->type)) {
+				return -EINVAL;
+			}
+			/*
+			 * Increment count of successive writes and
+			 * read pointer
+			 */
+			cnt++;
+			next++;
+		}
+
+		/* Now we start writing ... */
+		r = et8ek8_i2c_buffered_write_regs(client, regs, cnt);
+
+		/* ... and then check that everything was OK */
+		if (r < 0) {
+			dev_err(&client->dev, "i2c transfer error!\n");
+			return r;
+		}
+
+		/*
+		 * If we ran into a sleep statement when going through
+		 * the list, this is where we snooze for the required time
+		 */
+		if (next->type == ET8EK8_REG_DELAY) {
+			msleep(next->val);
+			/*
+			 * ZZZ ...
+			 * Update list pointers and cnt and start over ...
+			 */
+			next++;
+			regs = next;
+			cnt = 0;
+		}
+	} while (next->type != ET8EK8_REG_TERM);
+
+	return 0;
+}
+
+/*
+ * Write to a 8/16-bit register.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length,
+				u16 reg, u32 val)
+{
+	int r;
+	struct i2c_msg msg;
+	unsigned char data[6];
+
+	if (!client->adapter)
+		return -ENODEV;
+	if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT)
+		return -EINVAL;
+
+	et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data);
+
+	r = i2c_transfer(client->adapter, &msg, 1);
+	if (r < 0)
+		dev_err(&client->dev,
+			"wrote 0x%x to offset 0x%x error %d\n", val, reg, r);
+	else
+		r = 0; /* on success i2c_transfer() returns messages trasfered */
+
+	return r;
+}
+
+static struct et8ek8_reglist *et8ek8_reglist_find_type(
+		struct et8ek8_meta_reglist *meta,
+		u16 type)
+{
+	struct et8ek8_reglist **next = &meta->reglist[0].ptr;
+
+	while (*next) {
+		if ((*next)->type == type)
+			return *next;
+
+		next++;
+	}
+
+	return NULL;
+}
+
+static int et8ek8_i2c_reglist_find_write(struct i2c_client *client,
+					 struct et8ek8_meta_reglist *meta,
+					 u16 type)
+{
+	struct et8ek8_reglist *reglist;
+
+	reglist = et8ek8_reglist_find_type(meta, type);
+	if (!reglist)
+		return -EINVAL;
+
+	return et8ek8_i2c_write_regs(client, reglist->regs);
+}
+
+static struct et8ek8_reglist **et8ek8_reglist_first(
+		struct et8ek8_meta_reglist *meta)
+{
+	return &meta->reglist[0].ptr;
+}
+
+static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist,
+				   struct v4l2_mbus_framefmt *fmt)
+{
+	fmt->width = reglist->mode.window_width;
+	fmt->height = reglist->mode.window_height;
+
+	if (reglist->mode.pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
+		fmt->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
+	else
+		fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+}
+
+static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt(
+		struct et8ek8_meta_reglist *meta,
+		struct v4l2_mbus_framefmt *fmt)
+{
+	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
+	struct et8ek8_reglist *best_match = NULL;
+	struct et8ek8_reglist *best_other = NULL;
+	struct v4l2_mbus_framefmt format;
+	unsigned int max_dist_match = (unsigned int)-1;
+	unsigned int max_dist_other = (unsigned int)-1;
+
+	/*
+	 * Find the mode with the closest image size. The distance between
+	 * image sizes is the size in pixels of the non-overlapping regions
+	 * between the requested size and the frame-specified size.
+	 *
+	 * Store both the closest mode that matches the requested format, and
+	 * the closest mode for all other formats. The best match is returned
+	 * if found, otherwise the best mode with a non-matching format is
+	 * returned.
+	 */
+	for (; *list; list++) {
+		unsigned int dist;
+
+		if ((*list)->type != ET8EK8_REGLIST_MODE)
+			continue;
+
+		et8ek8_reglist_to_mbus(*list, &format);
+
+		dist = min(fmt->width, format.width)
+		     * min(fmt->height, format.height);
+		dist = format.width * format.height
+		     + fmt->width * fmt->height - 2 * dist;
+
+
+		if (fmt->code == format.code) {
+			if (dist < max_dist_match || !best_match) {
+				best_match = *list;
+				max_dist_match = dist;
+			}
+		} else {
+			if (dist < max_dist_other || !best_other) {
+				best_other = *list;
+				max_dist_other = dist;
+			}
+		}
+	}
+
+	return best_match ? best_match : best_other;
+}
+
+#define TIMEPERFRAME_AVG_FPS(t)						\
+	(((t).denominator + ((t).numerator >> 1)) / (t).numerator)
+
+static struct et8ek8_reglist *et8ek8_reglist_find_mode_ival(
+		struct et8ek8_meta_reglist *meta,
+		struct et8ek8_reglist *current_reglist,
+		struct v4l2_fract *timeperframe)
+{
+	int fps = TIMEPERFRAME_AVG_FPS(*timeperframe);
+	struct et8ek8_reglist **list = et8ek8_reglist_first(meta);
+	struct et8ek8_mode *current_mode = &current_reglist->mode;
+
+	for (; *list; list++) {
+		struct et8ek8_mode *mode = &(*list)->mode;
+
+		if ((*list)->type != ET8EK8_REGLIST_MODE)
+			continue;
+
+		if (mode->window_width != current_mode->window_width ||
+		    mode->window_height != current_mode->window_height)
+			continue;
+
+		if (TIMEPERFRAME_AVG_FPS(mode->timeperframe) == fps)
+			return *list;
+	}
+
+	return NULL;
+}
+
+static int et8ek8_reglist_cmp(const void *a, const void *b)
+{
+	const struct et8ek8_reglist **list1 = (const struct et8ek8_reglist **)a,
+		**list2 = (const struct et8ek8_reglist **)b;
+
+	/* Put real modes in the beginning. */
+	if ((*list1)->type == ET8EK8_REGLIST_MODE &&
+	    (*list2)->type != ET8EK8_REGLIST_MODE)
+		return -1;
+	if ((*list1)->type != ET8EK8_REGLIST_MODE &&
+	    (*list2)->type == ET8EK8_REGLIST_MODE)
+		return 1;
+
+	/* Descending width. */
+	if ((*list1)->mode.window_width > (*list2)->mode.window_width)
+		return -1;
+	if ((*list1)->mode.window_width < (*list2)->mode.window_width)
+		return 1;
+
+	if ((*list1)->mode.window_height > (*list2)->mode.window_height)
+		return -1;
+	if ((*list1)->mode.window_height < (*list2)->mode.window_height)
+		return 1;
+
+	return 0;
+}
+
+static int et8ek8_reglist_import(struct i2c_client *client,
+				 struct et8ek8_meta_reglist *meta)
+{
+	int nlists = 0, i;
+
+	dev_info(&client->dev, "meta_reglist version %s\n", meta->version);
+
+	while (meta->reglist[nlists].ptr)
+		nlists++;
+
+	if (!nlists)
+		return -EINVAL;
+
+	sort(&meta->reglist[0].ptr, nlists, sizeof(meta->reglist[0].ptr),
+	     et8ek8_reglist_cmp, NULL);
+
+	i = nlists;
+	nlists = 0;
+
+	while (i--) {
+		struct et8ek8_reglist *list;
+
+		list = meta->reglist[nlists].ptr;
+
+		dev_dbg(&client->dev,
+		       "%s: type %d\tw %d\th %d\tfmt %x\tival %d/%d\tptr %p\n",
+		       __func__,
+		       list->type,
+		       list->mode.window_width, list->mode.window_height,
+		       list->mode.pixel_format,
+		       list->mode.timeperframe.numerator,
+		       list->mode.timeperframe.denominator,
+		       (void *)meta->reglist[nlists].ptr);
+
+		nlists++;
+	}
+
+	return 0;
+}
+
+typedef unsigned int fixpoint8; /* .8 fixed point format. */
+
+/*
+ * Return time of one row in microseconds
+ * If the sensor is not set to any mode, return zero.
+ */
+fixpoint8 et8ek8_get_row_time(struct et8ek8_sensor *sensor)
+{
+	unsigned int clock;	/* Pixel clock in Hz>>10 fixed point */
+	fixpoint8 rt;	/* Row time in .8 fixed point */
+
+	if (!sensor->current_reglist)
+		return 0;
+
+	clock = sensor->current_reglist->mode.pixel_clock;
+	clock = (clock + (1 << 9)) >> 10;
+	rt = sensor->current_reglist->mode.width * (1000000 >> 2);
+	rt = (rt + (clock >> 1)) / clock;
+
+	return rt;
+}
+
+/*
+ * Convert exposure time `us' to rows. Modify `us' to make it to
+ * correspond to the actual exposure time.
+ */
+static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us)
+{
+	unsigned int rows;	/* Exposure value as written to HW (ie. rows) */
+	fixpoint8 rt;	/* Row time in .8 fixed point */
+
+	/* Assume that the maximum exposure time is at most ~8 s,
+	 * and the maximum width (with blanking) ~8000 pixels.
+	 * The formula here is in principle as simple as
+	 *    rows = exptime / 1e6 / width * pixel_clock
+	 * but to get accurate results while coping with value ranges,
+	 * have to do some fixed point math.
+	 */
+
+	rt = et8ek8_get_row_time(sensor);
+	rows = ((*us << 8) + (rt >> 1)) / rt;
+
+	if (rows > sensor->current_reglist->mode.max_exp)
+		rows = sensor->current_reglist->mode.max_exp;
+
+	/* Set the exposure time to the rounded value */
+	*us = (rt * rows + (1 << 7)) >> 8;
+
+	return rows;
+}
+
+/*
+ * Convert exposure time in rows to microseconds
+ */
+static int et8ek8_exposure_rows_to_us(struct et8ek8_sensor *sensor, int rows)
+{
+	return (et8ek8_get_row_time(sensor) * rows + (1 << 7)) >> 8;
+}
+
+/* Called to change the V4L2 gain control value. This function
+ * rounds and clamps the given value and updates the V4L2 control value.
+ * If power is on, also updates the sensor analog and digital gains.
+ * gain is in 0.1 EV (exposure value) units.
+ */
+static int et8ek8_set_gain(struct et8ek8_sensor *sensor, s32 gain)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+	struct et8ek8_gain new;
+	int r;
+
+	new = et8ek8_gain_table[gain];
+
+	/* FIXME: optimise I2C writes! */
+	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
+				0x124a, new.analog >> 8);
+	if (r)
+		return r;
+	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
+				0x1249, new.analog & 0xff);
+	if (r)
+		return r;
+
+	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
+				0x124d, new.digital >> 8);
+	if (r)
+		return r;
+	r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT,
+				0x124c, new.digital & 0xff);
+
+	return r;
+}
+
+static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+	int cbh_mode, cbv_mode, tp_mode, din_sw, r1420, rval;
+
+	/* Values for normal mode */
+	cbh_mode = 0;
+	cbv_mode = 0;
+	tp_mode  = 0;
+	din_sw   = 0x00;
+	r1420    = 0xF0;
+
+	if (mode) {
+		/* Test pattern mode */
+		if (mode < 5) {
+			cbh_mode = 1;
+			cbv_mode = 1;
+			tp_mode  = mode + 3;
+		} else {
+			cbh_mode = 0;
+			cbv_mode = 0;
+			tp_mode  = mode - 4 + 3;
+		}
+
+		din_sw   = 0x01;
+		r1420    = 0xE0;
+	}
+
+	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x111B,
+				    tp_mode << 4);
+	if (rval)
+		return rval;
+
+	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1121,
+				    cbh_mode << 7);
+	if (rval)
+		return rval;
+
+	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124,
+				    cbv_mode << 7);
+	if (rval)
+		return rval;		
+
+	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x112C, din_sw);
+	if (rval)
+		return rval;
+
+	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420);
+	return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 controls
+ */
+
+static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct et8ek8_sensor *sensor =
+		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+	int uninitialized_var(rows);
+
+	if (ctrl->id == V4L2_CID_EXPOSURE)
+		rows = et8ek8_exposure_us_to_rows(sensor, (u32 *)&ctrl->val);
+
+	switch (ctrl->id) {
+	case V4L2_CID_GAIN:
+		return et8ek8_set_gain(sensor, ctrl->val);
+
+	case V4L2_CID_EXPOSURE:
+		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
+					    swab16(rows));
+
+	case V4L2_CID_TEST_PATTERN:
+		return et8ek8_set_test_pattern(sensor, ctrl->val);
+
+	case V4L2_CID_PIXEL_RATE:
+		/* For v4l2_ctrl_s_ctrl_int64() used internally. */
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = {
+	.s_ctrl = et8ek8_set_ctrl,
+};
+
+static const char * const et8ek8_test_pattern_menu[] = {
+	"Normal",
+	"Vertical colorbar",
+	"Horizontal colorbar",
+	"Scale",
+	"Ramp",
+	"Small vertical colorbar",
+	"Small horizontal colorbar",
+	"Small scale",
+	"Small ramp",
+};
+
+static int et8ek8_init_controls(struct et8ek8_sensor *sensor)
+{
+	u32 min, max;
+
+	v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4);
+
+	/* V4L2_CID_GAIN */
+	v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
+			  V4L2_CID_GAIN, 0, ARRAY_SIZE(et8ek8_gain_table) - 1,
+			  1, 0);
+
+	/* V4L2_CID_EXPOSURE */
+	min = et8ek8_exposure_rows_to_us(sensor, 1);
+	max = et8ek8_exposure_rows_to_us(sensor,
+				sensor->current_reglist->mode.max_exp);
+	sensor->exposure =
+		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
+				  V4L2_CID_EXPOSURE, min, max, min, max);
+
+	/* V4L2_CID_PIXEL_RATE */
+	sensor->pixel_rate =
+		v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
+		V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
+
+	/* V4L2_CID_TEST_PATTERN */
+	v4l2_ctrl_new_std_menu_items(&sensor->ctrl_handler,
+				     &et8ek8_ctrl_ops, V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(et8ek8_test_pattern_menu) - 1,
+				     0, 0, et8ek8_test_pattern_menu);
+
+	if (sensor->ctrl_handler.error)
+		return sensor->ctrl_handler.error;
+
+	sensor->subdev.ctrl_handler = &sensor->ctrl_handler;
+
+	return 0;
+}
+
+static void et8ek8_update_controls(struct et8ek8_sensor *sensor)
+{
+	struct v4l2_ctrl *ctrl = sensor->exposure;
+	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
+	u32 min, max, pixel_rate;
+	static const int S = 8;
+
+	min = et8ek8_exposure_rows_to_us(sensor, 1);
+	max = et8ek8_exposure_rows_to_us(sensor, mode->max_exp);
+
+	/*
+	 * Calculate average pixel clock per line. Assume buffers can spread
+	 * the data over horizontal blanking time. Rounding upwards.
+	 * Formula taken from stock Nokia N900 kernel.
+	 */
+	pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width;
+	pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width;
+
+	v4l2_ctrl_lock(ctrl);
+	ctrl->minimum = min;
+	ctrl->maximum = max;
+	ctrl->step = min;
+	ctrl->default_value = max;
+	ctrl->val = max;
+	ctrl->cur.val = max;
+	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
+	v4l2_ctrl_unlock(ctrl);
+}
+
+static int et8ek8_configure(struct et8ek8_sensor *sensor)
+{
+	struct v4l2_subdev *subdev = &sensor->subdev;
+	struct i2c_client *client = v4l2_get_subdevdata(subdev);
+	int rval;
+
+	rval = et8ek8_i2c_write_regs(client, sensor->current_reglist->regs);
+	if (rval)
+		goto fail;
+
+	/* Controls set while the power to the sensor is turned off are saved
+	 * but not applied to the hardware. Now that we're about to start
+	 * streaming apply all the current values to the hardware.
+	 */
+	rval = v4l2_ctrl_handler_setup(&sensor->ctrl_handler);
+	if (rval)
+		goto fail;
+
+	return 0;
+
+fail:
+	dev_err(&client->dev, "sensor configuration failed\n");
+
+	return rval;
+}
+
+static int et8ek8_stream_on(struct et8ek8_sensor *sensor)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+
+	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0xb0);
+}
+
+static int et8ek8_stream_off(struct et8ek8_sensor *sensor)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+
+	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0x30);
+}
+
+static int et8ek8_s_stream(struct v4l2_subdev *subdev, int streaming)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	int ret;
+
+	if (!streaming)
+		return et8ek8_stream_off(sensor);
+
+	ret = et8ek8_configure(sensor);
+	if (ret < 0)
+		return ret;
+
+	return et8ek8_stream_on(sensor);
+}
+
+/* --------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+static int et8ek8_power_off(struct et8ek8_sensor *sensor)
+{
+	gpiod_set_value(sensor->reset, 0);
+	udelay(1);
+
+	clk_disable_unprepare(sensor->ext_clk);
+
+	return regulator_disable(sensor->vana);
+}
+
+static int et8ek8_power_on(struct et8ek8_sensor *sensor)
+{
+	struct v4l2_subdev *subdev = &sensor->subdev;
+	struct i2c_client *client = v4l2_get_subdevdata(subdev);
+	unsigned int xclk_freq;
+	int val, rval;
+
+	rval = regulator_enable(sensor->vana);
+	if (rval) {
+		dev_err(&client->dev, "failed to enable vana regulator\n");
+		return rval;
+	}
+
+	if (sensor->current_reglist)
+		xclk_freq = sensor->current_reglist->mode.ext_clock;
+	else
+		xclk_freq = sensor->xclk_freq;
+
+	rval = clk_set_rate(sensor->ext_clk, xclk_freq);
+	if (rval < 0) {
+		dev_err(&client->dev, "unable to set extclk clock freq to %u\n",
+			xclk_freq);
+		goto out;
+	}
+	rval = clk_prepare_enable(sensor->ext_clk);
+	if (rval < 0) {
+		dev_err(&client->dev, "failed to enable extclk\n");
+		goto out;
+	}
+
+	if (rval)
+		goto out;
+
+	udelay(10); /* I wish this is a good value */
+
+	gpiod_set_value(sensor->reset, 1);
+
+	msleep(5000 * 1000 / xclk_freq + 1); /* Wait 5000 cycles */
+
+	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
+					     ET8EK8_REGLIST_POWERON);
+	if (rval)
+		goto out;
+
+#ifdef USE_CRC
+	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, 0x1263, &val);
+	if (rval)
+		goto out;
+#if USE_CRC /* TODO get crc setting from DT */
+	val |= BIT(4);
+#else
+	val &= ~BIT(4);
+#endif
+	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1263, val);
+	if (rval)
+		goto out;
+#endif
+
+out:
+	if (rval)
+		et8ek8_power_off(sensor);
+
+	return rval;
+}
+
+/* --------------------------------------------------------------------------
+ * V4L2 subdev video operations
+ */
+#define MAX_FMTS 4
+static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev,
+				 struct v4l2_subdev_pad_config *cfg,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct et8ek8_reglist **list =
+			et8ek8_reglist_first(&meta_reglist);
+	u32 pixelformat[MAX_FMTS];
+	int npixelformat = 0;
+
+	if (code->index >= MAX_FMTS)
+		return -EINVAL;
+
+	for (; *list; list++) {
+		struct et8ek8_mode *mode = &(*list)->mode;
+		int i;
+
+		if ((*list)->type != ET8EK8_REGLIST_MODE)
+			continue;
+
+		for (i = 0; i < npixelformat; i++) {
+			if (pixelformat[i] == mode->pixel_format)
+				break;
+		}
+		if (i != npixelformat)
+			continue;
+
+		if (code->index == npixelformat) {
+			if (mode->pixel_format == V4L2_PIX_FMT_SGRBG10DPCM8)
+				code->code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8;
+			else
+				code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+			return 0;
+		}
+
+		pixelformat[npixelformat] = mode->pixel_format;
+		npixelformat++;
+	}
+
+	return -EINVAL;
+}
+
+static int et8ek8_enum_frame_size(struct v4l2_subdev *subdev,
+				  struct v4l2_subdev_pad_config *cfg,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct et8ek8_reglist **list =
+			et8ek8_reglist_first(&meta_reglist);
+	struct v4l2_mbus_framefmt format;
+	int cmp_width = INT_MAX;
+	int cmp_height = INT_MAX;
+	int index = fse->index;
+
+	for (; *list; list++) {
+		if ((*list)->type != ET8EK8_REGLIST_MODE)
+			continue;
+
+		et8ek8_reglist_to_mbus(*list, &format);
+		if (fse->code != format.code)
+			continue;
+
+		/* Assume that the modes are grouped by frame size. */
+		if (format.width == cmp_width && format.height == cmp_height)
+			continue;
+
+		cmp_width = format.width;
+		cmp_height = format.height;
+
+		if (index-- == 0) {
+			fse->min_width = format.width;
+			fse->min_height = format.height;
+			fse->max_width = format.width;
+			fse->max_height = format.height;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int et8ek8_enum_frame_ival(struct v4l2_subdev *subdev,
+				  struct v4l2_subdev_pad_config *cfg,
+				  struct v4l2_subdev_frame_interval_enum *fie)
+{
+	struct et8ek8_reglist **list =
+			et8ek8_reglist_first(&meta_reglist);
+	struct v4l2_mbus_framefmt format;
+	int index = fie->index;
+
+	for (; *list; list++) {
+		struct et8ek8_mode *mode = &(*list)->mode;
+
+		if ((*list)->type != ET8EK8_REGLIST_MODE)
+			continue;
+
+		et8ek8_reglist_to_mbus(*list, &format);
+		if (fie->code != format.code)
+			continue;
+
+		if (fie->width != format.width || fie->height != format.height)
+			continue;
+
+		if (index-- == 0) {
+			fie->interval = mode->timeperframe;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static struct v4l2_mbus_framefmt *
+__et8ek8_get_pad_format(struct et8ek8_sensor *sensor,
+			struct v4l2_subdev_pad_config *cfg,
+			unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_get_try_format(&sensor->subdev, cfg, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &sensor->format;
+	default:
+		return NULL;
+	}
+}
+
+static int et8ek8_get_pad_format(struct v4l2_subdev *subdev,
+				 struct v4l2_subdev_pad_config *cfg,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	struct v4l2_mbus_framefmt *format;
+
+	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
+	if (!format)
+		return -EINVAL;
+
+	fmt->format = *format;
+
+	return 0;
+}
+
+static int et8ek8_set_pad_format(struct v4l2_subdev *subdev,
+				 struct v4l2_subdev_pad_config *cfg,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	struct v4l2_mbus_framefmt *format;
+	struct et8ek8_reglist *reglist;
+
+	format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which);
+	if (!format)
+		return -EINVAL;
+
+	reglist = et8ek8_reglist_find_mode_fmt(&meta_reglist, &fmt->format);
+	et8ek8_reglist_to_mbus(reglist, &fmt->format);
+	*format = fmt->format;
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+		sensor->current_reglist = reglist;
+		et8ek8_update_controls(sensor);
+	}
+
+	return 0;
+}
+
+static int et8ek8_get_frame_interval(struct v4l2_subdev *subdev,
+				     struct v4l2_subdev_frame_interval *fi)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+
+	memset(fi, 0, sizeof(*fi));
+	fi->interval = sensor->current_reglist->mode.timeperframe;
+
+	return 0;
+}
+
+static int et8ek8_set_frame_interval(struct v4l2_subdev *subdev,
+				     struct v4l2_subdev_frame_interval *fi)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	struct et8ek8_reglist *reglist;
+
+	reglist = et8ek8_reglist_find_mode_ival(&meta_reglist,
+						sensor->current_reglist,
+						&fi->interval);
+
+	if (!reglist)
+		return -EINVAL;
+
+	if (sensor->current_reglist->mode.ext_clock != reglist->mode.ext_clock)
+		return -EINVAL;
+
+	sensor->current_reglist = reglist;
+	et8ek8_update_controls(sensor);
+
+	return 0;
+}
+
+static int et8ek8_g_priv_mem(struct v4l2_subdev *subdev)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	struct i2c_client *client = v4l2_get_subdevdata(subdev);
+	unsigned int length = ET8EK8_PRIV_MEM_SIZE;
+	unsigned int offset = 0;
+	u8 *ptr  = sensor->priv_mem;
+	int rval = 0;
+
+	/* Read the EEPROM window-by-window, each window 8 bytes */
+	do {
+		u8 buffer[PRIV_MEM_WIN_SIZE];
+		struct i2c_msg msg;
+		int bytes, i;
+		int ofs;
+
+		/* Set the current window */
+		rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x0001,
+					    0xe0 | (offset >> 3));
+		if (rval < 0)
+			return rval;
+
+		/* Wait for status bit */
+		for (i = 0; i < 1000; ++i) {
+			u32 status;
+
+			rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
+						   0x0003, &status);
+			if (rval < 0)
+				return rval;
+			if (!(status & 0x08))
+				break;
+			usleep_range(1000, 2000);
+		};
+
+		if (i == 1000)
+			return -EIO;
+
+		/* Read window, 8 bytes at once, and copy to user space */
+		ofs = offset & 0x07;	/* Offset within this window */
+		bytes = length + ofs > 8 ? 8-ofs : length;
+		msg.addr = client->addr;
+		msg.flags = 0;
+		msg.len = 2;
+		msg.buf = buffer;
+		ofs += PRIV_MEM_START_REG;
+		buffer[0] = (u8)(ofs >> 8);
+		buffer[1] = (u8)(ofs & 0xFF);
+
+		rval = i2c_transfer(client->adapter, &msg, 1);
+		if (rval < 0)
+			return rval;
+
+		mdelay(ET8EK8_I2C_DELAY);
+		msg.addr = client->addr;
+		msg.len = bytes;
+		msg.flags = I2C_M_RD;
+		msg.buf = buffer;
+		memset(buffer, 0, sizeof(buffer));
+
+		rval = i2c_transfer(client->adapter, &msg, 1);
+		if (rval < 0)
+			return rval;
+
+		rval = 0;
+		memcpy(ptr, buffer, bytes);
+
+		length -= bytes;
+		offset += bytes;
+		ptr += bytes;
+	} while (length > 0);
+
+	return rval;
+}
+
+static int et8ek8_dev_init(struct v4l2_subdev *subdev)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	struct i2c_client *client = v4l2_get_subdevdata(subdev);
+	int rval, rev_l, rev_h;
+
+	rval = et8ek8_power_on(sensor);
+	if (rval) {
+		dev_err(&client->dev, "could not power on\n");
+		return rval;
+	}
+
+	rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
+				   REG_REVISION_NUMBER_L, &rev_l);
+	if (!rval)
+		rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT,
+					   REG_REVISION_NUMBER_H, &rev_h);
+	if (rval) {
+		dev_err(&client->dev, "no et8ek8 sensor detected\n");
+		goto out_poweroff;
+	}
+
+	sensor->version = (rev_h << 8) + rev_l;
+	if (sensor->version != ET8EK8_REV_1 && sensor->version != ET8EK8_REV_2)
+		dev_info(&client->dev,
+			 "unknown version 0x%x detected, continuing anyway\n",
+			 sensor->version);
+
+	rval = et8ek8_reglist_import(client, &meta_reglist);
+	if (rval) {
+		dev_err(&client->dev,
+			"invalid register list %s, import failed\n",
+			ET8EK8_NAME);
+		goto out_poweroff;
+	}
+
+	sensor->current_reglist = et8ek8_reglist_find_type(&meta_reglist,
+							   ET8EK8_REGLIST_MODE);
+	if (!sensor->current_reglist) {
+		dev_err(&client->dev,
+			"invalid register list %s, no mode found\n",
+			ET8EK8_NAME);
+		rval = -ENODEV;
+		goto out_poweroff;
+	}
+
+	et8ek8_reglist_to_mbus(sensor->current_reglist, &sensor->format);
+
+	rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist,
+					     ET8EK8_REGLIST_POWERON);
+	if (rval) {
+		dev_err(&client->dev,
+			"invalid register list %s, no POWERON mode found\n",
+			ET8EK8_NAME);
+		goto out_poweroff;
+	}
+	rval = et8ek8_stream_on(sensor); /* Needed to be able to read EEPROM */
+	if (rval)
+		goto out_poweroff;
+	rval = et8ek8_g_priv_mem(subdev);
+	if (rval)
+		dev_warn(&client->dev,
+			"can not read OTP (EEPROM) memory from sensor\n");
+	rval = et8ek8_stream_off(sensor);
+	if (rval)
+		goto out_poweroff;
+
+	rval = et8ek8_power_off(sensor);
+	if (rval)
+		goto out_poweroff;
+
+	return 0;
+
+out_poweroff:
+	et8ek8_power_off(sensor);
+
+	return rval;
+}
+
+/* --------------------------------------------------------------------------
+ * sysfs attributes
+ */
+static ssize_t
+et8ek8_priv_mem_read(struct device *dev, struct device_attribute *attr,
+		     char *buf)
+{
+	struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+
+#if PAGE_SIZE < ET8EK8_PRIV_MEM_SIZE
+#error PAGE_SIZE too small!
+#endif
+
+	memcpy(buf, sensor->priv_mem, ET8EK8_PRIV_MEM_SIZE);
+
+	return ET8EK8_PRIV_MEM_SIZE;
+}
+static DEVICE_ATTR(priv_mem, S_IRUGO, et8ek8_priv_mem_read, NULL);
+
+/* --------------------------------------------------------------------------
+ * V4L2 subdev core operations
+ */
+
+static int
+et8ek8_registered(struct v4l2_subdev *subdev)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	struct i2c_client *client = v4l2_get_subdevdata(subdev);
+	struct v4l2_mbus_framefmt *format;
+	int rval;
+
+	dev_dbg(&client->dev, "registered!");
+
+	rval = device_create_file(&client->dev, &dev_attr_priv_mem);
+	if (rval) {
+		dev_err(&client->dev, "could not register sysfs entry\n");
+		return rval;
+	}
+
+	rval = et8ek8_dev_init(subdev);
+	if (rval)
+		goto err_file;
+
+	rval = et8ek8_init_controls(sensor);
+	if (rval) {
+		dev_err(&client->dev, "controls initialization failed\n");
+		goto err_file;
+	}
+
+	format = __et8ek8_get_pad_format(sensor, NULL, 0,
+					 V4L2_SUBDEV_FORMAT_ACTIVE);
+	return 0;
+
+err_file:
+	device_remove_file(&client->dev, &dev_attr_priv_mem);
+
+	return rval;
+}
+
+static int __et8ek8_set_power(struct et8ek8_sensor *sensor, bool on)
+{
+	return on ? et8ek8_power_on(sensor) : et8ek8_power_off(sensor);
+}
+
+static int et8ek8_set_power(struct v4l2_subdev *subdev, int on)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+	int ret = 0;
+
+	mutex_lock(&sensor->power_lock);
+
+	/* If the power count is modified from 0 to != 0 or from != 0 to 0,
+	 * update the power state.
+	 */
+	if (sensor->power_count == !on) {
+		ret = __et8ek8_set_power(sensor, !!on);
+		if (ret < 0)
+			goto done;
+	}
+
+	/* Update the power count. */
+	sensor->power_count += on ? 1 : -1;
+	WARN_ON(sensor->power_count < 0);
+
+done:
+	mutex_unlock(&sensor->power_lock);
+
+	return ret;
+}
+
+static int et8ek8_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(sd);
+	struct v4l2_mbus_framefmt *format;
+	struct et8ek8_reglist *reglist;
+
+	reglist = et8ek8_reglist_find_type(&meta_reglist, ET8EK8_REGLIST_MODE);
+	format = __et8ek8_get_pad_format(sensor, fh->pad, 0,
+					 V4L2_SUBDEV_FORMAT_TRY);
+	et8ek8_reglist_to_mbus(reglist, format);
+
+	return et8ek8_set_power(sd, true);
+}
+
+static int et8ek8_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	return et8ek8_set_power(sd, false);
+}
+
+static const struct v4l2_subdev_video_ops et8ek8_video_ops = {
+	.s_stream = et8ek8_s_stream,
+	.g_frame_interval = et8ek8_get_frame_interval,
+	.s_frame_interval = et8ek8_set_frame_interval,
+};
+
+static const struct v4l2_subdev_core_ops et8ek8_core_ops = {
+	.s_power = et8ek8_set_power,
+};
+
+static const struct v4l2_subdev_pad_ops et8ek8_pad_ops = {
+	.enum_mbus_code = et8ek8_enum_mbus_code,
+	.enum_frame_size = et8ek8_enum_frame_size,
+	.enum_frame_interval = et8ek8_enum_frame_ival,
+	.get_fmt = et8ek8_get_pad_format,
+	.set_fmt = et8ek8_set_pad_format,
+};
+
+static const struct v4l2_subdev_ops et8ek8_ops = {
+	.core = &et8ek8_core_ops,
+	.video = &et8ek8_video_ops,
+	.pad = &et8ek8_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = {
+	.registered = et8ek8_registered,
+	.open = et8ek8_open,
+	.close = et8ek8_close,
+};
+
+/* --------------------------------------------------------------------------
+ * I2C driver
+ */
+#ifdef CONFIG_PM
+
+static int et8ek8_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+
+	if (!sensor->power_count)
+		return 0;
+
+	return __et8ek8_set_power(sensor, false);
+}
+
+static int et8ek8_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+
+	if (!sensor->power_count)
+		return 0;
+
+	return __et8ek8_set_power(sensor, true);
+}
+
+#else
+
+#define et8ek8_suspend NULL
+#define et8ek8_resume NULL
+
+#endif /* CONFIG_PM */
+
+static int et8ek8_probe(struct i2c_client *client,
+			const struct i2c_device_id *devid)
+{
+	struct et8ek8_sensor *sensor;
+	struct device *dev = &client->dev;
+	int ret;
+
+	sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return -ENOMEM;
+
+	sensor->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(sensor->reset)) {
+		dev_dbg(&client->dev, "could not request reset gpio\n");
+		return PTR_ERR(sensor->reset);
+	}
+
+	sensor->vana = devm_regulator_get(dev, "vana");
+	if (IS_ERR(sensor->vana)) {
+		dev_err(&client->dev, "could not get regulator for vana\n");
+		return PTR_ERR(sensor->vana);
+	}
+
+	sensor->ext_clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(sensor->ext_clk)) {
+		dev_err(&client->dev, "could not get clock\n");
+		return PTR_ERR(sensor->ext_clk);
+	}
+
+	ret = of_property_read_u32(dev->of_node, "clock-frequency",
+				   &sensor->xclk_freq);
+	if (ret) {
+		dev_warn(dev, "can't get clock-frequency\n");
+		return ret;
+	}
+
+	mutex_init(&sensor->power_lock);
+
+	v4l2_i2c_subdev_init(&sensor->subdev, client, &et8ek8_ops);
+	sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sensor->subdev.internal_ops = &et8ek8_internal_ops;
+
+	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad);
+	if (ret < 0) {
+		dev_err(&client->dev, "media entity init failed!\n");
+		return ret;
+	}
+
+	ret = v4l2_async_register_subdev(&sensor->subdev);
+	if (ret < 0) {
+		media_entity_cleanup(&sensor->subdev.entity);
+		return ret;
+	}
+
+	dev_dbg(dev, "initialized!\n");
+
+	return 0;
+}
+
+static int __exit et8ek8_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+	struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
+
+	if (sensor->power_count) {
+		gpiod_set_value(sensor->reset, 0);
+		clk_disable_unprepare(sensor->ext_clk);
+		sensor->power_count = 0;
+	}
+
+	v4l2_device_unregister_subdev(&sensor->subdev);
+	device_remove_file(&client->dev, &dev_attr_priv_mem);
+	v4l2_ctrl_handler_free(&sensor->ctrl_handler);
+	media_entity_cleanup(&sensor->subdev.entity);
+
+	return 0;
+}
+
+static const struct of_device_id et8ek8_of_table[] = {
+	{ .compatible = "toshiba,et8ek8" },
+	{ },
+};
+
+static const struct i2c_device_id et8ek8_id_table[] = {
+	{ ET8EK8_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, et8ek8_id_table);
+
+static const struct dev_pm_ops et8ek8_pm_ops = {
+	.suspend	= et8ek8_suspend,
+	.resume		= et8ek8_resume,
+};
+
+static struct i2c_driver et8ek8_i2c_driver = {
+	.driver		= {
+		.name	= ET8EK8_NAME,
+		.pm	= &et8ek8_pm_ops,
+		.of_match_table	= et8ek8_of_table,
+	},
+	.probe		= et8ek8_probe,
+	.remove		= __exit_p(et8ek8_remove),
+	.id_table	= et8ek8_id_table,
+};
+
+module_i2c_driver(et8ek8_i2c_driver);
+
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
+MODULE_DESCRIPTION("Toshiba ET8EK8 camera sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/et8ek8/et8ek8_mode.c b/drivers/media/i2c/et8ek8/et8ek8_mode.c
new file mode 100644
index 0000000..956fc60
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_mode.c
@@ -0,0 +1,587 @@ 
+/*
+ * et8ek8_mode.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *          Tuukka Toivonen <tuukka.o.toivonen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "et8ek8_reg.h"
+
+/*
+ * Stingray sensor mode settings for Scooby
+ */
+
+/* Mode1_poweron_Mode2_16VGA_2592x1968_12.07fps */
+static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = {
+/* (without the +1)
+ * SPCK       = 80 MHz
+ * CCP2       = 640 MHz
+ * VCO        = 640 MHz
+ * VCOUNT     = 84 (2016)
+ * HCOUNT     = 137 (3288)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 200
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 7
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 0
+ */
+	.type = ET8EK8_REGLIST_POWERON,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3288,
+		.height = 2016,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 2592,
+		.window_height = 1968,
+		.pixel_clock = 80000000,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 1207
+		},
+		.max_exp = 2012,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.sensitivity = 65536
+	},
+	.regs = {
+		/* Need to set firstly */
+		{ ET8EK8_REG_8BIT, 0x126C, 0xCC },
+		/* Strobe and Data of CCP2 delay are minimized. */
+		{ ET8EK8_REG_8BIT, 0x1269, 0x00 },
+		/* Refined value of Min H_COUNT  */
+		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
+		/* Frequency of SPCK setting (SPCK=MRCK) */
+		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
+		{ ET8EK8_REG_8BIT, 0x1241, 0x94 },
+		{ ET8EK8_REG_8BIT, 0x1242, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x124B, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1255, 0xFF },
+		{ ET8EK8_REG_8BIT, 0x1256, 0x9F },
+		{ ET8EK8_REG_8BIT, 0x1258, 0x00 },
+		/* From parallel out to serial out */
+		{ ET8EK8_REG_8BIT, 0x125D, 0x88 },
+		/* From w/ embeded data to w/o embeded data */
+		{ ET8EK8_REG_8BIT, 0x125E, 0xC0 },
+		/* CCP2 out is from STOP to ACTIVE */
+		{ ET8EK8_REG_8BIT, 0x1263, 0x98 },
+		{ ET8EK8_REG_8BIT, 0x1268, 0xC6 },
+		{ ET8EK8_REG_8BIT, 0x1434, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1163, 0x44 },
+		{ ET8EK8_REG_8BIT, 0x1166, 0x29 },
+		{ ET8EK8_REG_8BIT, 0x1140, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x1011, 0x24 },
+		{ ET8EK8_REG_8BIT, 0x1151, 0x80 },
+		{ ET8EK8_REG_8BIT, 0x1152, 0x23 },
+		/* Initial setting for improvement2 of lower frequency noise */
+		{ ET8EK8_REG_8BIT, 0x1014, 0x05 },
+		{ ET8EK8_REG_8BIT, 0x1033, 0x06 },
+		{ ET8EK8_REG_8BIT, 0x1034, 0x79 },
+		{ ET8EK8_REG_8BIT, 0x1423, 0x3F },
+		{ ET8EK8_REG_8BIT, 0x1424, 0x3F },
+		{ ET8EK8_REG_8BIT, 0x1426, 0x00 },
+		/* Switch of Preset-White-balance (0d:disable / 1d:enable) */
+		{ ET8EK8_REG_8BIT, 0x1439, 0x00 },
+		/* Switch of blemish correction (0d:disable / 1d:enable) */
+		{ ET8EK8_REG_8BIT, 0x161F, 0x60 },
+		/* Switch of auto noise correction (0d:disable / 1d:enable) */
+		{ ET8EK8_REG_8BIT, 0x1634, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1646, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1648, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x113E, 0x01 },
+		{ ET8EK8_REG_8BIT, 0x113F, 0x22 },
+		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode1_16VGA_2592x1968_13.12fps_DPCM10-8 */
+static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = {
+/* (without the +1)
+ * SPCK       = 80 MHz
+ * CCP2       = 560 MHz
+ * VCO        = 560 MHz
+ * VCOUNT     = 84 (2016)
+ * HCOUNT     = 128 (3072)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 175
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 6
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 0
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3072,
+		.height = 2016,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 2592,
+		.window_height = 1968,
+		.pixel_clock = 80000000,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 1292
+		},
+		.max_exp = 2012,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x57 },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x06 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0x80 }, /* <-changed to v14 7E->80 */
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode3_4VGA_1296x984_29.99fps_DPCM10-8 */
+static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = {
+/* (without the +1)
+ * SPCK       = 96.5333333333333 MHz
+ * CCP2       = 579.2 MHz
+ * VCO        = 579.2 MHz
+ * VCOUNT     = 84 (2016)
+ * HCOUNT     = 133 (3192)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 181
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 5
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 0
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3192,
+		.height = 1008,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 1296,
+		.window_height = 984,
+		.pixel_clock = 96533333,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 3000
+		},
+		.max_exp = 1004,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x5A },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode4_SVGA_864x656_29.88fps */
+static struct et8ek8_reglist mode4_svga_864x656_29_88fps = {
+/* (without the +1)
+ * SPCK       = 80 MHz
+ * CCP2       = 320 MHz
+ * VCO        = 640 MHz
+ * VCOUNT     = 84 (2016)
+ * HCOUNT     = 166 (3984)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 200
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 7
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 1
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3984,
+		.height = 672,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 864,
+		.window_height = 656,
+		.pixel_clock = 80000000,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 2988
+		},
+		.max_exp = 668,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x62 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x62 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0xA6 },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode5_VGA_648x492_29.93fps */
+static struct et8ek8_reglist mode5_vga_648x492_29_93fps = {
+/* (without the +1)
+ * SPCK       = 80 MHz
+ * CCP2       = 320 MHz
+ * VCO        = 640 MHz
+ * VCOUNT     = 84 (2016)
+ * HCOUNT     = 221 (5304)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 200
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 7
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 1
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 5304,
+		.height = 504,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 648,
+		.window_height = 492,
+		.pixel_clock = 80000000,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 2993
+		},
+		.max_exp = 500,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode2_16VGA_2592x1968_3.99fps */
+static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = {
+/* (without the +1)
+ * SPCK       = 80 MHz
+ * CCP2       = 640 MHz
+ * VCO        = 640 MHz
+ * VCOUNT     = 254 (6096)
+ * HCOUNT     = 137 (3288)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 200
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 7
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 0
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3288,
+		.height = 6096,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 2592,
+		.window_height = 1968,
+		.pixel_clock = 80000000,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 399
+		},
+		.max_exp = 6092,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x07 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0xFE },
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode_648x492_5fps */
+static struct et8ek8_reglist mode_648x492_5fps = {
+/* (without the +1)
+ * SPCK       = 13.3333333333333 MHz
+ * CCP2       = 53.3333333333333 MHz
+ * VCO        = 640 MHz
+ * VCOUNT     = 84 (2016)
+ * HCOUNT     = 221 (5304)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 200
+ * VCO_DIV    = 5
+ * SPCK_DIV   = 7
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 1
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 5304,
+		.height = 504,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 648,
+		.window_height = 492,
+		.pixel_clock = 13333333,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 499
+		},
+		.max_exp = 500,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x64 },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x71 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x57 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x61 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x61 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0xDD },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x54 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode3_4VGA_1296x984_5fps */
+static struct et8ek8_reglist mode3_4vga_1296x984_5fps = {
+/* (without the +1)
+ * SPCK       = 49.4 MHz
+ * CCP2       = 395.2 MHz
+ * VCO        = 790.4 MHz
+ * VCOUNT     = 250 (6000)
+ * HCOUNT     = 137 (3288)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 247
+ * VCO_DIV    = 1
+ * SPCK_DIV   = 7
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 0
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3288,
+		.height = 3000,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 1296,
+		.window_height = 984,
+		.pixel_clock = 49400000,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 501
+		},
+		.max_exp = 2996,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x7B },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x82 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x17 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0x89 },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0xFA },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/  */
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+/* Mode_4VGA_1296x984_25fps_DPCM10-8 */
+static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = {
+/* (without the +1)
+ * SPCK       = 84.2666666666667 MHz
+ * CCP2       = 505.6 MHz
+ * VCO        = 505.6 MHz
+ * VCOUNT     = 88 (2112)
+ * HCOUNT     = 133 (3192)
+ * CKREF_DIV  = 2
+ * CKVAR_DIV  = 158
+ * VCO_DIV    = 0
+ * SPCK_DIV   = 5
+ * MRCK_DIV   = 7
+ * LVDSCK_DIV = 0
+ */
+	.type = ET8EK8_REGLIST_MODE,
+	.mode = {
+		.sensor_width = 2592,
+		.sensor_height = 1968,
+		.sensor_window_origin_x = 0,
+		.sensor_window_origin_y = 0,
+		.sensor_window_width = 2592,
+		.sensor_window_height = 1968,
+		.width = 3192,
+		.height = 1056,
+		.window_origin_x = 0,
+		.window_origin_y = 0,
+		.window_width = 1296,
+		.window_height = 984,
+		.pixel_clock = 84266667,
+		.ext_clock = 9600000,
+		.timeperframe = {
+			.numerator = 100,
+			.denominator = 2500
+		},
+		.max_exp = 1052,
+		/* .max_gain = 0, */
+		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
+		.sensitivity = 65536
+	},
+	.regs = {
+		{ ET8EK8_REG_8BIT, 0x1239, 0x4F },
+		{ ET8EK8_REG_8BIT, 0x1238, 0x02 },
+		{ ET8EK8_REG_8BIT, 0x123B, 0x70 },
+		{ ET8EK8_REG_8BIT, 0x123A, 0x05 },
+		{ ET8EK8_REG_8BIT, 0x121B, 0x63 },
+		{ ET8EK8_REG_8BIT, 0x1220, 0x85 },
+		{ ET8EK8_REG_8BIT, 0x1221, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x1222, 0x58 },
+		{ ET8EK8_REG_8BIT, 0x1223, 0x00 },
+		{ ET8EK8_REG_8BIT, 0x121D, 0x63 },
+		{ ET8EK8_REG_8BIT, 0x125D, 0x83 },
+		{ ET8EK8_REG_TERM, 0, 0}
+	}
+};
+
+struct et8ek8_meta_reglist meta_reglist = {
+	.version = "V14 03-June-2008",
+	.reglist = {
+		{ .ptr = &mode1_poweron_mode2_16vga_2592x1968_12_07fps },
+		{ .ptr = &mode1_16vga_2592x1968_13_12fps_dpcm10_8 },
+		{ .ptr = &mode3_4vga_1296x984_29_99fps_dpcm10_8 },
+		{ .ptr = &mode4_svga_864x656_29_88fps },
+		{ .ptr = &mode5_vga_648x492_29_93fps },
+		{ .ptr = &mode2_16vga_2592x1968_3_99fps },
+		{ .ptr = &mode_648x492_5fps },
+		{ .ptr = &mode3_4vga_1296x984_5fps },
+		{ .ptr = &mode_4vga_1296x984_25fps_dpcm10_8 },
+		{ .ptr = NULL }
+	}
+};
diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h
new file mode 100644
index 0000000..9970bff
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
@@ -0,0 +1,96 @@ 
+/*
+ * et8ek8.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *          Tuukka Toivonen <tuukkat76@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef ET8EK8REGS_H
+#define ET8EK8REGS_H
+
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/v4l2-subdev.h>
+
+struct v4l2_mbus_framefmt;
+struct v4l2_subdev_pad_mbus_code_enum;
+
+struct et8ek8_mode {
+	/* Physical sensor resolution and current image window */
+	u16 sensor_width;
+	u16 sensor_height;
+	u16 sensor_window_origin_x;
+	u16 sensor_window_origin_y;
+	u16 sensor_window_width;
+	u16 sensor_window_height;
+
+	/* Image data coming from sensor (after scaling) */
+	u16 width;
+	u16 height;
+	u16 window_origin_x;
+	u16 window_origin_y;
+	u16 window_width;
+	u16 window_height;
+
+	u32 pixel_clock;		/* in Hz */
+	u32 ext_clock;			/* in Hz */
+	struct v4l2_fract timeperframe;
+	u32 max_exp;			/* Maximum exposure value */
+	u32 pixel_format;		/* V4L2_PIX_FMT_xxx */
+	u32 sensitivity;		/* 16.16 fixed point */
+};
+
+#define ET8EK8_REG_8BIT			1
+#define ET8EK8_REG_16BIT		2
+#define ET8EK8_REG_DELAY		100
+#define ET8EK8_REG_TERM			0xff
+struct et8ek8_reg {
+	u16 type;
+	u16 reg;			/* 16-bit offset */
+	u32 val;			/* 8/16/32-bit value */
+};
+
+/* Possible struct smia_reglist types. */
+#define ET8EK8_REGLIST_STANDBY		0
+#define ET8EK8_REGLIST_POWERON		1
+#define ET8EK8_REGLIST_RESUME		2
+#define ET8EK8_REGLIST_STREAMON		3
+#define ET8EK8_REGLIST_STREAMOFF	4
+#define ET8EK8_REGLIST_DISABLED		5
+
+#define ET8EK8_REGLIST_MODE		10
+
+#define ET8EK8_REGLIST_LSC_ENABLE	100
+#define ET8EK8_REGLIST_LSC_DISABLE	101
+#define ET8EK8_REGLIST_ANR_ENABLE	102
+#define ET8EK8_REGLIST_ANR_DISABLE	103
+
+struct et8ek8_reglist {
+	u32 type;
+	struct et8ek8_mode mode;
+	struct et8ek8_reg regs[];
+};
+
+#define ET8EK8_MAX_LEN			32
+struct et8ek8_meta_reglist {
+	char version[ET8EK8_MAX_LEN];
+	union {
+		struct et8ek8_reglist *ptr;
+	} reglist[];
+};
+
+extern struct et8ek8_meta_reglist meta_reglist;
+
+#endif /* ET8EK8REGS */