linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
@ 2016-10-23 20:03 Pavel Machek
  2016-10-23 20:19 ` Sakari Ailus
  2016-11-19 23:29 ` Sakari Ailus
  0 siblings, 2 replies; 97+ messages in thread
From: Pavel Machek @ 2016-10-23 20:03 UTC (permalink / raw)
  To: ivo.g.dimitrov.75, sakari.ailus, sre, pali.rohar, linux-media,
	galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 64049 bytes --]


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)
+{
+	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 */

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:03 [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor Pavel Machek
@ 2016-10-23 20:19 ` Sakari Ailus
  2016-10-23 20:33   ` Pavel Machek
                     ` (3 more replies)
  2016-11-19 23:29 ` Sakari Ailus
  1 sibling, 4 replies; 97+ messages in thread
From: Sakari Ailus @ 2016-10-23 20:19 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

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 */
> 

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:19 ` Sakari Ailus
@ 2016-10-23 20:33   ` Pavel Machek
  2016-10-31 22:54     ` Sakari Ailus
  2016-10-23 20:40   ` Pavel Machek
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-10-23 20:33 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 74770 bytes --]

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 */
> > 
> 

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:19 ` Sakari Ailus
  2016-10-23 20:33   ` Pavel Machek
@ 2016-10-23 20:40   ` Pavel Machek
  2016-10-31 22:58     ` Sakari Ailus
  2016-10-23 20:47   ` Pavel Machek
  2016-12-13 21:05   ` Pavel Machek
  3 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-10-23 20:40 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2676 bytes --]

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
								
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:19 ` Sakari Ailus
  2016-10-23 20:33   ` Pavel Machek
  2016-10-23 20:40   ` Pavel Machek
@ 2016-10-23 20:47   ` Pavel Machek
  2016-12-13 21:05   ` Pavel Machek
  3 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-10-23 20:47 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 493 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:33   ` Pavel Machek
@ 2016-10-31 22:54     ` Sakari Ailus
  2016-11-01  6:36       ` Ivaylo Dimitrov
                         ` (2 more replies)
  0 siblings, 3 replies; 97+ messages in thread
From: Sakari Ailus @ 2016-10-31 22:54 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

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?

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:40   ` Pavel Machek
@ 2016-10-31 22:58     ` Sakari Ailus
  2016-11-02  0:45       ` Laurent Pinchart
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-10-31 22:58 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel, laurent.pinchart

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. :-(

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-31 22:54     ` Sakari Ailus
@ 2016-11-01  6:36       ` Ivaylo Dimitrov
  2016-11-01 20:11         ` Sakari Ailus
  2016-11-02  8:15         ` Pavel Machek
  2016-11-01 15:39       ` Pavel Machek
  2016-11-03 22:48       ` Sebastian Reichel
  2 siblings, 2 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2016-11-01  6:36 UTC (permalink / raw)
  To: Sakari Ailus, Pavel Machek
  Cc: sre, pali.rohar, linux-media, galak, mchehab, linux-kernel

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

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-31 22:54     ` Sakari Ailus
  2016-11-01  6:36       ` Ivaylo Dimitrov
@ 2016-11-01 15:39       ` Pavel Machek
  2016-11-01 20:08         ` Sakari Ailus
  2016-11-03 22:48       ` Sebastian Reichel
  2 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-11-01 15:39 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2234 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-01 15:39       ` Pavel Machek
@ 2016-11-01 20:08         ` Sakari Ailus
  2016-11-03  8:14           ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-11-01 20:08 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

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.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-01  6:36       ` Ivaylo Dimitrov
@ 2016-11-01 20:11         ` Sakari Ailus
  2016-11-01 22:14           ` Ivaylo Dimitrov
  2016-11-02  8:15         ` Pavel Machek
  1 sibling, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-11-01 20:11 UTC (permalink / raw)
  To: Ivaylo Dimitrov
  Cc: Pavel Machek, sre, pali.rohar, linux-media, galak, mchehab, linux-kernel

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. :-) ;-)

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-01 20:11         ` Sakari Ailus
@ 2016-11-01 22:14           ` Ivaylo Dimitrov
  0 siblings, 0 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2016-11-01 22:14 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pavel Machek, sre, pali.rohar, linux-media, galak, mchehab, linux-kernel



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 :)

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-31 22:58     ` Sakari Ailus
@ 2016-11-02  0:45       ` Laurent Pinchart
  0 siblings, 0 replies; 97+ messages in thread
From: Laurent Pinchart @ 2016-11-02  0:45 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pavel Machek, ivo.g.dimitrov.75, sre, pali.rohar, linux-media,
	galak, mchehab, linux-kernel

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. :-(

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-01  6:36       ` Ivaylo Dimitrov
  2016-11-01 20:11         ` Sakari Ailus
@ 2016-11-02  8:15         ` Pavel Machek
  2016-11-02  8:16           ` Ivaylo Dimitrov
  1 sibling, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-11-02  8:15 UTC (permalink / raw)
  To: Ivaylo Dimitrov
  Cc: Sakari Ailus, sre, pali.rohar, linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1290 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-02  8:15         ` Pavel Machek
@ 2016-11-02  8:16           ` Ivaylo Dimitrov
  0 siblings, 0 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2016-11-02  8:16 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, sre, pali.rohar, linux-media, galak, mchehab, linux-kernel



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

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-01 20:08         ` Sakari Ailus
@ 2016-11-03  8:14           ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-11-03  8:14 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2800 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-31 22:54     ` Sakari Ailus
  2016-11-01  6:36       ` Ivaylo Dimitrov
  2016-11-01 15:39       ` Pavel Machek
@ 2016-11-03 22:48       ` Sebastian Reichel
  2016-11-03 23:05         ` Sakari Ailus
  2016-11-15 10:54         ` Pavel Machek
  2 siblings, 2 replies; 97+ messages in thread
From: Sebastian Reichel @ 2016-11-03 22:48 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pavel Machek, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2524 bytes --]

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-03 22:48       ` Sebastian Reichel
@ 2016-11-03 23:05         ` Sakari Ailus
  2016-11-03 23:40           ` Ivaylo Dimitrov
  2016-11-04  0:05           ` Sebastian Reichel
  2016-11-15 10:54         ` Pavel Machek
  1 sibling, 2 replies; 97+ messages in thread
From: Sakari Ailus @ 2016-11-03 23:05 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Pavel Machek, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

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

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-03 23:05         ` Sakari Ailus
@ 2016-11-03 23:40           ` Ivaylo Dimitrov
  2016-11-04  0:05           ` Sebastian Reichel
  1 sibling, 0 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2016-11-03 23:40 UTC (permalink / raw)
  To: Sakari Ailus, Sebastian Reichel
  Cc: Pavel Machek, pali.rohar, linux-media, galak, mchehab, linux-kernel

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

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-03 23:05         ` Sakari Ailus
  2016-11-03 23:40           ` Ivaylo Dimitrov
@ 2016-11-04  0:05           ` Sebastian Reichel
  2016-11-14 21:58             ` Sakari Ailus
  1 sibling, 1 reply; 97+ messages in thread
From: Sebastian Reichel @ 2016-11-04  0:05 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pavel Machek, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 4088 bytes --]

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-04  0:05           ` Sebastian Reichel
@ 2016-11-14 21:58             ` Sakari Ailus
  2016-11-15  0:53               ` Sebastian Reichel
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-11-14 21:58 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Pavel Machek, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

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...

-- 
Regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-14 21:58             ` Sakari Ailus
@ 2016-11-15  0:53               ` Sebastian Reichel
  0 siblings, 0 replies; 97+ messages in thread
From: Sebastian Reichel @ 2016-11-15  0:53 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pavel Machek, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 761 bytes --]

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-03 22:48       ` Sebastian Reichel
  2016-11-03 23:05         ` Sakari Ailus
@ 2016-11-15 10:54         ` Pavel Machek
  2016-11-15 22:55           ` Sakari Ailus
  1 sibling, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-11-15 10:54 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1628 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-15 10:54         ` Pavel Machek
@ 2016-11-15 22:55           ` Sakari Ailus
  0 siblings, 0 replies; 97+ messages in thread
From: Sakari Ailus @ 2016-11-15 22:55 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sebastian Reichel, ivo.g.dimitrov.75, pali.rohar, linux-media,
	galak, mchehab, linux-kernel

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.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:03 [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor Pavel Machek
  2016-10-23 20:19 ` Sakari Ailus
@ 2016-11-19 23:29 ` Sakari Ailus
  2016-11-20 10:02   ` Pavel Machek
                     ` (4 more replies)
  1 sibling, 5 replies; 97+ messages in thread
From: Sakari Ailus @ 2016-11-19 23:29 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

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 */
> 

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-19 23:29 ` Sakari Ailus
@ 2016-11-20 10:02   ` Pavel Machek
  2016-11-20 15:20   ` Pavel Machek
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-11-20 10:02 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 413 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-19 23:29 ` Sakari Ailus
  2016-11-20 10:02   ` Pavel Machek
@ 2016-11-20 15:20   ` Pavel Machek
  2016-11-20 15:21   ` Pavel Machek
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-11-20 15:20 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 5070 bytes --]

Hi!

> > +	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.

Do you have some more hints? I'll try to figure it out...

> > +#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.

Ok, I guess I figured it out from other code (no network at the
moment).

> > +	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.

Added.

> > +	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;
> > +}

> > +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?

Ok, I guess that saves few lines.

> > +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

Me? Ok, I can list myself there, but I don't really know much about
that driver.

> > + * 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).

Fixed.

Ok, these cleanups are here.

									Pavel

diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
index eb131b2..eb8c1b4 100644
--- a/drivers/media/i2c/et8ek8/et8ek8_driver.c
+++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
@@ -5,6 +5,7 @@
  *
  * Contact: Sakari Ailus <sakari.ailus@iki.fi>
  *          Tuukka Toivonen <tuukkat76@gmail.com>
+ *          Pavel Machek <pavel@ucw.cz>
  *
  * Based on code from Toni Leinonen <toni.leinonen@offcode.fi>.
  *
@@ -1435,9 +1436,7 @@ static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = {
 /* --------------------------------------------------------------------------
  * I2C driver
  */
-#ifdef CONFIG_PM
-
-static int et8ek8_suspend(struct device *dev)
+static int __maybe_unused et8ek8_suspend(struct device *dev)
 {
 	struct i2c_client *client = to_i2c_client(dev);
 	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
@@ -1449,7 +1448,7 @@ static int et8ek8_suspend(struct device *dev)
 	return __et8ek8_set_power(sensor, false);
 }
 
-static int et8ek8_resume(struct device *dev)
+static int __maybe_unused et8ek8_resume(struct device *dev)
 {
 	struct i2c_client *client = to_i2c_client(dev);
 	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
@@ -1461,13 +1460,6 @@ static int et8ek8_resume(struct device *dev)
 	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)
 {
@@ -1542,6 +1534,7 @@ static int __exit et8ek8_remove(struct i2c_client *client)
 	v4l2_device_unregister_subdev(&sensor->subdev);
 	device_remove_file(&client->dev, &dev_attr_priv_mem);
 	v4l2_ctrl_handler_free(&sensor->ctrl_handler);
+	v4l2_async_unregister_subdev(&sensor->subdev);
 	media_entity_cleanup(&sensor->subdev.entity);
 
 	return 0;
@@ -1559,8 +1552,7 @@ static const struct i2c_device_id et8ek8_id_table[] = {
 MODULE_DEVICE_TABLE(i2c, et8ek8_id_table);
 
 static const struct dev_pm_ops et8ek8_pm_ops = {
-	.suspend	= et8ek8_suspend,
-	.resume		= et8ek8_resume,
+	SET_SYSTEM_SLEEP_PM_OPS(et8ek8_suspend, et8ek8_resume)
 };
 
 static struct i2c_driver et8ek8_i2c_driver = {
@@ -1576,6 +1568,6 @@ static struct i2c_driver et8ek8_i2c_driver = {
 
 module_i2c_driver(et8ek8_i2c_driver);
 
-MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>, Pavel Machek <pavel@ucw.cz");
 MODULE_DESCRIPTION("Toshiba ET8EK8 camera sensor driver");
 MODULE_LICENSE("GPL");


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-19 23:29 ` Sakari Ailus
  2016-11-20 10:02   ` Pavel Machek
  2016-11-20 15:20   ` Pavel Machek
@ 2016-11-20 15:21   ` Pavel Machek
  2016-11-20 15:31   ` Pavel Machek
  2016-12-14 12:24   ` [PATCH v5] " Pavel Machek
  4 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-11-20 15:21 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 5801 bytes --]

Hi!

> > +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. :-)

Makes sense...

Something like this? [untested, will test complete changes.]

									Pavel

diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
index 0301e81..eb131b2 100644
--- a/drivers/media/i2c/et8ek8/et8ek8_driver.c
+++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
@@ -395,11 +395,7 @@ static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist,
 {
 	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;
+	fmt->code = reglist->mode.bus_format;
 }
 
 static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt(
@@ -538,7 +534,7 @@ static int et8ek8_reglist_import(struct i2c_client *client,
 		       __func__,
 		       list->type,
 		       list->mode.window_width, list->mode.window_height,
-		       list->mode.pixel_format,
+		       list->mode.bus_format,
 		       list->mode.timeperframe.numerator,
 		       list->mode.timeperframe.denominator,
 		       (void *)meta->reglist[nlists].ptr);
@@ -967,21 +963,18 @@ static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev,
 			continue;
 
 		for (i = 0; i < npixelformat; i++) {
-			if (pixelformat[i] == mode->pixel_format)
+			if (pixelformat[i] == mode->bus_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;
+			code->code = mode->bus_format;
 			return 0;
 		}
 
-		pixelformat[npixelformat] = mode->pixel_format;
+		pixelformat[npixelformat] = mode->bus_format;
 		npixelformat++;
 	}
 
diff --git a/drivers/media/i2c/et8ek8/et8ek8_mode.c b/drivers/media/i2c/et8ek8/et8ek8_mode.c
index 956fc60..12998d8 100644
--- a/drivers/media/i2c/et8ek8/et8ek8_mode.c
+++ b/drivers/media/i2c/et8ek8/et8ek8_mode.c
@@ -59,7 +59,7 @@ static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = {
 		},
 		.max_exp = 2012,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -160,7 +160,7 @@ static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = {
 		},
 		.max_exp = 2012,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -216,7 +216,7 @@ static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = {
 		},
 		.max_exp = 1004,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -272,7 +272,7 @@ static struct et8ek8_reglist mode4_svga_864x656_29_88fps = {
 		},
 		.max_exp = 668,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -328,7 +328,7 @@ static struct et8ek8_reglist mode5_vga_648x492_29_93fps = {
 		},
 		.max_exp = 500,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -384,7 +384,7 @@ static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = {
 		},
 		.max_exp = 6092,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -439,7 +439,7 @@ static struct et8ek8_reglist mode_648x492_5fps = {
 		},
 		.max_exp = 500,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -495,7 +495,7 @@ static struct et8ek8_reglist mode3_4vga_1296x984_5fps = {
 		},
 		.max_exp = 2996,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
 		.sensitivity = 65536
 	},
 	.regs = {
@@ -551,7 +551,7 @@ static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = {
 		},
 		.max_exp = 1052,
 		/* .max_gain = 0, */
-		.pixel_format = V4L2_PIX_FMT_SGRBG10DPCM8,
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
 		.sensitivity = 65536
 	},
 	.regs = {
diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h
index 9970bff..64a8fb7 100644
--- a/drivers/media/i2c/et8ek8/et8ek8_reg.h
+++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
@@ -48,7 +48,7 @@ struct et8ek8_mode {
 	u32 ext_clock;			/* in Hz */
 	struct v4l2_fract timeperframe;
 	u32 max_exp;			/* Maximum exposure value */
-	u32 pixel_format;		/* V4L2_PIX_FMT_xxx */
+	u32 bus_format;			/* MEDIA_BUS_FMT_ */
 	u32 sensitivity;		/* 16.16 fixed point */
 };
 

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-19 23:29 ` Sakari Ailus
                     ` (2 preceding siblings ...)
  2016-11-20 15:21   ` Pavel Machek
@ 2016-11-20 15:31   ` Pavel Machek
  2016-12-14 12:24   ` [PATCH v5] " Pavel Machek
  4 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-11-20 15:31 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1259 bytes --]

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
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-10-23 20:19 ` Sakari Ailus
                     ` (2 preceding siblings ...)
  2016-10-23 20:47   ` Pavel Machek
@ 2016-12-13 21:05   ` Pavel Machek
  2016-12-18 21:56     ` Sakari Ailus
  3 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-13 21:05 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1965 bytes --]

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

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-11-19 23:29 ` Sakari Ailus
                     ` (3 preceding siblings ...)
  2016-11-20 15:31   ` Pavel Machek
@ 2016-12-14 12:24   ` Pavel Machek
  2016-12-14 13:03     ` Pali Rohár
                       ` (2 more replies)
  4 siblings, 3 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-14 12:24 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 61863 bytes --]

 
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.

Exposure value is now in native units, which simplifies the code.

The patch to add device tree bindings was already acked by device tree
people.

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..4a638f8
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
@@ -0,0 +1,1515 @@
+/*
+ * et8ek8_driver.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *          Tuukka Toivonen <tuukkat76@gmail.com>
+ *          Pavel Machek <pavel@ucw.cz>
+ *
+ * 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;
+	fmt->code = reglist->mode.bus_format;
+}
+
+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.bus_format,
+		       list->mode.timeperframe.numerator,
+		       list->mode.timeperframe.denominator,
+		       (void *)meta->reglist[nlists].ptr);
+
+		nlists++;
+	}
+
+	return 0;
+}
+
+/* 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);
+
+	switch (ctrl->id) {
+	case V4L2_CID_GAIN:
+		return et8ek8_set_gain(sensor, ctrl->val);
+
+	case V4L2_CID_EXPOSURE:
+	{
+		int rows;
+		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+		rows = ctrl->val;
+		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:
+		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)
+{
+	s32 max_rows;
+
+	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);
+
+	max_rows = sensor->current_reglist->mode.max_exp;
+	{
+		u32 min = 1, max = max_rows;
+
+		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;
+	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
+	
+	u32 min, max, pixel_rate;
+	static const int S = 8;
+
+	ctrl = sensor->exposure;
+
+	min = 1;
+	max = 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_modify_range(ctrl, min, max, min, max);
+	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
+}
+
+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->bus_format)
+				break;
+		}
+		if (i != npixelformat)
+			continue;
+
+		if (code->index == npixelformat) {
+			code->code = mode->bus_format;
+			return 0;
+		}
+
+		pixelformat[npixelformat] = mode->bus_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
+ */
+static int __maybe_unused 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 __maybe_unused 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);
+}
+
+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");
+		goto err_mutex;
+	}
+
+	ret = v4l2_async_register_subdev(&sensor->subdev);
+	if (ret < 0)
+		goto err_entity;
+
+	dev_dbg(dev, "initialized!\n");
+
+	return 0;
+
+err_entity:
+	media_entity_cleanup(&sensor->subdev.entity);
+err_mutex:
+	mutex_destroy(&sensor->power_lock);
+	return ret;
+}
+
+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) {
+		WARN_ON(1);
+		et8ek8_power_off(sensor);
+		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);
+	v4l2_async_unregister_subdev(&sensor->subdev);
+	media_entity_cleanup(&sensor->subdev.entity);
+	mutex_destroy(&sensor->power_lock);
+
+	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 = {
+	SET_SYSTEM_SLEEP_PM_OPS(et8ek8_suspend, 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>, Pavel Machek <pavel@ucw.cz");
+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..a79882a
--- /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 <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.
+ */
+
+#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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+		.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..07f1873
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
@@ -0,0 +1,96 @@
+/*
+ * 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 bus_format;			/* MEDIA_BUS_FMT_ */
+	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 */


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 12:24   ` [PATCH v5] " Pavel Machek
@ 2016-12-14 13:03     ` Pali Rohár
  2016-12-14 15:52       ` Ivaylo Dimitrov
  2016-12-14 20:12       ` Pavel Machek
  2016-12-21 13:42     ` Sakari Ailus
  2016-12-22 10:01     ` [PATCH v6] " Pavel Machek
  2 siblings, 2 replies; 97+ messages in thread
From: Pali Rohár @ 2016-12-14 13:03 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

Hi! See inlined some my notes.

On Wednesday 14 December 2016 13:24:51 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.
> 
> Exposure value is now in native units, which simplifies the code.
> 
> The patch to add device tree bindings was already acked by device tree
> people.
> 
> 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..4a638f8
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
> @@ -0,0 +1,1515 @@
> +/*
> + * et8ek8_driver.c
> + *
> + * Copyright (C) 2008 Nokia Corporation
> + *
> + * Contact: Sakari Ailus <sakari.ailus@iki.fi>
> + *          Tuukka Toivonen <tuukkat76@gmail.com>
> + *          Pavel Machek <pavel@ucw.cz>
> + *
> + * 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__);

dev_warn_once()

> +	}
> +}
> +
> +/*
> + * 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__)) {

Maybe replace it with dev_warn_once() too? That condition in WARN_ONCE
does not look nice...

> +		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)) {

dev_warn()

> +				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;
> +}

Above code looks like re-implementation of linked-list. Does not kernel
already provide some?

> +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;
> +	fmt->code = reglist->mode.bus_format;
> +}
> +
> +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.bus_format,
> +		       list->mode.timeperframe.numerator,
> +		       list->mode.timeperframe.denominator,
> +		       (void *)meta->reglist[nlists].ptr);
> +
> +		nlists++;
> +	}
> +
> +	return 0;
> +}
> +
> +/* 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! */

Is this FIMXE still valid?

> +	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);
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_GAIN:
> +		return et8ek8_set_gain(sensor, ctrl->val);
> +
> +	case V4L2_CID_EXPOSURE:
> +	{
> +		int rows;
> +		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +		rows = ctrl->val;
> +		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:
> +		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)
> +{
> +	s32 max_rows;
> +
> +	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);
> +
> +	max_rows = sensor->current_reglist->mode.max_exp;
> +	{
> +		u32 min = 1, max = max_rows;
> +
> +		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;
> +	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
> +	
> +	u32 min, max, pixel_rate;
> +	static const int S = 8;
> +
> +	ctrl = sensor->exposure;
> +
> +	min = 1;
> +	max = 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_modify_range(ctrl, min, max, min, max);
> +	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
> +}
> +
> +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

USE_CRC is defined to 1. Do we need that #ifdef check at all?

What with above TODO?

> +
> +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->bus_format)
> +				break;
> +		}
> +		if (i != npixelformat)
> +			continue;
> +
> +		if (code->index == npixelformat) {
> +			code->code = mode->bus_format;
> +			return 0;
> +		}
> +
> +		pixelformat[npixelformat] = mode->bus_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);

Rather some dev_warn()? Do we need stack trace here?

> +
> +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
> + */
> +static int __maybe_unused 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 __maybe_unused 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);
> +}
> +
> +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");
> +		goto err_mutex;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&sensor->subdev);
> +	if (ret < 0)
> +		goto err_entity;
> +
> +	dev_dbg(dev, "initialized!\n");
> +
> +	return 0;
> +
> +err_entity:
> +	media_entity_cleanup(&sensor->subdev.entity);
> +err_mutex:
> +	mutex_destroy(&sensor->power_lock);
> +	return ret;
> +}
> +
> +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) {
> +		WARN_ON(1);

Such warning is probably not useful...

> +		et8ek8_power_off(sensor);
> +		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);
> +	v4l2_async_unregister_subdev(&sensor->subdev);
> +	media_entity_cleanup(&sensor->subdev.entity);
> +	mutex_destroy(&sensor->power_lock);
> +
> +	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 = {
> +	SET_SYSTEM_SLEEP_PM_OPS(et8ek8_suspend, 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>, Pavel Machek <pavel@ucw.cz");
> +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..a79882a
> --- /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 <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.
> + */
> +
> +#include "et8ek8_reg.h"
> +
> +/*
> + * Stingray sensor mode settings for Scooby
> + */

Are settings for this sensor Stingray enough?

It was me who copied these sensors settings to kernel driver. And I
chose only Stingray as this is what was needed for my N900 for
testing... Btw, you could add somewhere my and Ivo's Signed-off and
copyright state as we both modified et8ek8.c code...

Other files are (or at least were) in camera-firmware repository on
gitorious.

> +/* 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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.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, */
> +		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
> +		.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..07f1873
> --- /dev/null
> +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
> @@ -0,0 +1,96 @@
> +/*
> + * 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 bus_format;			/* MEDIA_BUS_FMT_ */
> +	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 */
> 
> 

-- 
Pali Rohár
pali.rohar@gmail.com

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 13:03     ` Pali Rohár
@ 2016-12-14 15:52       ` Ivaylo Dimitrov
  2016-12-14 20:12       ` Pavel Machek
  1 sibling, 0 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2016-12-14 15:52 UTC (permalink / raw)
  To: Pali Rohár, Pavel Machek
  Cc: Sakari Ailus, sre, linux-media, galak, mchehab, linux-kernel

Hi

On 14.12.2016 15:03, Pali Rohár wrote:
> Hi! See inlined some my notes.
>

>> +
>> +#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
>
> USE_CRC is defined to 1. Do we need that #ifdef check at all?
>
> What with above TODO?
>
>> +

It becomes a bit more complicated :) - on n900, front camera doesn't use 
CRC, while back camera does. Right now there is no way, even if we use 
video bus switch driver, to tell ISP to have 2 ports with different 
settings, not only for CRC, but for strobe, etc. Look at that commit 
https://github.com/freemangordon/linux-n900/commit/e5582fa56bbc0161d6c567157df42433829ee4de. 
What I think here is that ISP should take settings from the remote 
endpoints rather from it's local port. So far it does not.

So, until there is a way for ISP to have more than one CCP channel with 
different settings, I can't think of anything we can do here but to 
place TODO.

Ivo

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 13:03     ` Pali Rohár
  2016-12-14 15:52       ` Ivaylo Dimitrov
@ 2016-12-14 20:12       ` Pavel Machek
  2016-12-14 22:07         ` Pali Rohár
  2016-12-18 22:01         ` Sakari Ailus
  1 sibling, 2 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-14 20:12 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Sakari Ailus, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3383 bytes --]

Hi!

> On Wednesday 14 December 2016 13:24:51 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.
> > 
> > Exposure value is now in native units, which simplifies the code.
> > 
> > The patch to add device tree bindings was already acked by device tree
> > people.

> > +	default:
> > +		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
> > +			  __func__);
> 
> dev_warn_once()
...
> > +	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
> > +		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
> 
> Maybe replace it with dev_warn_once() too? That condition in WARN_ONCE
> does not look nice...
...
> > +			if (WARN(next->type != ET8EK8_REG_8BIT &&
> > +				 next->type != ET8EK8_REG_16BIT,
> > +				 "Invalid type = %d", next->type)) {
> dev_warn()
>
> > +	WARN_ON(sensor->power_count < 0);
> 
> Rather some dev_warn()? Do we need stack trace here?

I don't see what is wrong with WARN(). These are not expected to
trigger, if they do we'll fix it. If you feel strongly about this,
feel free to suggest a patch.

> > +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;
> > +}
> 
> Above code looks like re-implementation of linked-list. Does not kernel
> already provide some?

Its actually array of pointers as far as I can tell. I don't think any
helpers would be useful here.

> > +	new = et8ek8_gain_table[gain];
> > +
> > +	/* FIXME: optimise I2C writes! */
> 
> Is this FIMXE still valid?

Probably. Lets optimize it after merge.

> > +	if (sensor->power_count) {
> > +		WARN_ON(1);
> 
> Such warning is probably not useful...

It should not happen, AFAICT. That's why I'd like to know if it does.

> > +#include "et8ek8_reg.h"
> > +
> > +/*
> > + * Stingray sensor mode settings for Scooby
> > + */
> 
> Are settings for this sensor Stingray enough?

Seems to work well enough for me. If more modes are needed, we can add
them later.

> It was me who copied these sensors settings to kernel driver. And I
> chose only Stingray as this is what was needed for my N900 for
> testing... Btw, you could add somewhere my and Ivo's Signed-off and
> copyright state as we both modified et8ek8.c code...

Normally, people add copyrights when they modify the code. If you want
to do it now, please send me a patch. (With those warn_ons too, if you
care, but I think the code is fine as is).

I got code from Dmitry, so it has his signed-off.

Thanks,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 20:12       ` Pavel Machek
@ 2016-12-14 22:07         ` Pali Rohár
  2016-12-14 22:35           ` Pavel Machek
  2016-12-18 22:01         ` Sakari Ailus
  1 sibling, 1 reply; 97+ messages in thread
From: Pali Rohár @ 2016-12-14 22:07 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

On Wednesday 14 December 2016 21:12:02 Pavel Machek wrote:
> Hi!
> 
> > On Wednesday 14 December 2016 13:24:51 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.
> > > 
> > > Exposure value is now in native units, which simplifies the code.
> > > 
> > > The patch to add device tree bindings was already acked by device tree
> > > people.
> 
> > > +	default:
> > > +		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
> > > +			  __func__);
> > 
> > dev_warn_once()
> ...
> > > +	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
> > > +		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
> > 
> > Maybe replace it with dev_warn_once() too? That condition in WARN_ONCE
> > does not look nice...
> ...
> > > +			if (WARN(next->type != ET8EK8_REG_8BIT &&
> > > +				 next->type != ET8EK8_REG_16BIT,
> > > +				 "Invalid type = %d", next->type)) {
> > dev_warn()
> >
> > > +	WARN_ON(sensor->power_count < 0);
> > 
> > Rather some dev_warn()? Do we need stack trace here?
> 
> I don't see what is wrong with WARN(). These are not expected to
> trigger, if they do we'll fix it. If you feel strongly about this,
> feel free to suggest a patch.

One thing is consistency with other parts of code... On all other places
is used dev_warn and on above 4 places WARN. dev_warn automatically adds
device name for easy debugging...

Another thing is that above WARNs do not write why it is warning. It
just write that some condition is not truth...

> > > +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;
> > > +}
> > 
> > Above code looks like re-implementation of linked-list. Does not kernel
> > already provide some?
> 
> Its actually array of pointers as far as I can tell. I don't think any
> helpers would be useful here.

Ok.

> > > +	new = et8ek8_gain_table[gain];
> > > +
> > > +	/* FIXME: optimise I2C writes! */
> > 
> > Is this FIMXE still valid?
> 
> Probably. Lets optimize it after merge.
> 
> > > +	if (sensor->power_count) {
> > > +		WARN_ON(1);
> > 
> > Such warning is probably not useful...
> 
> It should not happen, AFAICT. That's why I'd like to know if it does.

I mean: warning should have better description, what happened. Such
warning for somebody who does not see this code is not useful...

> > > +#include "et8ek8_reg.h"
> > > +
> > > +/*
> > > + * Stingray sensor mode settings for Scooby
> > > + */
> > 
> > Are settings for this sensor Stingray enough?
> 
> Seems to work well enough for me. If more modes are needed, we can add
> them later.

Ok.

> > It was me who copied these sensors settings to kernel driver. And I
> > chose only Stingray as this is what was needed for my N900 for
> > testing... Btw, you could add somewhere my and Ivo's Signed-off and
> > copyright state as we both modified et8ek8.c code...
> 
> Normally, people add copyrights when they modify the code. If you want
> to do it now, please send me a patch. (With those warn_ons too, if you
> care, but I think the code is fine as is).

I think sending patch in unified diff format for such change is
overkill. Just place to header it.

-- 
Pali Rohár
pali.rohar@gmail.com

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 22:07         ` Pali Rohár
@ 2016-12-14 22:35           ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-14 22:35 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Sakari Ailus, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1479 bytes --]

Hi!

> > > Rather some dev_warn()? Do we need stack trace here?
> > 
> > I don't see what is wrong with WARN(). These are not expected to
> > trigger, if they do we'll fix it. If you feel strongly about this,
> > feel free to suggest a patch.
> 
> One thing is consistency with other parts of code... On all other places
> is used dev_warn and on above 4 places WARN. dev_warn automatically adds
> device name for easy debugging...
> 
> Another thing is that above WARNs do not write why it is warning. It
> just write that some condition is not truth...

As I said, I believe it is fine as is.

> > > It was me who copied these sensors settings to kernel driver. And I
> > > chose only Stingray as this is what was needed for my N900 for
> > > testing... Btw, you could add somewhere my and Ivo's Signed-off and
> > > copyright state as we both modified et8ek8.c code...
> > 
> > Normally, people add copyrights when they modify the code. If you want
> > to do it now, please send me a patch. (With those warn_ons too, if you
> > care, but I think the code is fine as is).
> 
> I think sending patch in unified diff format for such change is
> overkill. Just place to header it.

Then the change does not happen. Sorry, I do not know what you
modified and when, and if it is copyrightable.

									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-13 21:05   ` Pavel Machek
@ 2016-12-18 21:56     ` Sakari Ailus
  0 siblings, 0 replies; 97+ messages in thread
From: Sakari Ailus @ 2016-12-18 21:56 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

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! :-)

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 20:12       ` Pavel Machek
  2016-12-14 22:07         ` Pali Rohár
@ 2016-12-18 22:01         ` Sakari Ailus
  2016-12-20 12:37           ` Pavel Machek
  1 sibling, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-12-18 22:01 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Pali Rohár, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

Hi Pavel,

On Wed, Dec 14, 2016 at 09:12:02PM +0100, Pavel Machek wrote:
> Hi!
> 
> > On Wednesday 14 December 2016 13:24:51 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.
> > > 
> > > Exposure value is now in native units, which simplifies the code.
> > > 
> > > The patch to add device tree bindings was already acked by device tree
> > > people.
> 
> > > +	default:
> > > +		WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n",
> > > +			  __func__);
> > 
> > dev_warn_once()
> ...
> > > +	if (WARN_ONCE(cnt > ET8EK8_MAX_MSG,
> > > +		      ET8EK8_NAME ": %s: too many messages.\n", __func__)) {
> > 
> > Maybe replace it with dev_warn_once() too? That condition in WARN_ONCE
> > does not look nice...
> ...
> > > +			if (WARN(next->type != ET8EK8_REG_8BIT &&
> > > +				 next->type != ET8EK8_REG_16BIT,
> > > +				 "Invalid type = %d", next->type)) {
> > dev_warn()
> >
> > > +	WARN_ON(sensor->power_count < 0);
> > 
> > Rather some dev_warn()? Do we need stack trace here?
> 
> I don't see what is wrong with WARN(). These are not expected to
> trigger, if they do we'll fix it. If you feel strongly about this,
> feel free to suggest a patch.

I think WARN() is good. It's a driver bug and it deserves to be notified.

> 
> > > +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;
> > > +}
> > 
> > Above code looks like re-implementation of linked-list. Does not kernel
> > already provide some?
> 
> Its actually array of pointers as far as I can tell. I don't think any
> helpers would be useful here.
> 
> > > +	new = et8ek8_gain_table[gain];
> > > +
> > > +	/* FIXME: optimise I2C writes! */
> > 
> > Is this FIMXE still valid?
> 
> Probably. Lets optimize it after merge.

I guess it's been like this since 2008 or so. I guess the comment could be
simply removed, it's not a real problem.

> 
> > > +	if (sensor->power_count) {
> > > +		WARN_ON(1);
> > 
> > Such warning is probably not useful...
> 
> It should not happen, AFAICT. That's why I'd like to know if it does.
> 
> > > +#include "et8ek8_reg.h"
> > > +
> > > +/*
> > > + * Stingray sensor mode settings for Scooby
> > > + */
> > 
> > Are settings for this sensor Stingray enough?
> 
> Seems to work well enough for me. If more modes are needed, we can add
> them later.

AFAIR the module is called Stingray.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-18 22:01         ` Sakari Ailus
@ 2016-12-20 12:37           ` Pavel Machek
  2016-12-20 14:01             ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-20 12:37 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pali Rohár, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 685 bytes --]

Hi!

> I think WARN() is good. It's a driver bug and it deserves to be notified.
...
> I guess it's been like this since 2008 or so. I guess the comment could be
> simply removed, it's not a real problem.
...
> AFAIR the module is called Stingray.

Ok, so it seems we are pretty good? Can you take the patch now? Device
tree documentation is in

Subject: [PATCH v6] media: et8ek8: add device tree binding documentation

and we have

Acked-by: Rob Herring <robh@kernel.org>

on that.

Thanks and best regards,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-20 12:37           ` Pavel Machek
@ 2016-12-20 14:01             ` Sakari Ailus
  2016-12-20 22:42               ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-12-20 14:01 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Pali Rohár, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

Hi Pavel,

On Tue, Dec 20, 2016 at 01:37:56PM +0100, Pavel Machek wrote:
> Hi!
> 
> > I think WARN() is good. It's a driver bug and it deserves to be notified.
> ...
> > I guess it's been like this since 2008 or so. I guess the comment could be
> > simply removed, it's not a real problem.
> ...
> > AFAIR the module is called Stingray.
> 
> Ok, so it seems we are pretty good? Can you take the patch now? Device

Did you see this:

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

> tree documentation is in
> 
> Subject: [PATCH v6] media: et8ek8: add device tree binding documentation
> 
> and we have
> 
> Acked-by: Rob Herring <robh@kernel.org>

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-20 14:01             ` Sakari Ailus
@ 2016-12-20 22:42               ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-20 22:42 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Pali Rohár, ivo.g.dimitrov.75, sre, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1121 bytes --]

Hi!

> On Tue, Dec 20, 2016 at 01:37:56PM +0100, Pavel Machek wrote:
> > Hi!
> > 
> > > I think WARN() is good. It's a driver bug and it deserves to be notified.
> > ...
> > > I guess it's been like this since 2008 or so. I guess the comment could be
> > > simply removed, it's not a real problem.
> > ...
> > > AFAIR the module is called Stingray.
> > 
> > Ok, so it seems we are pretty good? Can you take the patch now? Device
> 
> Did you see this:
> 
> <URL:http://www.spinics.net/lists/linux-media/msg109426.html>

Yes, I did. I did add the WARN_ON() we discussed, and we are now using
the native units. Adjusting the userspace was "fun", but I agree that
native interface has some advantages, so lets keep it that way. I
truly believe we are ready now :-). 

> > tree documentation is in
> > 
> > Subject: [PATCH v6] media: et8ek8: add device tree binding documentation
> > 
> > and we have
> > 
> > Acked-by: Rob Herring <robh@kernel.org>
> 

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 12:24   ` [PATCH v5] " Pavel Machek
  2016-12-14 13:03     ` Pali Rohár
@ 2016-12-21 13:42     ` Sakari Ailus
  2016-12-21 22:42       ` Pavel Machek
  2016-12-22 10:01     ` [PATCH v6] " Pavel Machek
  2 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-12-21 13:42 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

Hi Pavel,

Thanks for the update.

On Wed, Dec 14, 2016 at 01:24:51PM +0100, Pavel Machek wrote:
...
> +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct et8ek8_sensor *sensor =
> +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_GAIN:
> +		return et8ek8_set_gain(sensor, ctrl->val);
> +
> +	case V4L2_CID_EXPOSURE:
> +	{
> +		int rows;
> +		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> +		rows = ctrl->val;
> +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> +					    swab16(rows));

Why swab16()? Doesn't the et8ek8_i2c_write_reg() already do the right thing?

16-bit writes aren't used elsewhere... and the register address and value
seem to have different endianness there, it looks like a bug to me in that
function.

-- 
Regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-21 13:42     ` Sakari Ailus
@ 2016-12-21 22:42       ` Pavel Machek
  2016-12-21 23:29         ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-21 22:42 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1292 bytes --]

Hi!

> Thanks for the update.
> 
> On Wed, Dec 14, 2016 at 01:24:51PM +0100, Pavel Machek wrote:
> ...
> > +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +	struct et8ek8_sensor *sensor =
> > +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> > +
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_GAIN:
> > +		return et8ek8_set_gain(sensor, ctrl->val);
> > +
> > +	case V4L2_CID_EXPOSURE:
> > +	{
> > +		int rows;
> > +		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > +		rows = ctrl->val;
> > +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> > +					    swab16(rows));
> 
> Why swab16()? Doesn't the et8ek8_i2c_write_reg() already do the right thing?
> 
> 16-bit writes aren't used elsewhere... and the register address and value
> seem to have different endianness there, it looks like a bug to me in that
> function.

I'm pretty sure I did not invent that swab16(). I checked, and
exposure seems to work properly. I tried swapping the bytes, but then
exposure did not seem to work. So this one seems to be correct.

Best regards,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-21 22:42       ` Pavel Machek
@ 2016-12-21 23:29         ` Sakari Ailus
  2016-12-22  9:34           ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-12-21 23:29 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

On Wed, Dec 21, 2016 at 11:42:16PM +0100, Pavel Machek wrote:
> Hi!
> 
> > Thanks for the update.
> > 
> > On Wed, Dec 14, 2016 at 01:24:51PM +0100, Pavel Machek wrote:
> > ...
> > > +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> > > +{
> > > +	struct et8ek8_sensor *sensor =
> > > +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> > > +
> > > +	switch (ctrl->id) {
> > > +	case V4L2_CID_GAIN:
> > > +		return et8ek8_set_gain(sensor, ctrl->val);
> > > +
> > > +	case V4L2_CID_EXPOSURE:
> > > +	{
> > > +		int rows;
> > > +		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > > +		rows = ctrl->val;
> > > +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> > > +					    swab16(rows));
> > 
> > Why swab16()? Doesn't the et8ek8_i2c_write_reg() already do the right thing?
> > 
> > 16-bit writes aren't used elsewhere... and the register address and value
> > seem to have different endianness there, it looks like a bug to me in that
> > function.
> 
> I'm pretty sure I did not invent that swab16(). I checked, and
> exposure seems to work properly. I tried swapping the bytes, but then
> exposure did not seem to work. So this one seems to be correct.

I can fix that too, but I have no device to test. In terms of how the
hardware is controlled there should be no difference anyway.

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v5] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-21 23:29         ` Sakari Ailus
@ 2016-12-22  9:34           ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-22  9:34 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1740 bytes --]

Hi!

> > > Thanks for the update.
> > > 
> > > On Wed, Dec 14, 2016 at 01:24:51PM +0100, Pavel Machek wrote:
> > > ...
> > > > +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
> > > > +{
> > > > +	struct et8ek8_sensor *sensor =
> > > > +		container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler);
> > > > +
> > > > +	switch (ctrl->id) {
> > > > +	case V4L2_CID_GAIN:
> > > > +		return et8ek8_set_gain(sensor, ctrl->val);
> > > > +
> > > > +	case V4L2_CID_EXPOSURE:
> > > > +	{
> > > > +		int rows;
> > > > +		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
> > > > +		rows = ctrl->val;
> > > > +		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
> > > > +					    swab16(rows));
> > > 
> > > Why swab16()? Doesn't the et8ek8_i2c_write_reg() already do the right thing?
> > > 
> > > 16-bit writes aren't used elsewhere... and the register address and value
> > > seem to have different endianness there, it looks like a bug to me in that
> > > function.
> > 
> > I'm pretty sure I did not invent that swab16(). I checked, and
> > exposure seems to work properly. I tried swapping the bytes, but then
> > exposure did not seem to work. So this one seems to be correct.
> 
> I can fix that too, but I have no device to test. In terms of how the
> hardware is controlled there should be no difference anyway.

Aha, now I understand; you want me to fix write_reg. I can do that. It
seems read_reg has similar problem, but as noone is using 16-bit
reads, so it is dormant. Ok, let me fix that.

Best regards,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-14 12:24   ` [PATCH v5] " Pavel Machek
  2016-12-14 13:03     ` Pali Rohár
  2016-12-21 13:42     ` Sakari Ailus
@ 2016-12-22 10:01     ` Pavel Machek
  2016-12-22 13:39       ` [RFC/PATCH] media: Add video bus switch Pavel Machek
  2016-12-27  9:26       ` [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor Sakari Ailus
  2 siblings, 2 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-22 10:01 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 61833 bytes --]


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 v5: fix 16bit writing so that swab() is not neccessary.

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

Exposure value is now in native units, which simplifies the code.

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..d3de087
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
@@ -0,0 +1,1515 @@
+/*
+ * et8ek8_driver.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *          Tuukka Toivonen <tuukkat76@gmail.com>
+ *          Pavel Machek <pavel@ucw.cz>
+ *
+ * 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[1] << 8) + data[0];
+
+	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) & 0xff;
+		buf[3] = (u8) (val >> 8) & 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;
+	fmt->code = reglist->mode.bus_format;
+}
+
+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.bus_format,
+		       list->mode.timeperframe.numerator,
+		       list->mode.timeperframe.denominator,
+		       (void *)meta->reglist[nlists].ptr);
+
+		nlists++;
+	}
+
+	return 0;
+}
+
+/* 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);
+
+	switch (ctrl->id) {
+	case V4L2_CID_GAIN:
+		return et8ek8_set_gain(sensor, ctrl->val);
+
+	case V4L2_CID_EXPOSURE:
+	{
+		int rows;
+		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
+		rows = ctrl->val;
+		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
+					    rows);
+	}
+
+	case V4L2_CID_TEST_PATTERN:
+		return et8ek8_set_test_pattern(sensor, ctrl->val);
+
+	case V4L2_CID_PIXEL_RATE:
+		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)
+{
+	s32 max_rows;
+
+	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);
+
+	max_rows = sensor->current_reglist->mode.max_exp;
+	{
+		u32 min = 1, max = max_rows;
+
+		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;
+	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
+	
+	u32 min, max, pixel_rate;
+	static const int S = 8;
+
+	ctrl = sensor->exposure;
+
+	min = 1;
+	max = 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_modify_range(ctrl, min, max, min, max);
+	__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S);
+}
+
+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->bus_format)
+				break;
+		}
+		if (i != npixelformat)
+			continue;
+
+		if (code->index == npixelformat) {
+			code->code = mode->bus_format;
+			return 0;
+		}
+
+		pixelformat[npixelformat] = mode->bus_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
+ */
+static int __maybe_unused 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 __maybe_unused 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);
+}
+
+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");
+		goto err_mutex;
+	}
+
+	ret = v4l2_async_register_subdev(&sensor->subdev);
+	if (ret < 0)
+		goto err_entity;
+
+	dev_dbg(dev, "initialized!\n");
+
+	return 0;
+
+err_entity:
+	media_entity_cleanup(&sensor->subdev.entity);
+err_mutex:
+	mutex_destroy(&sensor->power_lock);
+	return ret;
+}
+
+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) {
+		WARN_ON(1);
+		et8ek8_power_off(sensor);
+		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);
+	v4l2_async_unregister_subdev(&sensor->subdev);
+	media_entity_cleanup(&sensor->subdev.entity);
+	mutex_destroy(&sensor->power_lock);
+
+	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 = {
+	SET_SYSTEM_SLEEP_PM_OPS(et8ek8_suspend, 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>, Pavel Machek <pavel@ucw.cz");
+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..a79882a
--- /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 <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.
+ */
+
+#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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.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, */
+		.bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+		.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..07f1873
--- /dev/null
+++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h
@@ -0,0 +1,96 @@
+/*
+ * 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 bus_format;			/* MEDIA_BUS_FMT_ */
+	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 */

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [RFC/PATCH] media: Add video bus switch
  2016-12-22 10:01     ` [PATCH v6] " Pavel Machek
@ 2016-12-22 13:39       ` Pavel Machek
  2016-12-22 14:32         ` Sebastian Reichel
  2016-12-24 15:20         ` [PATCH] " Pavel Machek
  2016-12-27  9:26       ` [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor Sakari Ailus
  1 sibling, 2 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-22 13:39 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 12319 bytes --]


N900 contains front and back camera, with a switch between the
two. This adds support for the swich component.

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

--

I see this needs dts documentation, anything else than needs to be
done?

Thanks,
									Pavel

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index d944421..0a99e63 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -91,6 +91,16 @@ config VIDEO_OMAP3_DEBUG
 	---help---
 	  Enable debug messages on OMAP 3 camera controller driver.
 
+config VIDEO_BUS_SWITCH
+	tristate "Video Bus switch"
+	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	depends on MEDIA_CONTROLLER
+	depends on OF
+	---help---
+	  Driver for a GPIO controlled video bus switch, which is used to
+	  connect two camera sensors to the same port a the image signal
+	  processor.
+
 config VIDEO_PXA27x
 	tristate "PXA27x Quick Capture Interface driver"
 	depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 5b3cb27..a4c9eab 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/
 obj-$(CONFIG_VIDEO_OMAP3)	+= omap3isp/
 obj-$(CONFIG_VIDEO_PXA27x)	+= pxa_camera.o
 
+obj-$(CONFIG_VIDEO_BUS_SWITCH) += video-bus-switch.o
+
 obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o
 
 obj-$(CONFIG_VIDEO_VIVID)		+= vivid/
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
new file mode 100644
index 0000000..1a5d944
--- /dev/null
+++ b/drivers/media/platform/video-bus-switch.c
@@ -0,0 +1,371 @@
+/*
+ * Generic driver for video bus switches
+ *
+ * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
+ *
+ * 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.
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/gpio/consumer.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-of.h>
+
+/*
+ * TODO:
+ * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
+ */
+
+#define CSI_SWITCH_SUBDEVS 2
+#define CSI_SWITCH_PORTS 3
+
+enum vbs_state {
+	CSI_SWITCH_DISABLED,
+	CSI_SWITCH_PORT_1,
+	CSI_SWITCH_PORT_2,
+};
+
+struct vbs_src_pads {
+	struct media_entity *src;
+	int src_pad;
+};
+
+struct vbs_data {
+	struct gpio_desc *swgpio;
+	struct v4l2_subdev subdev;
+	struct v4l2_async_notifier notifier;
+	struct media_pad pads[CSI_SWITCH_PORTS];
+	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
+	enum vbs_state state;
+};
+
+struct vbs_async_subdev {
+	struct v4l2_subdev *sd;
+	struct v4l2_async_subdev asd;
+	u8 port;
+};
+
+static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
+{
+	struct v4l2_async_notifier *notifier = &pdata->notifier;
+	struct device_node *node = NULL;
+
+	notifier->subdevs = devm_kcalloc(dev, CSI_SWITCH_SUBDEVS,
+		sizeof(*notifier->subdevs), GFP_KERNEL);
+	if (!notifier->subdevs)
+		return -ENOMEM;
+
+	notifier->num_subdevs = 0;
+	while (notifier->num_subdevs < CSI_SWITCH_SUBDEVS &&
+	       (node = of_graph_get_next_endpoint(dev->of_node, node))) {
+		struct v4l2_of_endpoint vep;
+		struct vbs_async_subdev *ssd;
+
+		/* skip first port (connected to isp) */
+		v4l2_of_parse_endpoint(node, &vep);
+		if (vep.base.port == 0) {
+			struct device_node *ispnode;
+
+			ispnode = of_graph_get_remote_port_parent(node);
+			if (!ispnode) {
+				dev_warn(dev, "bad remote port parent\n");
+				return -EINVAL;
+			}
+
+			of_node_put(node);
+			continue;
+		}
+
+		ssd = devm_kzalloc(dev, sizeof(*ssd), GFP_KERNEL);
+		if (!ssd) {
+			of_node_put(node);
+			return -ENOMEM;
+		}
+
+		ssd->port = vep.base.port;
+
+		notifier->subdevs[notifier->num_subdevs] = &ssd->asd;
+
+		ssd->asd.match.of.node = of_graph_get_remote_port_parent(node);
+		of_node_put(node);
+		if (!ssd->asd.match.of.node) {
+			dev_warn(dev, "bad remote port parent\n");
+			return -EINVAL;
+		}
+
+		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+		notifier->num_subdevs++;
+	}
+
+	return notifier->num_subdevs;
+}
+
+static int vbs_registered(struct v4l2_subdev *sd)
+{
+	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
+	struct vbs_data *pdata;
+	int err;
+
+	dev_dbg(sd->dev, "registered, init notifier...\n");
+
+	pdata = v4l2_get_subdevdata(sd);
+
+	err = v4l2_async_notifier_register(v4l2_dev, &pdata->notifier);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct v4l2_subdev *vbs_get_remote_subdev(struct v4l2_subdev *sd)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	struct media_entity *src;
+
+	if (pdata->state == CSI_SWITCH_DISABLED)
+		return ERR_PTR(-ENXIO);
+
+	src = pdata->src_pads[pdata->state].src;
+
+	return media_entity_to_v4l2_subdev(src);
+}
+
+static int vbs_link_setup(struct media_entity *entity,
+			  const struct media_pad *local,
+			  const struct media_pad *remote, u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	bool enable = flags & MEDIA_LNK_FL_ENABLED;
+
+	if (local->index > CSI_SWITCH_PORTS - 1)
+		return -ENXIO;
+
+	/* no configuration needed on source port */
+	if (local->index == 0)
+		return 0;
+
+	if (!enable) {
+		if (local->index == pdata->state) {
+			pdata->state = CSI_SWITCH_DISABLED;
+
+			/* Make sure we have both cameras enabled */
+			gpiod_set_value(pdata->swgpio, 1);
+			return 0;
+		} else {
+			return -EINVAL;
+		}
+	}
+
+	/* there can only be one active sink at the same time */
+	if (pdata->state != CSI_SWITCH_DISABLED)
+		return -EBUSY;
+
+	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
+	pdata->state = local->index;
+
+	sd = vbs_get_remote_subdev(sd);
+	if (IS_ERR(sd))
+		return PTR_ERR(sd);
+
+	pdata->subdev.ctrl_handler = sd->ctrl_handler;
+
+	return 0;
+}
+
+static int vbs_subdev_notifier_bound(struct v4l2_async_notifier *async,
+				     struct v4l2_subdev *subdev,
+				     struct v4l2_async_subdev *asd)
+{
+	struct vbs_data *pdata = container_of(async,
+		struct vbs_data, notifier);
+	struct vbs_async_subdev *ssd =
+		container_of(asd, struct vbs_async_subdev, asd);
+	struct media_entity *sink = &pdata->subdev.entity;
+	struct media_entity *src = &subdev->entity;
+	int sink_pad = ssd->port;
+	int src_pad;
+
+	if (sink_pad >= sink->num_pads) {
+		dev_err(pdata->subdev.dev, "no sink pad in internal entity!\n");
+		return -EINVAL;
+	}
+
+	for (src_pad = 0; src_pad < subdev->entity.num_pads; src_pad++) {
+		if (subdev->entity.pads[src_pad].flags & MEDIA_PAD_FL_SOURCE)
+			break;
+	}
+
+	if (src_pad >= src->num_pads) {
+		dev_err(pdata->subdev.dev, "no source pad in external entity\n");
+		return -EINVAL;
+	}
+
+	pdata->src_pads[sink_pad].src = src;
+	pdata->src_pads[sink_pad].src_pad = src_pad;
+	ssd->sd = subdev;
+
+	return 0;
+}
+
+static int vbs_subdev_notifier_complete(struct v4l2_async_notifier *async)
+{
+	struct vbs_data *pdata = container_of(async, struct vbs_data, notifier);
+	struct media_entity *sink = &pdata->subdev.entity;
+	int sink_pad;
+
+	for (sink_pad = 1; sink_pad < CSI_SWITCH_PORTS; sink_pad++) {
+		struct media_entity *src = pdata->src_pads[sink_pad].src;
+		int src_pad = pdata->src_pads[sink_pad].src_pad;
+		int err;
+
+		err = media_create_pad_link(src, src_pad, sink, sink_pad, 0);
+		if (err < 0)
+			return err;
+
+		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
+			src->name, src_pad, sink->name, sink_pad);
+	}
+
+	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);
+}
+
+static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
+
+	if (IS_ERR(subdev))
+		return PTR_ERR(subdev);
+
+	return v4l2_subdev_call(subdev, video, s_stream, enable);
+}
+
+static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
+	.registered = &vbs_registered,
+};
+
+static const struct media_entity_operations vbs_media_ops = {
+	.link_setup = vbs_link_setup,
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops vbs_video_ops = {
+	.s_stream = vbs_s_stream,
+};
+
+static const struct v4l2_subdev_ops vbs_ops = {
+	.video = &vbs_video_ops,
+};
+
+static int video_bus_switch_probe(struct platform_device *pdev)
+{
+	struct vbs_data *pdata;
+	int err = 0;
+
+	/* platform data */
+	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		printk("video-bus-switch: not enough memory\n");
+		return -ENOMEM;
+	}
+	platform_set_drvdata(pdev, pdata);
+
+	/* switch gpio */
+	pdata->swgpio = devm_gpiod_get(&pdev->dev, "switch", GPIOD_OUT_HIGH);
+	if (IS_ERR(pdata->swgpio)) {
+		err = PTR_ERR(pdata->swgpio);
+		dev_err(&pdev->dev, "Failed to request gpio: %d\n", err);
+		return err;
+	}
+
+	/* find sub-devices */
+	err = vbs_of_parse_nodes(&pdev->dev, pdata);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to parse nodes: %d\n", err);
+		return err;
+	}
+
+	pdata->state = CSI_SWITCH_DISABLED;
+	pdata->notifier.bound = vbs_subdev_notifier_bound;
+	pdata->notifier.complete = vbs_subdev_notifier_complete;
+
+	/* setup subdev */
+	pdata->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+	pdata->pads[1].flags = MEDIA_PAD_FL_SINK;
+	pdata->pads[2].flags = MEDIA_PAD_FL_SINK;
+
+	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
+	pdata->subdev.dev = &pdev->dev;
+	pdata->subdev.owner = pdev->dev.driver->owner;
+	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
+	v4l2_set_subdevdata(&pdata->subdev, pdata);
+	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
+	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;
+	pdata->subdev.entity.ops = &vbs_media_ops;
+	pdata->subdev.internal_ops = &vbs_internal_ops;
+	err = media_entity_pads_init(&pdata->subdev.entity, CSI_SWITCH_PORTS,
+				pdata->pads);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to init media entity: %d\n", err);
+		return err;
+	}
+
+	/* register subdev */
+	err = v4l2_async_register_subdev(&pdata->subdev);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
+		media_entity_cleanup(&pdata->subdev.entity);
+		return err;
+	}
+
+	dev_info(&pdev->dev, "video-bus-switch registered\n");
+
+	return 0;
+}
+
+static int video_bus_switch_remove(struct platform_device *pdev)
+{
+	struct vbs_data *pdata = platform_get_drvdata(pdev);
+
+	v4l2_async_notifier_unregister(&pdata->notifier);
+	v4l2_async_unregister_subdev(&pdata->subdev);
+	media_entity_cleanup(&pdata->subdev.entity);
+
+	return 0;
+}
+
+static const struct of_device_id video_bus_switch_of_match[] = {
+	{ .compatible = "video-bus-switch" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, video_bus_switch_of_match);
+
+static struct platform_driver video_bus_switch_driver = {
+	.driver = {
+		.name	= "video-bus-switch",
+		.of_match_table = video_bus_switch_of_match,
+	},
+	.probe		= video_bus_switch_probe,
+	.remove		= video_bus_switch_remove,
+};
+
+module_platform_driver(video_bus_switch_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("Video Bus Switch");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:video-bus-switch");


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 13:39       ` [RFC/PATCH] media: Add video bus switch Pavel Machek
@ 2016-12-22 14:32         ` Sebastian Reichel
  2016-12-22 20:53           ` Pavel Machek
  2016-12-22 22:42           ` Pavel Machek
  2016-12-24 15:20         ` [PATCH] " Pavel Machek
  1 sibling, 2 replies; 97+ messages in thread
From: Sebastian Reichel @ 2016-12-22 14:32 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2075 bytes --]

Hi Pavel,

On Thu, Dec 22, 2016 at 02:39:38PM +0100, Pavel Machek wrote:
> N900 contains front and back camera, with a switch between the
> two. This adds support for the swich component.
> 
> Signed-off-by: Sebastian Reichel <sre@kernel.org>
> Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> --
> 
> I see this needs dts documentation, anything else than needs to be
> done?

Yes. This driver takes care of the switch gpio, but the cameras also
use different bus settings. Currently omap3isp gets the bus-settings
from the link connected to the CCP2 port in DT at probe time (*).

So there are two general problems:

1. Settings must be applied before the streaming starts instead of
at probe time, since the settings may change (based one the selected
camera). That should be fairly easy to implement by just moving the
code to the s_stream callback as far as I can see.

2. omap3isp should try to get the bus settings from using a callback
in the connected driver instead of loading it from DT. Then the
video-bus-switch can load the bus-settings from its downstream links
in DT and propagate the correct ones to omap3isp based on the
selected port. The DT loading part should actually remain in omap3isp
as fallback, in case it does not find a callback in the connected driver.
That way everything is backward compatible and the DT variant is
nice for 1-on-1 scenarios.

Apart from that Sakari told me at ELCE, that the port numbers
should be reversed to match the order of other drivers. That's
obviously very easy to do :)

Regarding the binding document. I actually did write one:
https://git.kernel.org/cgit/linux/kernel/git/sre/linux-n900.git/commit/?h=n900-camera&id=81e74af53fe6d180616b05792f78badc615e871f

So all in all it shouldn't be that hard to implement the remaining
bits.

(*) Actually it does not for CCP2, but there are some old patches
from Sakari adding it for CCP2. It is implemented for parallel port
and CSI in this way.

-- Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 14:32         ` Sebastian Reichel
@ 2016-12-22 20:53           ` Pavel Machek
  2016-12-22 23:11             ` Sebastian Reichel
  2016-12-22 22:42           ` Pavel Machek
  1 sibling, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-22 20:53 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2224 bytes --]

Hi!

> > I see this needs dts documentation, anything else than needs to be
> > done?
> 
> Yes. This driver takes care of the switch gpio, but the cameras also
> use different bus settings. Currently omap3isp gets the bus-settings
> from the link connected to the CCP2 port in DT at probe time (*).
> 
> So there are two general problems:
> 
> 1. Settings must be applied before the streaming starts instead of
> at probe time, since the settings may change (based one the selected
> camera). That should be fairly easy to implement by just moving the
> code to the s_stream callback as far as I can see.

Ok, I see, where "the code" is basically in vbs_link_setup, right?

> 2. omap3isp should try to get the bus settings from using a callback
> in the connected driver instead of loading it from DT. Then the
> video-bus-switch can load the bus-settings from its downstream links
> in DT and propagate the correct ones to omap3isp based on the
> selected port. The DT loading part should actually remain in omap3isp
> as fallback, in case it does not find a callback in the connected driver.
> That way everything is backward compatible and the DT variant is
> nice for 1-on-1 scenarios.

So basically... (struct isp_bus_cfg *) isd->bus would change in
isp_pipeline_enable()...? 

> Apart from that Sakari told me at ELCE, that the port numbers
> should be reversed to match the order of other drivers. That's
> obviously very easy to do :)

Ok, I guess that can come later :-).

> Regarding the binding document. I actually did write one:
> https://git.kernel.org/cgit/linux/kernel/git/sre/linux-n900.git/commit/?h=n900-camera&id=81e74af53fe6d180616b05792f78badc615e871f

Thanks, got it.

> So all in all it shouldn't be that hard to implement the remaining
> bits.

:-).

> (*) Actually it does not for CCP2, but there are some old patches
> from Sakari adding it for CCP2. It is implemented for parallel port
> and CSI in this way.

I think I got the patches in my tree. Camera currently works for me.

Thanks,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 14:32         ` Sebastian Reichel
  2016-12-22 20:53           ` Pavel Machek
@ 2016-12-22 22:42           ` Pavel Machek
  2016-12-22 23:40             ` Sebastian Reichel
  1 sibling, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-22 22:42 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 6562 bytes --]

On Thu 2016-12-22 15:32:44, Sebastian Reichel wrote:
> Hi Pavel,
> 
> On Thu, Dec 22, 2016 at 02:39:38PM +0100, Pavel Machek wrote:
> > N900 contains front and back camera, with a switch between the
> > two. This adds support for the swich component.
> > 
> > Signed-off-by: Sebastian Reichel <sre@kernel.org>
> > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> > 
> > --
> > 
> > I see this needs dts documentation, anything else than needs to be
> > done?
> 
> Yes. This driver takes care of the switch gpio, but the cameras also
> use different bus settings. Currently omap3isp gets the bus-settings
> from the link connected to the CCP2 port in DT at probe time (*).
> 
> So there are two general problems:
> 
> 1. Settings must be applied before the streaming starts instead of
> at probe time, since the settings may change (based one the selected
> camera). That should be fairly easy to implement by just moving the
> code to the s_stream callback as far as I can see.
> 
> 2. omap3isp should try to get the bus settings from using a callback
> in the connected driver instead of loading it from DT. Then the
> video-bus-switch can load the bus-settings from its downstream links
> in DT and propagate the correct ones to omap3isp based on the
> selected port. The DT loading part should actually remain in omap3isp
> as fallback, in case it does not find a callback in the connected driver.
> That way everything is backward compatible and the DT variant is
> nice for 1-on-1 scenarios.

So... did I understood it correctly? (Needs some work to be done...)

diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c
index 45c69ed..1f44da1 100644
--- a/drivers/media/platform/omap3isp/isp.c
+++ b/drivers/media/platform/omap3isp/isp.c
@@ -702,6 +704,33 @@ static int isp_pipeline_enable(struct isp_pipeline *pipe,
 
 	entity = &pipe->output->video.entity;
 	while (1) {
+		struct v4l2_of_endpoint vep;
+		pad = &entity->pads[0];
+		if (!(pad->flags & MEDIA_PAD_FL_SINK))
+			break;
+
+		pad = media_entity_remote_pad(pad);
+		if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+			break;
+
+		entity = pad->entity;
+		subdev = media_entity_to_v4l2_subdev(entity);
+
+	       	printk("Entity = %p\n", entity);
+		ret = v4l2_subdev_call(subdev, video, g_endpoint_config, &vep);
+		/* Is there better method than walking a list?
+		   Can I easily get dev and isd pointers here? */
+#if 0
+		if (ret == 0) {
+			printk("success\n");
+			/* notifier->subdevs[notifier->num_subdevs] ... contains isd */
+			isp_endpoint_to_buscfg(dev, vep, isd->bus);
+		}
+#endif
+	}
+
+	entity = &pipe->output->video.entity;
+	while (1) {
 		pad = &entity->pads[0];
 		if (!(pad->flags & MEDIA_PAD_FL_SINK))
 			break;
@@ -2099,27 +2128,8 @@ static void isp_of_parse_node_csi2(struct device *dev,
 	buscfg->bus.csi2.crc = 1;
 }
 
-static int isp_of_parse_node_endpoint(struct device *dev,
-				      struct device_node *node,
-				      struct isp_async_subdev *isd)
+static int isp_endpoint_to_buscfg(struct device *dev, struct v4l2_of_endpoint vep, struct isp_bus_cfg *buscfg)
 {
-	struct isp_bus_cfg *buscfg;
-	struct v4l2_of_endpoint vep;
-	int ret;
-
-	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
-	if (!isd->bus)
-		return -ENOMEM;
-
-	buscfg = isd->bus;
-
-	ret = v4l2_of_parse_endpoint(node, &vep);
-	if (ret)
-		return ret;
-
-	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
-		vep.base.port);
-
 	switch (vep.base.port) {
 	case ISP_OF_PHY_PARALLEL:
 		buscfg->interface = ISP_INTERFACE_PARALLEL;
@@ -2147,10 +2157,35 @@ static int isp_of_parse_node_endpoint(struct device *dev,
 		break;
 
 	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int isp_of_parse_node_endpoint(struct device *dev,
+				      struct device_node *node,
+				      struct isp_async_subdev *isd)
+{
+	struct isp_bus_cfg *buscfg;
+	struct v4l2_of_endpoint vep;
+	int ret;
+
+	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
+	if (!isd->bus)
+		return -ENOMEM;
+
+	buscfg = isd->bus;
+
+	ret = v4l2_of_parse_endpoint(node, &vep);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
+		vep.base.port);
+
+	if (isp_endpoint_to_buscfg(dev, vep, buscfg))
 		dev_warn(dev, "%s: invalid interface %u\n", node->full_name,
 			 vep.base.port);
-		break;
-	}
 
 	return 0;
 }
@@ -2262,6 +2297,10 @@ static int isp_of_parse_nodes(struct device *dev,
 	}
 
 	return notifier->num_subdevs;
+
+error:
+	of_node_put(node);
+	return -EINVAL;
 }
 
 static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
index 1a5d944..3a2d442 100644
--- a/drivers/media/platform/video-bus-switch.c
+++ b/drivers/media/platform/video-bus-switch.c
@@ -247,12 +247,21 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
 {
 	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
 
+	/* FIXME: we need to set the GPIO here */
+
 	if (IS_ERR(subdev))
 		return PTR_ERR(subdev);
 
 	return v4l2_subdev_call(subdev, video, s_stream, enable);
 }
 
+static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct isp_bus_cfg *cfg)
+{
+	printk("vbs_g_endpoint_config...\n");
+	return 0;
+}
+
+
 static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
 	.registered = &vbs_registered,
 };
@@ -265,6 +274,7 @@ static const struct media_entity_operations vbs_media_ops = {
 /* subdev video operations */
 static const struct v4l2_subdev_video_ops vbs_video_ops = {
 	.s_stream = vbs_s_stream,
+	.g_endpoint_config = vbs_g_endpoint_config,
 };
 
 static const struct v4l2_subdev_ops vbs_ops = {
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..30457b0 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -415,6 +415,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**





-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 20:53           ` Pavel Machek
@ 2016-12-22 23:11             ` Sebastian Reichel
  0 siblings, 0 replies; 97+ messages in thread
From: Sebastian Reichel @ 2016-12-22 23:11 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3017 bytes --]

Hi,

On Thu, Dec 22, 2016 at 09:53:17PM +0100, Pavel Machek wrote:
> > 1. Settings must be applied before the streaming starts instead of
> > at probe time, since the settings may change (based one the selected
> > camera). That should be fairly easy to implement by just moving the
> > code to the s_stream callback as far as I can see.
> 
> Ok, I see, where "the code" is basically in vbs_link_setup, right?

I'm not talking about the video bus switch, but about omap3isp.
omap3isp must update the buscfg registers when it starts streaming
instead of at probe time. I just checked and it actually seems to do
so already. So the problem is only updating the buscfg inside of
ccp2_s_stream() before writing the device registers. See next
paragraph for more details.

> > 2. omap3isp should try to get the bus settings from using a callback
> > in the connected driver instead of loading it from DT. Then the
> > video-bus-switch can load the bus-settings from its downstream links
> > in DT and propagate the correct ones to omap3isp based on the
> > selected port. The DT loading part should actually remain in omap3isp
> > as fallback, in case it does not find a callback in the connected driver.
> > That way everything is backward compatible and the DT variant is
> > nice for 1-on-1 scenarios.
> 
> So basically... (struct isp_bus_cfg *) isd->bus would change in
> isp_pipeline_enable()...? 

isp_of_parse_node_csi1(), which is called by isp_of_parse_node()
inits buscfg using DT information. This does not work for N900,
which needs two different buscfg settings based on the selected
camera. I suggest to add a callback to the subdevice instead.

So something like pseudocode is needed in ccp2_s_stream():

/* get current buscfg */
if (subdevice->get_buscfg)
    buscfg = subdevice->get_buscfg();
else
    buscfg = isp_of_parse_node_csi1();

/* configure device registers */
ccp2_if_configure(buscfg);

This new callback must be implemented in the video-bus-switch,
so that it returns the buscfg based upon the selected camera.

> > Apart from that Sakari told me at ELCE, that the port numbers
> > should be reversed to match the order of other drivers. That's
> > obviously very easy to do :)
> 
> Ok, I guess that can come later :-).
> 
> > Regarding the binding document. I actually did write one:
> > https://git.kernel.org/cgit/linux/kernel/git/sre/linux-n900.git/commit/?h=n900-camera&id=81e74af53fe6d180616b05792f78badc615e871f
> 
> Thanks, got it.
> 
> > So all in all it shouldn't be that hard to implement the remaining
> > bits.
> 
> :-).
> 
> > (*) Actually it does not for CCP2, but there are some old patches
> > from Sakari adding it for CCP2. It is implemented for parallel port
> > and CSI in this way.
> 
> I think I got the patches in my tree. Camera currently works for me.

If you have working camera you have the CCP2 DT patches in your tree.
They are not yet mainline, though. As far as I can see.

-- Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 22:42           ` Pavel Machek
@ 2016-12-22 23:40             ` Sebastian Reichel
  2016-12-23 11:42               ` Pavel Machek
                                 ` (2 more replies)
  0 siblings, 3 replies; 97+ messages in thread
From: Sebastian Reichel @ 2016-12-22 23:40 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3067 bytes --]

Hi,

On Thu, Dec 22, 2016 at 11:42:26PM +0100, Pavel Machek wrote:
> On Thu 2016-12-22 15:32:44, Sebastian Reichel wrote:
> > Hi Pavel,
> > 
> > On Thu, Dec 22, 2016 at 02:39:38PM +0100, Pavel Machek wrote:
> > > N900 contains front and back camera, with a switch between the
> > > two. This adds support for the swich component.
> > > 
> > > Signed-off-by: Sebastian Reichel <sre@kernel.org>
> > > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> > > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> > > 
> > > --
> > > 
> > > I see this needs dts documentation, anything else than needs to be
> > > done?
> > 
> > Yes. This driver takes care of the switch gpio, but the cameras also
> > use different bus settings. Currently omap3isp gets the bus-settings
> > from the link connected to the CCP2 port in DT at probe time (*).
> > 
> > So there are two general problems:
> > 
> > 1. Settings must be applied before the streaming starts instead of
> > at probe time, since the settings may change (based one the selected
> > camera). That should be fairly easy to implement by just moving the
> > code to the s_stream callback as far as I can see.
> > 
> > 2. omap3isp should try to get the bus settings from using a callback
> > in the connected driver instead of loading it from DT. Then the
> > video-bus-switch can load the bus-settings from its downstream links
> > in DT and propagate the correct ones to omap3isp based on the
> > selected port. The DT loading part should actually remain in omap3isp
> > as fallback, in case it does not find a callback in the connected driver.
> > That way everything is backward compatible and the DT variant is
> > nice for 1-on-1 scenarios.
> 
> So... did I understood it correctly? (Needs some work to be done...)

I had a quick look and yes, that's basically what I had in mind to
solve the issue. If callback is not available the old system should
be used of course.

> [...]
>
>  static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
> diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
> index 1a5d944..3a2d442 100644
> --- a/drivers/media/platform/video-bus-switch.c
> +++ b/drivers/media/platform/video-bus-switch.c
> @@ -247,12 +247,21 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
>  {
>  	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
>  
> +	/* FIXME: we need to set the GPIO here */
> +

The gpio is set when the pad is selected, so no need to do it again.
The gpio selection actually works with your branch (assuming its
based on Ivo's).

>  	if (IS_ERR(subdev))
>  		return PTR_ERR(subdev);
>  
>  	return v4l2_subdev_call(subdev, video, s_stream, enable);
>  }
>  
> +static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct isp_bus_cfg *cfg)
> +{
> +	printk("vbs_g_endpoint_config...\n");
> +	return 0;
> +}

Would be nice to find something more abstract than isp_bus_cfg,
which is specific to omap3isp.

-- Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 23:40             ` Sebastian Reichel
@ 2016-12-23 11:42               ` Pavel Machek
  2016-12-23 18:53                 ` Ivaylo Dimitrov
  2016-12-23 20:56               ` Pavel Machek
  2016-12-24 14:26               ` Pavel Machek
  2 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-23 11:42 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 9348 bytes --]

Hi!

> > [...]
> >
> >  static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
> > diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
> > index 1a5d944..3a2d442 100644
> > --- a/drivers/media/platform/video-bus-switch.c
> > +++ b/drivers/media/platform/video-bus-switch.c
> > @@ -247,12 +247,21 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
> >  {
> >  	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
> >  
> > +	/* FIXME: we need to set the GPIO here */
> > +
> 
> The gpio is set when the pad is selected, so no need to do it again.
> The gpio selection actually works with your branch (assuming its
> based on Ivo's).

Yes. I did not notice... is there actually some interface to select
the camera from userland?

> >  	if (IS_ERR(subdev))
> >  		return PTR_ERR(subdev);
> >  
> >  	return v4l2_subdev_call(subdev, video, s_stream, enable);
> >  }
> >  
> > +static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct isp_bus_cfg *cfg)
> > +{
> > +	printk("vbs_g_endpoint_config...\n");
> > +	return 0;
> > +}
> 
> Would be nice to find something more abstract than isp_bus_cfg,
> which is specific to omap3isp.

Yes, that should be doable.

diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c
index 45c69ed..f0aa8cd 100644
--- a/drivers/media/platform/omap3isp/isp.c
+++ b/drivers/media/platform/omap3isp/isp.c
@@ -2024,44 +2054,51 @@ enum isp_of_phy {
 	ISP_OF_PHY_CSIPHY2,
 };
 
-static void isp_of_parse_node_csi1(struct device *dev,
-				   struct isp_bus_cfg *buscfg,
+void __isp_of_parse_node_csi1(struct device *dev,
+				   struct isp_ccp2_cfg *buscfg,
 				   struct v4l2_of_endpoint *vep)
 {
-	if (vep->base.port == ISP_OF_PHY_CSIPHY1)
-		buscfg->interface = ISP_INTERFACE_CCP2B_PHY1;
-	else
-		buscfg->interface = ISP_INTERFACE_CCP2B_PHY2;
-	buscfg->bus.ccp2.lanecfg.clk.pos = vep->bus.mipi_csi1.clock_lane;
-	buscfg->bus.ccp2.lanecfg.clk.pol =
+	buscfg->lanecfg.clk.pos = vep->bus.mipi_csi1.clock_lane;
+	buscfg->lanecfg.clk.pol =
 		vep->bus.mipi_csi1.lane_polarity[0];
 	dev_dbg(dev, "clock lane polarity %u, pos %u\n",
-		buscfg->bus.ccp2.lanecfg.clk.pol,
-		buscfg->bus.ccp2.lanecfg.clk.pos);
+		buscfg->lanecfg.clk.pol,
+		buscfg->lanecfg.clk.pos);
 
-	buscfg->bus.ccp2.lanecfg.data[0].pos = vep->bus.mipi_csi2.data_lanes[0];
-	buscfg->bus.ccp2.lanecfg.data[0].pol =
+	buscfg->lanecfg.data[0].pos = vep->bus.mipi_csi2.data_lanes[0];
+	buscfg->lanecfg.data[0].pol =
 		vep->bus.mipi_csi2.lane_polarities[1];
 	dev_dbg(dev, "data lane polarity %u, pos %u\n",
-		buscfg->bus.ccp2.lanecfg.data[0].pol,
-		buscfg->bus.ccp2.lanecfg.data[0].pos);
+		buscfg->lanecfg.data[0].pol,
+		buscfg->lanecfg.data[0].pos);
 
-	buscfg->bus.ccp2.strobe_clk_pol = vep->bus.mipi_csi1.clock_inv;
-	buscfg->bus.ccp2.phy_layer = vep->bus.mipi_csi1.strobe;
-	buscfg->bus.ccp2.ccp2_mode = vep->bus_type == V4L2_MBUS_CCP2;
+	buscfg->strobe_clk_pol = vep->bus.mipi_csi1.clock_inv;
+	buscfg->phy_layer = vep->bus.mipi_csi1.strobe;
+	buscfg->ccp2_mode = vep->bus_type == V4L2_MBUS_CCP2;
 
 	dev_dbg(dev, "clock_inv %u strobe %u ccp2 %u\n",
-		buscfg->bus.ccp2.strobe_clk_pol,
-		buscfg->bus.ccp2.phy_layer,
-		buscfg->bus.ccp2.ccp2_mode);
+		buscfg->strobe_clk_pol,
+		buscfg->phy_layer,
+		buscfg->ccp2_mode);
 	/*
 	 * FIXME: now we assume the CRC is always there.
 	 * Implement a way to obtain this information from the
 	 * sensor. Frame descriptors, perhaps?
 	 */
-	buscfg->bus.ccp2.crc = 1;
+	buscfg->crc = 1;
 
-	buscfg->bus.ccp2.vp_clk_pol = 1;
+	buscfg->vp_clk_pol = 1;
+}
+	
+static void isp_of_parse_node_csi1(struct device *dev,
+				   struct isp_bus_cfg *buscfg,
+				   struct v4l2_of_endpoint *vep)
+{
+	if (vep->base.port == ISP_OF_PHY_CSIPHY1)
+		buscfg->interface = ISP_INTERFACE_CCP2B_PHY1;
+	else
+		buscfg->interface = ISP_INTERFACE_CCP2B_PHY2;
+	__isp_of_parse_node_csi1(dev, &buscfg->bus.ccp2, vep);
 }
 
 static void isp_of_parse_node_csi2(struct device *dev,
@@ -2099,27 +2136,8 @@ static void isp_of_parse_node_csi2(struct device *dev,
 	buscfg->bus.csi2.crc = 1;
 }
 
-static int isp_of_parse_node_endpoint(struct device *dev,
-				      struct device_node *node,
-				      struct isp_async_subdev *isd)
+static int isp_endpoint_to_buscfg(struct device *dev, struct v4l2_of_endpoint vep, struct isp_bus_cfg *buscfg)
 {
-	struct isp_bus_cfg *buscfg;
-	struct v4l2_of_endpoint vep;
-	int ret;
-
-	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
-	if (!isd->bus)
-		return -ENOMEM;
-
-	buscfg = isd->bus;
-
-	ret = v4l2_of_parse_endpoint(node, &vep);
-	if (ret)
-		return ret;
-
-	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
-		vep.base.port);
-
 	switch (vep.base.port) {
 	case ISP_OF_PHY_PARALLEL:
 		buscfg->interface = ISP_INTERFACE_PARALLEL;
@@ -2147,10 +2165,35 @@ static int isp_of_parse_node_endpoint(struct device *dev,
 		break;
 
 	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int isp_of_parse_node_endpoint(struct device *dev,
+				      struct device_node *node,
+				      struct isp_async_subdev *isd)
+{
+	struct isp_bus_cfg *buscfg;
+	struct v4l2_of_endpoint vep;
+	int ret;
+
+	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
+	if (!isd->bus)
+		return -ENOMEM;
+
+	buscfg = isd->bus;
+
+	ret = v4l2_of_parse_endpoint(node, &vep);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
+		vep.base.port);
+
+	if (isp_endpoint_to_buscfg(dev, vep, buscfg))
 		dev_warn(dev, "%s: invalid interface %u\n", node->full_name,
 			 vep.base.port);
-		break;
-	}
 
 	return 0;
 }
diff --git a/drivers/media/platform/omap3isp/ispccp2.c b/drivers/media/platform/omap3isp/ispccp2.c
index 2d1463a..a6763b3 100644
--- a/drivers/media/platform/omap3isp/ispccp2.c
+++ b/drivers/media/platform/omap3isp/ispccp2.c
@@ -23,6 +23,8 @@
 #include <linux/regulator/consumer.h>
 #include <linux/regmap.h>
 
+#include <media/v4l2-of.h>
+
 #include "isp.h"
 #include "ispreg.h"
 #include "ispccp2.h"
@@ -169,6 +171,7 @@ static int ccp2_if_enable(struct isp_ccp2_device *ccp2, u8 enable)
 
 		pad = media_entity_remote_pad(&ccp2->pads[CCP2_PAD_SINK]);
 		sensor = media_entity_to_v4l2_subdev(pad->entity);
+		/* Struct isp_bus_cfg has union inside */ 
 		buscfg = &((struct isp_bus_cfg *)sensor->host_priv)->bus.ccp2;
 
 
@@ -369,6 +372,9 @@ static void ccp2_lcx_config(struct isp_ccp2_device *ccp2,
 	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQENABLE, val);
 }
 
+void __isp_of_parse_node_csi1(struct device *dev,
+			      struct isp_ccp2_cfg *buscfg,
+			      struct v4l2_of_endpoint *vep);
 /*
  * ccp2_if_configure - Configure ccp2 with data from sensor
  * @ccp2: Pointer to ISP CCP2 device
@@ -390,6 +396,21 @@ static int ccp2_if_configure(struct isp_ccp2_device *ccp2)
 	sensor = media_entity_to_v4l2_subdev(pad->entity);
 	buscfg = sensor->host_priv;
 
+	{
+		struct v4l2_subdev *subdev2;
+		subdev2 = media_entity_to_v4l2_subdev(pad->entity);
+		struct v4l2_of_endpoint vep;
+
+		printk("if_configure...\n");
+		printk("2: %p\n", subdev2);
+		ret = v4l2_subdev_call(subdev2, video, g_endpoint_config, &vep);
+		if (ret == 0) {
+			printk("Success: have configuration\n");
+			__isp_of_parse_node_csi1(NULL, &buscfg->bus.ccp2, &vep);
+			printk("Configured ok?\n");
+		}
+	}
+
 	ret = ccp2_phyif_config(ccp2, &buscfg->bus.ccp2);
 	if (ret < 0)
 		return ret;
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
index 1a5d944..3a2d442 100644
--- a/drivers/media/platform/video-bus-switch.c
+++ b/drivers/media/platform/video-bus-switch.c
@@ -247,12 +247,21 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
 {
 	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
 
+
+
 	if (IS_ERR(subdev))
 		return PTR_ERR(subdev);
 
 	return v4l2_subdev_call(subdev, video, s_stream, enable);
 }
 
+static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct isp_bus_cfg *cfg)
+{
+	printk("vbs_g_endpoint_config...\n");
+	return 0;
+}
+
+
 static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
 	.registered = &vbs_registered,
 };
@@ -265,6 +274,7 @@ static const struct media_entity_operations vbs_media_ops = {
 /* subdev video operations */
 static const struct v4l2_subdev_video_ops vbs_video_ops = {
 	.s_stream = vbs_s_stream,
+	.g_endpoint_config = vbs_g_endpoint_config,
 };
 
 static const struct v4l2_subdev_ops vbs_ops = {
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..30457b0 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -415,6 +415,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-23 11:42               ` Pavel Machek
@ 2016-12-23 18:53                 ` Ivaylo Dimitrov
  0 siblings, 0 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2016-12-23 18:53 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sebastian Reichel, Sakari Ailus, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

Hi,

On 23.12.2016 13:42, Pavel Machek wrote:
> Hi!
>
>>> [...]
>>>
>>>  static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
>>> diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
>>> index 1a5d944..3a2d442 100644
>>> --- a/drivers/media/platform/video-bus-switch.c
>>> +++ b/drivers/media/platform/video-bus-switch.c
>>> @@ -247,12 +247,21 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
>>>  {
>>>  	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
>>>
>>> +	/* FIXME: we need to set the GPIO here */
>>> +
>>
>> The gpio is set when the pad is selected, so no need to do it again.
>> The gpio selection actually works with your branch (assuming its
>> based on Ivo's).
>
> Yes. I did not notice... is there actually some interface to select
> the camera from userland?
>

When you construct the pipeline, you enable the port you need in vbs, so 
the camera is selected.

I used (similar to)this by the time I played with cameras:

front:

export LD_LIBRARY_PATH=./
./media-ctl -r
./media-ctl -l '"vs6555 binner 2-0010":1 -> "video-bus-switch":2 [1]'
./media-ctl -l '"video-bus-switch":0 -> "OMAP3 ISP CCP2":0 [1]'
./media-ctl -l '"OMAP3 ISP CCP2":1 -> "OMAP3 ISP CCDC":0 [1]'
./media-ctl -l '"OMAP3 ISP CCDC":2 -> "OMAP3 ISP preview":0 [1]'
./media-ctl -l '"OMAP3 ISP preview":1 -> "OMAP3 ISP resizer":0 [1]'
./media-ctl -l '"OMAP3 ISP resizer":1 -> "OMAP3 ISP resizer output":0 [1]'
./media-ctl -V '"vs6555 pixel array 2-0010":0 [SGRBG10/648x488 
(0,0)/648x488 (0,0)/648x488]'
./media-ctl -V '"vs6555 binner 2-0010":1 [SGRBG10/648x488 (0,0)/648x488 
(0,0)/648x488]'
./media-ctl -V '"OMAP3 ISP CCP2":0 [SGRBG10 648x488]'
./media-ctl -V '"OMAP3 ISP CCP2":1 [SGRBG10 648x488]'
./media-ctl -V '"OMAP3 ISP CCDC":2 [SGRBG10 648x488]'
./media-ctl -V '"OMAP3 ISP preview":1 [UYVY 648x488]'
./media-ctl -V '"OMAP3 ISP resizer":1 [UYVY 656x488]'


mplayer -tv 
driver=v4l2:width=656:height=488:outfmt=uyvy:device=/dev/video6 -vo xv 
-vf screenshot tv://

back:

export LD_LIBRARY_PATH=./
./media-ctl -r
./media-ctl -l '"et8ek8 3-003e":0 -> "video-bus-switch":1 [1]'
./media-ctl -l '"video-bus-switch":0 -> "OMAP3 ISP CCP2":0 [1]'
./media-ctl -l '"OMAP3 ISP CCP2":1 -> "OMAP3 ISP CCDC":0 [1]'
./media-ctl -l '"OMAP3 ISP CCDC":2 -> "OMAP3 ISP preview":0 [1]'
./media-ctl -l '"OMAP3 ISP preview":1 -> "OMAP3 ISP resizer":0 [1]'
./media-ctl -l '"OMAP3 ISP resizer":1 -> "OMAP3 ISP resizer output":0 [1]'

./media-ctl -V '"et8ek8 3-003e":0 [SGRBG10 864x656]'
./media-ctl -V '"OMAP3 ISP CCP2":0 [SGRBG10 864x656]'
./media-ctl -V '"OMAP3 ISP CCP2":1 [SGRBG10 864x656]'
./media-ctl -V '"OMAP3 ISP CCDC":2 [SGRBG10 864x656]'
./media-ctl -V '"OMAP3 ISP preview":1 [UYVY 864x656]'
./media-ctl -V '"OMAP3 ISP resizer":1 [UYVY 800x480]'

mplayer -tv 
driver=v4l2:width=800:height=480:outfmt=uyvy:device=/dev/video6 -vo xv 
-vf screenshot tv://


or IOW:

./media-ctl -l '"vs6555 binner 2-0010":1 -> "video-bus-switch":2 [1]' 
switches GPIO to use front camera

and
./media-ctl -l '"video-bus-switch":0 -> "OMAP3 ISP CCP2":0 [1]'

for back camera


Ivo

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 23:40             ` Sebastian Reichel
  2016-12-23 11:42               ` Pavel Machek
@ 2016-12-23 20:56               ` Pavel Machek
  2016-12-24 14:26               ` Pavel Machek
  2 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-23 20:56 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 12755 bytes --]

Hi!

Ok, this should be closer to something working. (But it does not work,
yet).

								Pavel
								

diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
new file mode 100644
index 0000000..1b9f8e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
@@ -0,0 +1,63 @@
+Video Bus Switch Binding
+========================
+
+This is a binding for a gpio controlled switch for camera interfaces. Such a
+device is used on some embedded devices to connect two cameras to the same
+interface of a image signal processor.
+
+Required properties
+===================
+
+compatible	: must contain "video-bus-switch"
+switch-gpios	: GPIO specifier for the gpio, which can toggle the
+		  selected camera. The GPIO should be configured, so
+		  that a disabled GPIO means, that the first port is
+		  selected.
+
+Required Port nodes
+===================
+
+More documentation on these bindings is available in
+video-interfaces.txt in the same directory.
+
+reg		: The interface:
+		  0 - port for image signal processor
+		  1 - port for first camera sensor
+		  2 - port for second camera sensor
+
+Example
+=======
+
+video-bus-switch {
+	compatible = "video-bus-switch"
+	switch-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			csi_switch_in: endpoint {
+				remote-endpoint = <&csi_isp>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			csi_switch_out1: endpoint {
+				remote-endpoint = <&csi_cam1>;
+			};
+		};
+
+		port@2 {
+			reg = <2>;
+
+			csi_switch_out2: endpoint {
+				remote-endpoint = <&csi_cam2>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/omap3-n900.dts b/arch/arm/boot/dts/omap3-n900.dts
index 8043290..7189dfd 100644
--- a/arch/arm/boot/dts/omap3-n900.dts
+++ b/arch/arm/boot/dts/omap3-n900.dts
@@ -230,6 +230,15 @@
 
 				csi_switch_out1: endpoint {
 					remote-endpoint = <&csi_cam1>;
+				bus-type = <3>; /* CCP2 */
+				clock-lanes = <0>;
+				data-lanes = <1>;
+				lane-polarity = <0 0>;
+				clock-inv = <0>;
+				/* Select strobe = <1> for back camera, <0> for front camera */
+				strobe = <1>;
+				crc = <0>;
+					
 				};
 			};
 
@@ -238,6 +247,15 @@
 
 				csi_switch_out2: endpoint {
 					remote-endpoint = <&csi_cam2>;
+				bus-type = <3>; /* CCP2 */
+				clock-lanes = <0>;
+				data-lanes = <1>;
+				lane-polarity = <0 0>;
+				clock-inv = <0>;
+				/* Select strobe = <1> for back camera, <0> for front camera */
+				strobe = <0>;
+				crc = <0>;
+					
 				};
 			};
 		};
diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c
index 45c69ed..f0aa8cd 100644
--- a/drivers/media/platform/omap3isp/isp.c
+++ b/drivers/media/platform/omap3isp/isp.c
@@ -2024,44 +2054,51 @@ enum isp_of_phy {
 	ISP_OF_PHY_CSIPHY2,
 };
 
-static void isp_of_parse_node_csi1(struct device *dev,
-				   struct isp_bus_cfg *buscfg,
+void __isp_of_parse_node_csi1(struct device *dev,
+				   struct isp_ccp2_cfg *buscfg,
 				   struct v4l2_of_endpoint *vep)
 {
-	if (vep->base.port == ISP_OF_PHY_CSIPHY1)
-		buscfg->interface = ISP_INTERFACE_CCP2B_PHY1;
-	else
-		buscfg->interface = ISP_INTERFACE_CCP2B_PHY2;
-	buscfg->bus.ccp2.lanecfg.clk.pos = vep->bus.mipi_csi1.clock_lane;
-	buscfg->bus.ccp2.lanecfg.clk.pol =
+	buscfg->lanecfg.clk.pos = vep->bus.mipi_csi1.clock_lane;
+	buscfg->lanecfg.clk.pol =
 		vep->bus.mipi_csi1.lane_polarity[0];
 	dev_dbg(dev, "clock lane polarity %u, pos %u\n",
-		buscfg->bus.ccp2.lanecfg.clk.pol,
-		buscfg->bus.ccp2.lanecfg.clk.pos);
+		buscfg->lanecfg.clk.pol,
+		buscfg->lanecfg.clk.pos);
 
-	buscfg->bus.ccp2.lanecfg.data[0].pos = vep->bus.mipi_csi2.data_lanes[0];
-	buscfg->bus.ccp2.lanecfg.data[0].pol =
+	buscfg->lanecfg.data[0].pos = vep->bus.mipi_csi2.data_lanes[0];
+	buscfg->lanecfg.data[0].pol =
 		vep->bus.mipi_csi2.lane_polarities[1];
 	dev_dbg(dev, "data lane polarity %u, pos %u\n",
-		buscfg->bus.ccp2.lanecfg.data[0].pol,
-		buscfg->bus.ccp2.lanecfg.data[0].pos);
+		buscfg->lanecfg.data[0].pol,
+		buscfg->lanecfg.data[0].pos);
 
-	buscfg->bus.ccp2.strobe_clk_pol = vep->bus.mipi_csi1.clock_inv;
-	buscfg->bus.ccp2.phy_layer = vep->bus.mipi_csi1.strobe;
-	buscfg->bus.ccp2.ccp2_mode = vep->bus_type == V4L2_MBUS_CCP2;
+	buscfg->strobe_clk_pol = vep->bus.mipi_csi1.clock_inv;
+	buscfg->phy_layer = vep->bus.mipi_csi1.strobe;
+	buscfg->ccp2_mode = vep->bus_type == V4L2_MBUS_CCP2;
 
 	dev_dbg(dev, "clock_inv %u strobe %u ccp2 %u\n",
-		buscfg->bus.ccp2.strobe_clk_pol,
-		buscfg->bus.ccp2.phy_layer,
-		buscfg->bus.ccp2.ccp2_mode);
+		buscfg->strobe_clk_pol,
+		buscfg->phy_layer,
+		buscfg->ccp2_mode);
 	/*
 	 * FIXME: now we assume the CRC is always there.
 	 * Implement a way to obtain this information from the
 	 * sensor. Frame descriptors, perhaps?
 	 */
-	buscfg->bus.ccp2.crc = 1;
+	buscfg->crc = 1;
 
-	buscfg->bus.ccp2.vp_clk_pol = 1;
+	buscfg->vp_clk_pol = 1;
+}
+	
+static void isp_of_parse_node_csi1(struct device *dev,
+				   struct isp_bus_cfg *buscfg,
+				   struct v4l2_of_endpoint *vep)
+{
+	if (vep->base.port == ISP_OF_PHY_CSIPHY1)
+		buscfg->interface = ISP_INTERFACE_CCP2B_PHY1;
+	else
+		buscfg->interface = ISP_INTERFACE_CCP2B_PHY2;
+	__isp_of_parse_node_csi1(dev, &buscfg->bus.ccp2, vep);
 }
 
 static void isp_of_parse_node_csi2(struct device *dev,
@@ -2099,27 +2136,8 @@ static void isp_of_parse_node_csi2(struct device *dev,
 	buscfg->bus.csi2.crc = 1;
 }
 
-static int isp_of_parse_node_endpoint(struct device *dev,
-				      struct device_node *node,
-				      struct isp_async_subdev *isd)
+static int isp_endpoint_to_buscfg(struct device *dev, struct v4l2_of_endpoint vep, struct isp_bus_cfg *buscfg)
 {
-	struct isp_bus_cfg *buscfg;
-	struct v4l2_of_endpoint vep;
-	int ret;
-
-	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
-	if (!isd->bus)
-		return -ENOMEM;
-
-	buscfg = isd->bus;
-
-	ret = v4l2_of_parse_endpoint(node, &vep);
-	if (ret)
-		return ret;
-
-	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
-		vep.base.port);
-
 	switch (vep.base.port) {
 	case ISP_OF_PHY_PARALLEL:
 		buscfg->interface = ISP_INTERFACE_PARALLEL;
@@ -2147,10 +2165,35 @@ static int isp_of_parse_node_endpoint(struct device *dev,
 		break;
 
 	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int isp_of_parse_node_endpoint(struct device *dev,
+				      struct device_node *node,
+				      struct isp_async_subdev *isd)
+{
+	struct isp_bus_cfg *buscfg;
+	struct v4l2_of_endpoint vep;
+	int ret;
+
+	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
+	if (!isd->bus)
+		return -ENOMEM;
+
+	buscfg = isd->bus;
+
+	ret = v4l2_of_parse_endpoint(node, &vep);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
+		vep.base.port);
+
+	if (isp_endpoint_to_buscfg(dev, vep, buscfg))
 		dev_warn(dev, "%s: invalid interface %u\n", node->full_name,
 			 vep.base.port);
-		break;
-	}
 
 	return 0;
 }
@@ -2262,6 +2305,10 @@ static int isp_of_parse_nodes(struct device *dev,
 	}
 
 	return notifier->num_subdevs;
+
+error:
+	of_node_put(node);
+	return -EINVAL;
 }
 
 static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
diff --git a/drivers/media/platform/omap3isp/ispccp2.c b/drivers/media/platform/omap3isp/ispccp2.c
index 2d1463a..d5df576 100644
--- a/drivers/media/platform/omap3isp/ispccp2.c
+++ b/drivers/media/platform/omap3isp/ispccp2.c
@@ -23,6 +23,8 @@
 #include <linux/regulator/consumer.h>
 #include <linux/regmap.h>
 
+#include <media/v4l2-of.h>
+
 #include "isp.h"
 #include "ispreg.h"
 #include "ispccp2.h"
@@ -169,6 +171,7 @@ static int ccp2_if_enable(struct isp_ccp2_device *ccp2, u8 enable)
 
 		pad = media_entity_remote_pad(&ccp2->pads[CCP2_PAD_SINK]);
 		sensor = media_entity_to_v4l2_subdev(pad->entity);
+		/* Struct isp_bus_cfg has union inside */ 
 		buscfg = &((struct isp_bus_cfg *)sensor->host_priv)->bus.ccp2;
 
 
@@ -369,6 +372,9 @@ static void ccp2_lcx_config(struct isp_ccp2_device *ccp2,
 	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQENABLE, val);
 }
 
+void __isp_of_parse_node_csi1(struct device *dev,
+			      struct isp_ccp2_cfg *buscfg,
+			      struct v4l2_of_endpoint *vep);
 /*
  * ccp2_if_configure - Configure ccp2 with data from sensor
  * @ccp2: Pointer to ISP CCP2 device
@@ -377,7 +383,7 @@ static void ccp2_lcx_config(struct isp_ccp2_device *ccp2,
  */
 static int ccp2_if_configure(struct isp_ccp2_device *ccp2)
 {
-	const struct isp_bus_cfg *buscfg;
+	struct isp_bus_cfg *buscfg;
 	struct v4l2_mbus_framefmt *format;
 	struct media_pad *pad;
 	struct v4l2_subdev *sensor;
@@ -390,6 +396,22 @@ static int ccp2_if_configure(struct isp_ccp2_device *ccp2)
 	sensor = media_entity_to_v4l2_subdev(pad->entity);
 	buscfg = sensor->host_priv;
 
+	{
+		struct v4l2_subdev *subdev2;
+		struct v4l2_of_endpoint vep;
+		
+		subdev2 = media_entity_to_v4l2_subdev(pad->entity);
+
+		printk("if_configure... subdev %p\n", subdev2);
+		ret = v4l2_subdev_call(subdev2, video, g_endpoint_config, &vep);
+		printk("if_configure ret %d\n", ret);
+		if (ret == 0) {
+			printk("Success: have configuration\n");
+			__isp_of_parse_node_csi1(NULL, &buscfg->bus.ccp2, &vep);
+			printk("Configured ok?\n");
+		}
+	}
+
 	ret = ccp2_phyif_config(ccp2, &buscfg->bus.ccp2);
 	if (ret < 0)
 		return ret;
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
index 1a5d944..f599e08 100644
--- a/drivers/media/platform/video-bus-switch.c
+++ b/drivers/media/platform/video-bus-switch.c
@@ -2,6 +2,7 @@
  * Generic driver for video bus switches
  *
  * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
+ * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -33,9 +34,9 @@
 #define CSI_SWITCH_PORTS 3
 
 enum vbs_state {
-	CSI_SWITCH_DISABLED,
-	CSI_SWITCH_PORT_1,
-	CSI_SWITCH_PORT_2,
+	CSI_SWITCH_DISABLED = 0,
+	CSI_SWITCH_PORT_1 = 1,
+	CSI_SWITCH_PORT_2 = 2,
 };
 
 struct vbs_src_pads {
@@ -49,6 +50,7 @@ struct vbs_data {
 	struct v4l2_async_notifier notifier;
 	struct media_pad pads[CSI_SWITCH_PORTS];
 	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
+	struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS];
 	enum vbs_state state;
 };
 
@@ -107,6 +109,7 @@ static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
 		}
 
 		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+		pdata->vep[notifier->num_subdevs] = vep;
 		notifier->num_subdevs++;
 	}
 
@@ -253,6 +256,19 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
 	return v4l2_subdev_call(subdev, video, s_stream, enable);
 }
 
+static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	printk("vbs_g_endpoint_config...\n");
+	printk("active port is %d\n", pdata->state);
+	*cfg = pdata->vep[pdata->state];
+
+	return -EINVAL;
+
+	return 0;
+}
+
+
 static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
 	.registered = &vbs_registered,
 };
@@ -265,6 +281,7 @@ static const struct media_entity_operations vbs_media_ops = {
 /* subdev video operations */
 static const struct v4l2_subdev_video_ops vbs_video_ops = {
 	.s_stream = vbs_s_stream,
+	.g_endpoint_config = vbs_g_endpoint_config,
 };
 
 static const struct v4l2_subdev_ops vbs_ops = {
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..448dbb5 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -25,6 +25,7 @@
 #include <media/v4l2-dev.h>
 #include <media/v4l2-fh.h>
 #include <media/v4l2-mediabus.h>
+#include <media/v4l2-of.h>
 
 /* generic v4l2_device notify callback notification values */
 #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
@@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**



-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-22 23:40             ` Sebastian Reichel
  2016-12-23 11:42               ` Pavel Machek
  2016-12-23 20:56               ` Pavel Machek
@ 2016-12-24 14:26               ` Pavel Machek
  2016-12-24 14:43                 ` Pavel Machek
  2 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-24 14:26 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 492 bytes --]

Hi!

> > So... did I understood it correctly? (Needs some work to be done...)
> 
> I had a quick look and yes, that's basically what I had in mind to
> solve the issue. If callback is not available the old system should
> be used of course.

Ok, got it to work, thanks for all the help. I'll clean it up now.

Best regards,
									Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [RFC/PATCH] media: Add video bus switch
  2016-12-24 14:26               ` Pavel Machek
@ 2016-12-24 14:43                 ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-24 14:43 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 12698 bytes --]

On Sat 2016-12-24 15:26:57, Pavel Machek wrote:
> Hi!
> 
> > > So... did I understood it correctly? (Needs some work to be done...)
> > 
> > I had a quick look and yes, that's basically what I had in mind to
> > solve the issue. If callback is not available the old system should
> > be used of course.
> 
> Ok, got it to work, thanks for all the help. I'll clean it up now.

Relative to sre's version, patch is this:

diff --git a/arch/arm/boot/dts/omap3-n900.dts b/arch/arm/boot/dts/omap3-n900.dts
index 8043290..7189dfd 100644
--- a/arch/arm/boot/dts/omap3-n900.dts
+++ b/arch/arm/boot/dts/omap3-n900.dts
@@ -230,6 +230,15 @@
 
 				csi_switch_out1: endpoint {
 					remote-endpoint = <&csi_cam1>;
+				bus-type = <3>; /* CCP2 */
+				clock-lanes = <0>;
+				data-lanes = <1>;
+				lane-polarity = <0 0>;
+				clock-inv = <0>;
+				/* Select strobe = <1> for back camera, <0> for front camera */
+				strobe = <1>;
+				crc = <0>;
+					
 				};
 			};
 
@@ -238,6 +247,15 @@
 
 				csi_switch_out2: endpoint {
 					remote-endpoint = <&csi_cam2>;
+				bus-type = <3>; /* CCP2 */
+				clock-lanes = <0>;
+				data-lanes = <1>;
+				lane-polarity = <0 0>;
+				clock-inv = <0>;
+				/* Select strobe = <1> for back camera, <0> for front camera */
+				strobe = <0>;
+				crc = <0>;
+					
 				};
 			};
 		};
diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c
index 45c69ed..b8cc29b 100644
--- a/drivers/media/platform/omap3isp/isp.c
+++ b/drivers/media/platform/omap3isp/isp.c
@@ -42,6 +42,8 @@
  * published by the Free Software Foundation.
  */
 
+#define DEBUG
+
 #include <asm/cacheflush.h>
 
 #include <linux/clk.h>
@@ -480,8 +482,8 @@ void omap3isp_hist_dma_done(struct isp_device *isp)
 	    omap3isp_stat_pcr_busy(&isp->isp_hist)) {
 		/* Histogram cannot be enabled in this frame anymore */
 		atomic_set(&isp->isp_hist.buf_err, 1);
-		dev_dbg(isp->dev, "hist: Out of synchronization with "
-				  "CCDC. Ignoring next buffer.\n");
+		dev_dbg(isp->dev,
+			"hist: Out of synchronization with CCDC. Ignoring next buffer.\n");
 	}
 }
 
@@ -699,7 +701,7 @@ static int isp_pipeline_enable(struct isp_pipeline *pipe,
 	spin_unlock_irqrestore(&pipe->lock, flags);
 
 	pipe->do_propagation = false;
-
+	
 	entity = &pipe->output->video.entity;
 	while (1) {
 		pad = &entity->pads[0];
@@ -2024,44 +2026,51 @@ enum isp_of_phy {
 	ISP_OF_PHY_CSIPHY2,
 };
 
-static void isp_of_parse_node_csi1(struct device *dev,
-				   struct isp_bus_cfg *buscfg,
+void __isp_of_parse_node_csi1(struct device *dev,
+				   struct isp_ccp2_cfg *buscfg,
 				   struct v4l2_of_endpoint *vep)
 {
-	if (vep->base.port == ISP_OF_PHY_CSIPHY1)
-		buscfg->interface = ISP_INTERFACE_CCP2B_PHY1;
-	else
-		buscfg->interface = ISP_INTERFACE_CCP2B_PHY2;
-	buscfg->bus.ccp2.lanecfg.clk.pos = vep->bus.mipi_csi1.clock_lane;
-	buscfg->bus.ccp2.lanecfg.clk.pol =
+	buscfg->lanecfg.clk.pos = vep->bus.mipi_csi1.clock_lane;
+	buscfg->lanecfg.clk.pol =
 		vep->bus.mipi_csi1.lane_polarity[0];
 	dev_dbg(dev, "clock lane polarity %u, pos %u\n",
-		buscfg->bus.ccp2.lanecfg.clk.pol,
-		buscfg->bus.ccp2.lanecfg.clk.pos);
+		buscfg->lanecfg.clk.pol,
+		buscfg->lanecfg.clk.pos);
 
-	buscfg->bus.ccp2.lanecfg.data[0].pos = vep->bus.mipi_csi2.data_lanes[0];
-	buscfg->bus.ccp2.lanecfg.data[0].pol =
+	buscfg->lanecfg.data[0].pos = vep->bus.mipi_csi2.data_lanes[0];
+	buscfg->lanecfg.data[0].pol =
 		vep->bus.mipi_csi2.lane_polarities[1];
 	dev_dbg(dev, "data lane polarity %u, pos %u\n",
-		buscfg->bus.ccp2.lanecfg.data[0].pol,
-		buscfg->bus.ccp2.lanecfg.data[0].pos);
+		buscfg->lanecfg.data[0].pol,
+		buscfg->lanecfg.data[0].pos);
 
-	buscfg->bus.ccp2.strobe_clk_pol = vep->bus.mipi_csi1.clock_inv;
-	buscfg->bus.ccp2.phy_layer = vep->bus.mipi_csi1.strobe;
-	buscfg->bus.ccp2.ccp2_mode = vep->bus_type == V4L2_MBUS_CCP2;
+	buscfg->strobe_clk_pol = vep->bus.mipi_csi1.clock_inv;
+	buscfg->phy_layer = vep->bus.mipi_csi1.strobe;
+	buscfg->ccp2_mode = vep->bus_type == V4L2_MBUS_CCP2;
 
 	dev_dbg(dev, "clock_inv %u strobe %u ccp2 %u\n",
-		buscfg->bus.ccp2.strobe_clk_pol,
-		buscfg->bus.ccp2.phy_layer,
-		buscfg->bus.ccp2.ccp2_mode);
+		buscfg->strobe_clk_pol,
+		buscfg->phy_layer,
+		buscfg->ccp2_mode);
 	/*
 	 * FIXME: now we assume the CRC is always there.
 	 * Implement a way to obtain this information from the
 	 * sensor. Frame descriptors, perhaps?
 	 */
-	buscfg->bus.ccp2.crc = 1;
+	buscfg->crc = 1;
 
-	buscfg->bus.ccp2.vp_clk_pol = 1;
+	buscfg->vp_clk_pol = 1;
+}
+	
+static void isp_of_parse_node_csi1(struct device *dev,
+				   struct isp_bus_cfg *buscfg,
+				   struct v4l2_of_endpoint *vep)
+{
+	if (vep->base.port == ISP_OF_PHY_CSIPHY1)
+		buscfg->interface = ISP_INTERFACE_CCP2B_PHY1;
+	else
+		buscfg->interface = ISP_INTERFACE_CCP2B_PHY2;
+	__isp_of_parse_node_csi1(dev, &buscfg->bus.ccp2, vep);
 }
 
 static void isp_of_parse_node_csi2(struct device *dev,
@@ -2099,27 +2108,8 @@ static void isp_of_parse_node_csi2(struct device *dev,
 	buscfg->bus.csi2.crc = 1;
 }
 
-static int isp_of_parse_node_endpoint(struct device *dev,
-				      struct device_node *node,
-				      struct isp_async_subdev *isd)
+static int isp_endpoint_to_buscfg(struct device *dev, struct v4l2_of_endpoint vep, struct isp_bus_cfg *buscfg)
 {
-	struct isp_bus_cfg *buscfg;
-	struct v4l2_of_endpoint vep;
-	int ret;
-
-	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
-	if (!isd->bus)
-		return -ENOMEM;
-
-	buscfg = isd->bus;
-
-	ret = v4l2_of_parse_endpoint(node, &vep);
-	if (ret)
-		return ret;
-
-	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
-		vep.base.port);
-
 	switch (vep.base.port) {
 	case ISP_OF_PHY_PARALLEL:
 		buscfg->interface = ISP_INTERFACE_PARALLEL;
@@ -2147,10 +2137,35 @@ static int isp_of_parse_node_endpoint(struct device *dev,
 		break;
 
 	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int isp_of_parse_node_endpoint(struct device *dev,
+				      struct device_node *node,
+				      struct isp_async_subdev *isd)
+{
+	struct isp_bus_cfg *buscfg;
+	struct v4l2_of_endpoint vep;
+	int ret;
+
+	isd->bus = devm_kzalloc(dev, sizeof(*isd->bus), GFP_KERNEL);
+	if (!isd->bus)
+		return -ENOMEM;
+
+	buscfg = isd->bus;
+
+	ret = v4l2_of_parse_endpoint(node, &vep);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name,
+		vep.base.port);
+
+	if (isp_endpoint_to_buscfg(dev, vep, buscfg))
 		dev_warn(dev, "%s: invalid interface %u\n", node->full_name,
 			 vep.base.port);
-		break;
-	}
 
 	return 0;
 }
@@ -2262,6 +2277,10 @@ static int isp_of_parse_nodes(struct device *dev,
 	}
 
 	return notifier->num_subdevs;
+
+error:
+	of_node_put(node);
+	return -EINVAL;
 }
 
 static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
diff --git a/drivers/media/platform/omap3isp/ispccp2.c b/drivers/media/platform/omap3isp/ispccp2.c
index 2d1463a..c2bc6b7 100644
--- a/drivers/media/platform/omap3isp/ispccp2.c
+++ b/drivers/media/platform/omap3isp/ispccp2.c
@@ -23,6 +23,8 @@
 #include <linux/regulator/consumer.h>
 #include <linux/regmap.h>
 
+#include <media/v4l2-of.h>
+
 #include "isp.h"
 #include "ispreg.h"
 #include "ispccp2.h"
@@ -169,6 +171,7 @@ static int ccp2_if_enable(struct isp_ccp2_device *ccp2, u8 enable)
 
 		pad = media_entity_remote_pad(&ccp2->pads[CCP2_PAD_SINK]);
 		sensor = media_entity_to_v4l2_subdev(pad->entity);
+		/* Struct isp_bus_cfg has union inside */ 
 		buscfg = &((struct isp_bus_cfg *)sensor->host_priv)->bus.ccp2;
 
 
@@ -369,6 +372,9 @@ static void ccp2_lcx_config(struct isp_ccp2_device *ccp2,
 	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQENABLE, val);
 }
 
+void __isp_of_parse_node_csi1(struct device *dev,
+			      struct isp_ccp2_cfg *buscfg,
+			      struct v4l2_of_endpoint *vep);
 /*
  * ccp2_if_configure - Configure ccp2 with data from sensor
  * @ccp2: Pointer to ISP CCP2 device
@@ -377,7 +383,7 @@ static void ccp2_lcx_config(struct isp_ccp2_device *ccp2,
  */
 static int ccp2_if_configure(struct isp_ccp2_device *ccp2)
 {
-	const struct isp_bus_cfg *buscfg;
+	struct isp_bus_cfg *buscfg;
 	struct v4l2_mbus_framefmt *format;
 	struct media_pad *pad;
 	struct v4l2_subdev *sensor;
@@ -390,6 +396,25 @@ static int ccp2_if_configure(struct isp_ccp2_device *ccp2)
 	sensor = media_entity_to_v4l2_subdev(pad->entity);
 	buscfg = sensor->host_priv;
 
+	{
+		struct v4l2_subdev *subdev2;
+		struct v4l2_of_endpoint vep;
+		
+		subdev2 = media_entity_to_v4l2_subdev(pad->entity);
+
+		printk("if_configure... subdev %p\n", subdev2);
+		/* fixme: vep.base.port is wrong? */
+		ret = v4l2_subdev_call(subdev2, video, g_endpoint_config, &vep);
+		printk("if_configure ret %d\n", ret);
+		if (ret == 0) {
+			struct isp_ccp2_cfg prev_cfg = buscfg->bus.ccp2;
+			printk("Success: have configuration\n");
+			printk("Compare: %d\n", memcmp(&prev_cfg, &buscfg->bus.ccp2, sizeof(prev_cfg)));			
+			__isp_of_parse_node_csi1(NULL, &buscfg->bus.ccp2, &vep);
+			printk("Configured ok?\n");
+		}
+	}
+
 	ret = ccp2_phyif_config(ccp2, &buscfg->bus.ccp2);
 	if (ret < 0)
 		return ret;
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
index 1a5d944..b238dac 100644
--- a/drivers/media/platform/video-bus-switch.c
+++ b/drivers/media/platform/video-bus-switch.c
@@ -2,6 +2,7 @@
  * Generic driver for video bus switches
  *
  * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
+ * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -33,9 +34,9 @@
 #define CSI_SWITCH_PORTS 3
 
 enum vbs_state {
-	CSI_SWITCH_DISABLED,
-	CSI_SWITCH_PORT_1,
-	CSI_SWITCH_PORT_2,
+	CSI_SWITCH_DISABLED = 0,
+	CSI_SWITCH_PORT_1 = 1,
+	CSI_SWITCH_PORT_2 = 2,
 };
 
 struct vbs_src_pads {
@@ -49,6 +50,7 @@ struct vbs_data {
 	struct v4l2_async_notifier notifier;
 	struct media_pad pads[CSI_SWITCH_PORTS];
 	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
+	struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS];
 	enum vbs_state state;
 };
 
@@ -107,6 +109,7 @@ static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
 		}
 
 		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+		pdata->vep[notifier->num_subdevs] = vep;
 		notifier->num_subdevs++;
 	}
 
@@ -174,6 +177,8 @@ static int vbs_link_setup(struct media_entity *entity,
 	if (pdata->state != CSI_SWITCH_DISABLED)
 		return -EBUSY;
 
+	printk("Link setup: going to config %d\n", local->index);
+
 	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
 	pdata->state = local->index;
 
@@ -253,6 +258,17 @@ static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
 	return v4l2_subdev_call(subdev, video, s_stream, enable);
 }
 
+static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	printk("vbs_g_endpoint_config...\n");
+	printk("active port is %d\n", pdata->state);
+	*cfg = pdata->vep[pdata->state - 1];
+
+	return 0;
+}
+
+
 static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
 	.registered = &vbs_registered,
 };
@@ -265,6 +281,7 @@ static const struct media_entity_operations vbs_media_ops = {
 /* subdev video operations */
 static const struct v4l2_subdev_video_ops vbs_video_ops = {
 	.s_stream = vbs_s_stream,
+	.g_endpoint_config = vbs_g_endpoint_config,
 };
 
 static const struct v4l2_subdev_ops vbs_ops = {
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..448dbb5 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -25,6 +25,7 @@
 #include <media/v4l2-dev.h>
 #include <media/v4l2-fh.h>
 #include <media/v4l2-mediabus.h>
+#include <media/v4l2-of.h>
 
 /* generic v4l2_device notify callback notification values */
 #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
@@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**




-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [PATCH] media: Add video bus switch
  2016-12-22 13:39       ` [RFC/PATCH] media: Add video bus switch Pavel Machek
  2016-12-22 14:32         ` Sebastian Reichel
@ 2016-12-24 15:20         ` Pavel Machek
  2016-12-24 18:35           ` kbuild test robot
                             ` (2 more replies)
  1 sibling, 3 replies; 97+ messages in thread
From: Pavel Machek @ 2016-12-24 15:20 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 15779 bytes --]


N900 contains front and back camera, with a switch between the
two. This adds support for the switch component, and it is now
possible to select between front and back cameras during runtime.

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


diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
new file mode 100644
index 0000000..1b9f8e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
@@ -0,0 +1,63 @@
+Video Bus Switch Binding
+========================
+
+This is a binding for a gpio controlled switch for camera interfaces. Such a
+device is used on some embedded devices to connect two cameras to the same
+interface of a image signal processor.
+
+Required properties
+===================
+
+compatible	: must contain "video-bus-switch"
+switch-gpios	: GPIO specifier for the gpio, which can toggle the
+		  selected camera. The GPIO should be configured, so
+		  that a disabled GPIO means, that the first port is
+		  selected.
+
+Required Port nodes
+===================
+
+More documentation on these bindings is available in
+video-interfaces.txt in the same directory.
+
+reg		: The interface:
+		  0 - port for image signal processor
+		  1 - port for first camera sensor
+		  2 - port for second camera sensor
+
+Example
+=======
+
+video-bus-switch {
+	compatible = "video-bus-switch"
+	switch-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			csi_switch_in: endpoint {
+				remote-endpoint = <&csi_isp>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			csi_switch_out1: endpoint {
+				remote-endpoint = <&csi_cam1>;
+			};
+		};
+
+		port@2 {
+			reg = <2>;
+
+			csi_switch_out2: endpoint {
+				remote-endpoint = <&csi_cam2>;
+			};
+		};
+	};
+};
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index ce4a96f..a4b509e 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -91,6 +91,16 @@ config VIDEO_OMAP3_DEBUG
 	---help---
 	  Enable debug messages on OMAP 3 camera controller driver.
 
+config VIDEO_BUS_SWITCH
+	tristate "Video Bus switch"
+	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	depends on MEDIA_CONTROLLER
+	depends on OF
+	---help---
+	  Driver for a GPIO controlled video bus switch, which is used to
+	  connect two camera sensors to the same port a the image signal
+	  processor.
+
 config VIDEO_PXA27x
 	tristate "PXA27x Quick Capture Interface driver"
 	depends on VIDEO_DEV && HAS_DMA
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 40b18d1..8eafc27 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/
 obj-$(CONFIG_VIDEO_OMAP3)	+= omap3isp/
 obj-$(CONFIG_VIDEO_PXA27x)	+= pxa_camera.o
 
+obj-$(CONFIG_VIDEO_BUS_SWITCH) += video-bus-switch.o
+
 obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o
 
 obj-$(CONFIG_VIDEO_VIVID)		+= vivid/
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
new file mode 100644
index 0000000..6400cfc
--- /dev/null
+++ b/drivers/media/platform/video-bus-switch.c
@@ -0,0 +1,387 @@
+/*
+ * Generic driver for video bus switches
+ *
+ * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
+ * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz>
+ *
+ * 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.
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/gpio/consumer.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-of.h>
+
+/*
+ * TODO:
+ * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
+ */
+
+#define CSI_SWITCH_SUBDEVS 2
+#define CSI_SWITCH_PORTS 3
+
+enum vbs_state {
+	CSI_SWITCH_DISABLED = 0,
+	CSI_SWITCH_PORT_1 = 1,
+	CSI_SWITCH_PORT_2 = 2,
+};
+
+struct vbs_src_pads {
+	struct media_entity *src;
+	int src_pad;
+};
+
+struct vbs_data {
+	struct gpio_desc *swgpio;
+	struct v4l2_subdev subdev;
+	struct v4l2_async_notifier notifier;
+	struct media_pad pads[CSI_SWITCH_PORTS];
+	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
+	struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS];
+	enum vbs_state state;
+};
+
+struct vbs_async_subdev {
+	struct v4l2_subdev *sd;
+	struct v4l2_async_subdev asd;
+	u8 port;
+};
+
+static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
+{
+	struct v4l2_async_notifier *notifier = &pdata->notifier;
+	struct device_node *node = NULL;
+
+	notifier->subdevs = devm_kcalloc(dev, CSI_SWITCH_SUBDEVS,
+		sizeof(*notifier->subdevs), GFP_KERNEL);
+	if (!notifier->subdevs)
+		return -ENOMEM;
+
+	notifier->num_subdevs = 0;
+	while (notifier->num_subdevs < CSI_SWITCH_SUBDEVS &&
+	       (node = of_graph_get_next_endpoint(dev->of_node, node))) {
+		struct v4l2_of_endpoint vep;
+		struct vbs_async_subdev *ssd;
+
+		/* skip first port (connected to isp) */
+		v4l2_of_parse_endpoint(node, &vep);
+		if (vep.base.port == 0) {
+			struct device_node *ispnode;
+
+			ispnode = of_graph_get_remote_port_parent(node);
+			if (!ispnode) {
+				dev_warn(dev, "bad remote port parent\n");
+				return -EINVAL;
+			}
+
+			of_node_put(node);
+			continue;
+		}
+
+		ssd = devm_kzalloc(dev, sizeof(*ssd), GFP_KERNEL);
+		if (!ssd) {
+			of_node_put(node);
+			return -ENOMEM;
+		}
+
+		ssd->port = vep.base.port;
+
+		notifier->subdevs[notifier->num_subdevs] = &ssd->asd;
+
+		ssd->asd.match.of.node = of_graph_get_remote_port_parent(node);
+		of_node_put(node);
+		if (!ssd->asd.match.of.node) {
+			dev_warn(dev, "bad remote port parent\n");
+			return -EINVAL;
+		}
+
+		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+		pdata->vep[notifier->num_subdevs] = vep;
+		notifier->num_subdevs++;
+	}
+
+	return notifier->num_subdevs;
+}
+
+static int vbs_registered(struct v4l2_subdev *sd)
+{
+	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
+	struct vbs_data *pdata;
+	int err;
+
+	dev_dbg(sd->dev, "registered, init notifier...\n");
+
+	pdata = v4l2_get_subdevdata(sd);
+
+	err = v4l2_async_notifier_register(v4l2_dev, &pdata->notifier);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct v4l2_subdev *vbs_get_remote_subdev(struct v4l2_subdev *sd)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	struct media_entity *src;
+
+	if (pdata->state == CSI_SWITCH_DISABLED)
+		return ERR_PTR(-ENXIO);
+
+	src = pdata->src_pads[pdata->state].src;
+
+	return media_entity_to_v4l2_subdev(src);
+}
+
+static int vbs_link_setup(struct media_entity *entity,
+			  const struct media_pad *local,
+			  const struct media_pad *remote, u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	bool enable = flags & MEDIA_LNK_FL_ENABLED;
+
+	if (local->index > CSI_SWITCH_PORTS - 1)
+		return -ENXIO;
+
+	/* no configuration needed on source port */
+	if (local->index == 0)
+		return 0;
+
+	if (!enable) {
+		if (local->index == pdata->state) {
+			pdata->state = CSI_SWITCH_DISABLED;
+
+			/* Make sure we have both cameras enabled */
+			gpiod_set_value(pdata->swgpio, 1);
+			return 0;
+		} else {
+			return -EINVAL;
+		}
+	}
+
+	/* there can only be one active sink at the same time */
+	if (pdata->state != CSI_SWITCH_DISABLED)
+		return -EBUSY;
+
+	dev_dbg(sd->dev, "Link setup: going to config %d\n", local->index);
+
+	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
+	pdata->state = local->index;
+
+	sd = vbs_get_remote_subdev(sd);
+	if (IS_ERR(sd))
+		return PTR_ERR(sd);
+
+	pdata->subdev.ctrl_handler = sd->ctrl_handler;
+
+	return 0;
+}
+
+static int vbs_subdev_notifier_bound(struct v4l2_async_notifier *async,
+				     struct v4l2_subdev *subdev,
+				     struct v4l2_async_subdev *asd)
+{
+	struct vbs_data *pdata = container_of(async,
+		struct vbs_data, notifier);
+	struct vbs_async_subdev *ssd =
+		container_of(asd, struct vbs_async_subdev, asd);
+	struct media_entity *sink = &pdata->subdev.entity;
+	struct media_entity *src = &subdev->entity;
+	int sink_pad = ssd->port;
+	int src_pad;
+
+	if (sink_pad >= sink->num_pads) {
+		dev_err(pdata->subdev.dev, "no sink pad in internal entity!\n");
+		return -EINVAL;
+	}
+
+	for (src_pad = 0; src_pad < subdev->entity.num_pads; src_pad++) {
+		if (subdev->entity.pads[src_pad].flags & MEDIA_PAD_FL_SOURCE)
+			break;
+	}
+
+	if (src_pad >= src->num_pads) {
+		dev_err(pdata->subdev.dev, "no source pad in external entity\n");
+		return -EINVAL;
+	}
+
+	pdata->src_pads[sink_pad].src = src;
+	pdata->src_pads[sink_pad].src_pad = src_pad;
+	ssd->sd = subdev;
+
+	return 0;
+}
+
+static int vbs_subdev_notifier_complete(struct v4l2_async_notifier *async)
+{
+	struct vbs_data *pdata = container_of(async, struct vbs_data, notifier);
+	struct media_entity *sink = &pdata->subdev.entity;
+	int sink_pad;
+
+	for (sink_pad = 1; sink_pad < CSI_SWITCH_PORTS; sink_pad++) {
+		struct media_entity *src = pdata->src_pads[sink_pad].src;
+		int src_pad = pdata->src_pads[sink_pad].src_pad;
+		int err;
+
+		err = media_create_pad_link(src, src_pad, sink, sink_pad, 0);
+		if (err < 0)
+			return err;
+
+		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
+			src->name, src_pad, sink->name, sink_pad);
+	}
+
+	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);
+}
+
+static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
+
+	if (IS_ERR(subdev))
+		return PTR_ERR(subdev);
+
+	return v4l2_subdev_call(subdev, video, s_stream, enable);
+}
+
+static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	dev_dbg(sd->dev, "vbs_g_endpoint_config... active port is %d\n", pdata->state);
+	*cfg = pdata->vep[pdata->state - 1];
+
+	return 0;
+}
+
+
+static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
+	.registered = &vbs_registered,
+};
+
+static const struct media_entity_operations vbs_media_ops = {
+	.link_setup = vbs_link_setup,
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops vbs_video_ops = {
+	.s_stream = vbs_s_stream,
+	.g_endpoint_config = vbs_g_endpoint_config,
+};
+
+static const struct v4l2_subdev_ops vbs_ops = {
+	.video = &vbs_video_ops,
+};
+
+static int video_bus_switch_probe(struct platform_device *pdev)
+{
+	struct vbs_data *pdata;
+	int err = 0;
+
+	/* platform data */
+	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		dev_dbg(&pdev->dev, "video-bus-switch: not enough memory\n");
+		return -ENOMEM;
+	}
+	platform_set_drvdata(pdev, pdata);
+
+	/* switch gpio */
+	pdata->swgpio = devm_gpiod_get(&pdev->dev, "switch", GPIOD_OUT_HIGH);
+	if (IS_ERR(pdata->swgpio)) {
+		err = PTR_ERR(pdata->swgpio);
+		dev_err(&pdev->dev, "Failed to request gpio: %d\n", err);
+		return err;
+	}
+
+	/* find sub-devices */
+	err = vbs_of_parse_nodes(&pdev->dev, pdata);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to parse nodes: %d\n", err);
+		return err;
+	}
+
+	pdata->state = CSI_SWITCH_DISABLED;
+	pdata->notifier.bound = vbs_subdev_notifier_bound;
+	pdata->notifier.complete = vbs_subdev_notifier_complete;
+
+	/* setup subdev */
+	pdata->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+	pdata->pads[1].flags = MEDIA_PAD_FL_SINK;
+	pdata->pads[2].flags = MEDIA_PAD_FL_SINK;
+
+	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
+	pdata->subdev.dev = &pdev->dev;
+	pdata->subdev.owner = pdev->dev.driver->owner;
+	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
+	v4l2_set_subdevdata(&pdata->subdev, pdata);
+	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
+	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;
+	pdata->subdev.entity.ops = &vbs_media_ops;
+	pdata->subdev.internal_ops = &vbs_internal_ops;
+	err = media_entity_pads_init(&pdata->subdev.entity, CSI_SWITCH_PORTS,
+				pdata->pads);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to init media entity: %d\n", err);
+		return err;
+	}
+
+	/* register subdev */
+	err = v4l2_async_register_subdev(&pdata->subdev);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
+		media_entity_cleanup(&pdata->subdev.entity);
+		return err;
+	}
+
+	dev_info(&pdev->dev, "video-bus-switch registered\n");
+
+	return 0;
+}
+
+static int video_bus_switch_remove(struct platform_device *pdev)
+{
+	struct vbs_data *pdata = platform_get_drvdata(pdev);
+
+	v4l2_async_notifier_unregister(&pdata->notifier);
+	v4l2_async_unregister_subdev(&pdata->subdev);
+	media_entity_cleanup(&pdata->subdev.entity);
+
+	return 0;
+}
+
+static const struct of_device_id video_bus_switch_of_match[] = {
+	{ .compatible = "video-bus-switch" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, video_bus_switch_of_match);
+
+static struct platform_driver video_bus_switch_driver = {
+	.driver = {
+		.name	= "video-bus-switch",
+		.of_match_table = video_bus_switch_of_match,
+	},
+	.probe		= video_bus_switch_probe,
+	.remove		= video_bus_switch_remove,
+};
+
+module_platform_driver(video_bus_switch_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("Video Bus Switch");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:video-bus-switch");
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..448dbb5 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -25,6 +25,7 @@
 #include <media/v4l2-dev.h>
 #include <media/v4l2-fh.h>
 #include <media/v4l2-mediabus.h>
+#include <media/v4l2-of.h>
 
 /* generic v4l2_device notify callback notification values */
 #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
@@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**
diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
index 4890787..94648ab 100644
--- a/include/uapi/linux/media.h
+++ b/include/uapi/linux/media.h
@@ -147,6 +147,7 @@ struct media_device_info {
  * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER.
  */
 #define MEDIA_ENT_F_TUNER		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 5)
+#define MEDIA_ENT_F_SWITCH		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 6)
 
 #define MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN	MEDIA_ENT_F_OLD_SUBDEV_BASE
 


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] media: Add video bus switch
  2016-12-24 15:20         ` [PATCH] " Pavel Machek
@ 2016-12-24 18:35           ` kbuild test robot
  2017-01-12 11:17           ` Pavel Machek
  2017-02-03 12:35           ` [PATCH] devicetree: " Pavel Machek
  2 siblings, 0 replies; 97+ messages in thread
From: kbuild test robot @ 2016-12-24 18:35 UTC (permalink / raw)
  To: Pavel Machek
  Cc: kbuild-all, Sakari Ailus, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 6357 bytes --]

Hi Pavel,

[auto build test WARNING on v4.9-rc8]
[also build test WARNING on next-20161224]
[cannot apply to linuxtv-media/master]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Pavel-Machek/media-Add-video-bus-switch/20161225-003239
reproduce: make htmldocs

All warnings (new ones prefixed by >>):

   make[3]: warning: jobserver unavailable: using -j1.  Add '+' to parent make rule.
   include/linux/init.h:1: warning: no structured comments found
   include/linux/workqueue.h:392: warning: No description found for parameter '...'
   include/linux/workqueue.h:392: warning: Excess function parameter 'args' description in 'alloc_workqueue'
   include/linux/workqueue.h:413: warning: No description found for parameter '...'
   include/linux/workqueue.h:413: warning: Excess function parameter 'args' description in 'alloc_ordered_workqueue'
   include/linux/kthread.h:26: warning: No description found for parameter '...'
   kernel/sys.c:1: warning: no structured comments found
   drivers/dma-buf/seqno-fence.c:1: warning: no structured comments found
   include/linux/fence-array.h:61: warning: No description found for parameter 'fence'
   include/sound/core.h:324: warning: No description found for parameter '...'
   include/sound/core.h:335: warning: No description found for parameter '...'
   include/sound/core.h:388: warning: No description found for parameter '...'
   include/media/media-entity.h:1054: warning: No description found for parameter '...'
>> include/media/v4l2-subdev.h:421: warning: No description found for parameter 'g_endpoint_config'
   include/net/mac80211.h:3207: ERROR: Unexpected indentation.
   include/net/mac80211.h:3210: WARNING: Block quote ends without a blank line; unexpected unindent.
   include/net/mac80211.h:3212: ERROR: Unexpected indentation.
   include/net/mac80211.h:3213: WARNING: Block quote ends without a blank line; unexpected unindent.
   include/net/mac80211.h:1772: ERROR: Unexpected indentation.
   include/net/mac80211.h:1776: WARNING: Block quote ends without a blank line; unexpected unindent.
   kernel/sched/fair.c:7259: WARNING: Inline emphasis start-string without end-string.
   kernel/time/timer.c:1240: ERROR: Unexpected indentation.
   kernel/time/timer.c:1242: ERROR: Unexpected indentation.
   kernel/time/timer.c:1243: WARNING: Block quote ends without a blank line; unexpected unindent.
   include/linux/wait.h:121: WARNING: Block quote ends without a blank line; unexpected unindent.
   include/linux/wait.h:124: ERROR: Unexpected indentation.
   include/linux/wait.h:126: WARNING: Block quote ends without a blank line; unexpected unindent.
   kernel/time/hrtimer.c:1021: WARNING: Block quote ends without a blank line; unexpected unindent.
   kernel/signal.c:317: WARNING: Inline literal start-string without end-string.
   drivers/base/firmware_class.c:1348: WARNING: Bullet list ends without a blank line; unexpected unindent.
   drivers/message/fusion/mptbase.c:5054: WARNING: Definition list ends without a blank line; unexpected unindent.
   drivers/tty/serial/serial_core.c:1893: WARNING: Definition list ends without a blank line; unexpected unindent.
   include/linux/spi/spi.h:369: ERROR: Unexpected indentation.
   WARNING: dvipng command 'dvipng' cannot be run (needed for math display), check the imgmath_dvipng setting
   Documentation/output/media.h.rst:6: WARNING: undefined label: media-ent-f-switch (if the link has no caption the label must precede a section header)

vim +/g_endpoint_config +421 include/media/v4l2-subdev.h

35c3017a Laurent Pinchart       2010-05-05  405  	int (*s_frame_interval)(struct v4l2_subdev *sd,
35c3017a Laurent Pinchart       2010-05-05  406  				struct v4l2_subdev_frame_interval *interval);
b6456c0c Muralidharan Karicheri 2009-11-19  407  	int (*s_dv_timings)(struct v4l2_subdev *sd,
b6456c0c Muralidharan Karicheri 2009-11-19  408  			struct v4l2_dv_timings *timings);
b6456c0c Muralidharan Karicheri 2009-11-19  409  	int (*g_dv_timings)(struct v4l2_subdev *sd,
b6456c0c Muralidharan Karicheri 2009-11-19  410  			struct v4l2_dv_timings *timings);
5d7758ee Hans Verkuil           2012-05-15  411  	int (*query_dv_timings)(struct v4l2_subdev *sd,
5d7758ee Hans Verkuil           2012-05-15  412  			struct v4l2_dv_timings *timings);
91c79530 Guennadi Liakhovetski  2011-07-01  413  	int (*g_mbus_config)(struct v4l2_subdev *sd,
91c79530 Guennadi Liakhovetski  2011-07-01  414  			     struct v4l2_mbus_config *cfg);
91c79530 Guennadi Liakhovetski  2011-07-01  415  	int (*s_mbus_config)(struct v4l2_subdev *sd,
91c79530 Guennadi Liakhovetski  2011-07-01  416  			     const struct v4l2_mbus_config *cfg);
a375e1df Sylwester Nawrocki     2012-09-13  417  	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
a375e1df Sylwester Nawrocki     2012-09-13  418  			   unsigned int *size);
d19af828 Pavel Machek           2016-12-24  419  	int (*g_endpoint_config)(struct v4l2_subdev *sd,
d19af828 Pavel Machek           2016-12-24  420  			    struct v4l2_of_endpoint *cfg);
2a1fcdf0 Hans Verkuil           2008-11-29 @421  };
2a1fcdf0 Hans Verkuil           2008-11-29  422  
5c662984 Mauro Carvalho Chehab  2015-08-22  423  /**
5c662984 Mauro Carvalho Chehab  2015-08-22  424   * struct v4l2_subdev_vbi_ops - Callbacks used when v4l device was opened
5c662984 Mauro Carvalho Chehab  2015-08-22  425   *				  in video mode via the vbi device node.
5c662984 Mauro Carvalho Chehab  2015-08-22  426   *
5c662984 Mauro Carvalho Chehab  2015-08-22  427   *  @decode_vbi_line: video decoders that support sliced VBI need to implement
9ef0b3f3 Mauro Carvalho Chehab  2016-09-09  428   *	this ioctl. Field p of the &struct v4l2_decode_vbi_line is set to the
5c662984 Mauro Carvalho Chehab  2015-08-22  429   *	start of the VBI data that was generated by the decoder. The driver

:::::: The code at line 421 was first introduced by commit
:::::: 2a1fcdf08230522bd5024f91da24aaa6e8d81f59 V4L/DVB (9820): v4l2: add v4l2_device and v4l2_subdev structs to the v4l2 framework.

:::::: TO: Hans Verkuil <hverkuil@xs4all.nl>
:::::: CC: Mauro Carvalho Chehab <mchehab@redhat.com>

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 6425 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-22 10:01     ` [PATCH v6] " Pavel Machek
  2016-12-22 13:39       ` [RFC/PATCH] media: Add video bus switch Pavel Machek
@ 2016-12-27  9:26       ` Sakari Ailus
  2016-12-27 20:45         ` Pavel Machek
  1 sibling, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2016-12-27  9:26 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

On Thu, Dec 22, 2016 at 11:01:04AM +0100, 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>

Thanks!

I fixed a few checkpatch warnings and one or two minor matters, the diff is
here. No functional changes. I'm a bit surprised checkpatch.pl suggests to
use numerical values for permissions but I think I agree with that. Reason
is prioritised agains the rules. :-)

Btw. should we update maintainers as well? Would you like to put yourself
there? Feel free to add me, too...

The patches are here. I think they should be good to go to v4.11.

<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=et8ek8>

Let me know if you're (not) happy with these:

diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c
index d3de087..2df3ff4 100644
--- a/drivers/media/i2c/et8ek8/et8ek8_driver.c
+++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c
@@ -347,13 +347,13 @@ static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length,
 	et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data);
 
 	r = i2c_transfer(client->adapter, &msg, 1);
-	if (r < 0)
+	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;
+	}
 
-	return r;
+	return 0;
 }
 
 static struct et8ek8_reglist *et8ek8_reglist_find_type(
@@ -620,14 +620,13 @@ static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode)
 	rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124,
 				    cbv_mode << 7);
 	if (rval)
-		return 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;
+	return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420);
 }
 
 /* -----------------------------------------------------------------------------
@@ -645,11 +644,11 @@ static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl)
 
 	case V4L2_CID_EXPOSURE:
 	{
-		int rows;
-		struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev);
-		rows = ctrl->val;
+		struct i2c_client *client =
+			v4l2_get_subdevdata(&sensor->subdev);
+
 		return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243,
-					    rows);
+					    ctrl->val);
 	}
 
 	case V4L2_CID_TEST_PATTERN:
@@ -695,8 +694,9 @@ static int et8ek8_init_controls(struct et8ek8_sensor *sensor)
 		u32 min = 1, max = max_rows;
 
 		sensor->exposure =
-			v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops,
-					  V4L2_CID_EXPOSURE, min, max, min, max);
+			v4l2_ctrl_new_std(&sensor->ctrl_handler,
+					  &et8ek8_ctrl_ops, V4L2_CID_EXPOSURE,
+					  min, max, min, max);
 	}
 
 	/* V4L2_CID_PIXEL_RATE */
@@ -722,7 +722,7 @@ static void et8ek8_update_controls(struct et8ek8_sensor *sensor)
 {
 	struct v4l2_ctrl *ctrl;
 	struct et8ek8_mode *mode = &sensor->current_reglist->mode;
-	
+
 	u32 min, max, pixel_rate;
 	static const int S = 8;
 
@@ -1248,7 +1248,7 @@ et8ek8_priv_mem_read(struct device *dev, struct device_attribute *attr,
 
 	return ET8EK8_PRIV_MEM_SIZE;
 }
-static DEVICE_ATTR(priv_mem, S_IRUGO, et8ek8_priv_mem_read, NULL);
+static DEVICE_ATTR(priv_mem, 0444, et8ek8_priv_mem_read, NULL);
 
 /* --------------------------------------------------------------------------
  * V4L2 subdev core operations


-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor
  2016-12-27  9:26       ` [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor Sakari Ailus
@ 2016-12-27 20:45         ` Pavel Machek
  2016-12-27 20:59           ` [PATCH] mark myself as mainainer for camera on N900 Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-27 20:45 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1459 bytes --]

Hi!

On Tue 2016-12-27 11:26:35, Sakari Ailus wrote:
> On Thu, Dec 22, 2016 at 11:01:04AM +0100, 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>
> 
> Thanks!
> 
> I fixed a few checkpatch warnings and one or two minor matters, the diff is
> here. No functional changes. I'm a bit surprised checkpatch.pl suggests to
> use numerical values for permissions but I think I agree with that. Reason
> is prioritised agains the rules. :-)

Yeah, there was big flamewar about the permissions. In the end Linus
decided that everyone knows the octal numbers, but the constants are
tricky. It began with patch series with 1000 patches...

> Btw. should we update maintainers as well? Would you like to put yourself
> there? Feel free to add me, too...

Ok, will do.

> The patches are here. I think they should be good to go to v4.11.
> 
> <URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=et8ek8>
> 
> Let me know if you're (not) happy with these:

Happy, thanks for doing that. And looking forward for v4.11 :-).

Best regards,
									Pavel
									
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [PATCH] mark myself as mainainer for camera on N900
  2016-12-27 20:45         ` Pavel Machek
@ 2016-12-27 20:59           ` Pavel Machek
  2016-12-27 23:57             ` Sebastian Reichel
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2016-12-27 20:59 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1498 bytes --]

Mark and Sakari as maintainers for Nokia N900 camera pieces.

Signed-off-by: Pavel Machek <pavel@ucw.cz>

---

Hi!

> Yeah, there was big flamewar about the permissions. In the end Linus
> decided that everyone knows the octal numbers, but the constants are
> tricky. It began with patch series with 1000 patches...
> 
> > Btw. should we update maintainers as well? Would you like to put yourself
> > there? Feel free to add me, too...
> 
> Ok, will do.

Something like this? Actually, I guess we could merge ADP1653 entry
there. Yes, it is random collection of devices, but are usually tested
"together", so I believe one entry makes sense.

(But I have no problem with having multiple entries, too.)

Thanks,
								Pavel


diff --git a/MAINTAINERS b/MAINTAINERS
index 63cefa6..1cb1d97 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8613,6 +8613,14 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/lftan/nios2.git
 S:	Maintained
 F:	arch/nios2/
 
+NOKIA N900 CAMERA SUPPORT (ET8EK8 SENSOR, AD5820 FOCUS)
+M:	Pavel Machek <pavel@ucw.cz>
+M:	Sakari Ailus <sakari.ailus@iki.fi>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	drivers/media/i2c/et8ek8
+F:	drivers/media/i2c/ad5820.c
+
 NOKIA N900 POWER SUPPLY DRIVERS
 R:	Pali Rohár <pali.rohar@gmail.com>
 F:	include/linux/power/bq2415x_charger.h



-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] mark myself as mainainer for camera on N900
  2016-12-27 20:59           ` [PATCH] mark myself as mainainer for camera on N900 Pavel Machek
@ 2016-12-27 23:57             ` Sebastian Reichel
  2017-01-25 13:48               ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Sebastian Reichel @ 2016-12-27 23:57 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3039 bytes --]

Hi,

On Tue, Dec 27, 2016 at 09:59:23PM +0100, Pavel Machek wrote:
> Mark and Sakari as maintainers for Nokia N900 camera pieces.

^^^ missing me after Mark. Otherwise Mark looks like a name :)

> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> ---
> 
> Hi!
> 
> > Yeah, there was big flamewar about the permissions. In the end Linus
> > decided that everyone knows the octal numbers, but the constants are
> > tricky. It began with patch series with 1000 patches...
> > 
> > > Btw. should we update maintainers as well? Would you like to put yourself
> > > there? Feel free to add me, too...
> > 
> > Ok, will do.
> 
> Something like this? Actually, I guess we could merge ADP1653 entry
> there. Yes, it is random collection of devices, but are usually tested
> "together", so I believe one entry makes sense.
> 
> (But I have no problem with having multiple entries, too.)
> 
> Thanks,
> 								Pavel
> 
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 63cefa6..1cb1d97 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8613,6 +8613,14 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/lftan/nios2.git
>  S:	Maintained
>  F:	arch/nios2/
>  
> +NOKIA N900 CAMERA SUPPORT (ET8EK8 SENSOR, AD5820 FOCUS)
> +M:	Pavel Machek <pavel@ucw.cz>
> +M:	Sakari Ailus <sakari.ailus@iki.fi>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	drivers/media/i2c/et8ek8
> +F:	drivers/media/i2c/ad5820.c

Not sure if this is useful information, but I solved it like this
for N900 power supply drivers:

NOKIA N900 POWER SUPPLY DRIVERS
R:	Pali Rohár <pali.rohar@gmail.com>
F:	include/linux/power/bq2415x_charger.h
F:	include/linux/power/bq27xxx_battery.h
F:	include/linux/power/isp1704_charger.h
F:	drivers/power/supply/bq2415x_charger.c
F:	drivers/power/supply/bq27xxx_battery.c
F:	drivers/power/supply/bq27xxx_battery_i2c.c
F:	drivers/power/supply/isp1704_charger.c
F:	drivers/power/supply/rx51_battery.c

TI BQ27XXX POWER SUPPLY DRIVER
R:	Andrew F. Davis <afd@ti.com>
F:	include/linux/power/bq27xxx_battery.h
F:	drivers/power/supply/bq27xxx_battery.c
F:	drivers/power/supply/bq27xxx_battery_i2c.c

POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS
M:	Sebastian Reichel <sre@kernel.org>
L:	linux-pm@vger.kernel.org
T:	git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
S:	Maintained
F:	Documentation/devicetree/bindings/power/supply/
F:	include/linux/power_supply.h
F:	drivers/power/supply/

This makes it quite easy to see who applies patches and who should
be Cc'd for what reason:

$ ./scripts/get_maintainer.pl -f drivers/power/supply/bq27xxx_battery.c 
"Pali Rohár" <pali.rohar@gmail.com> (reviewer:NOKIA N900 POWER SUPPLY DRIVERS)
"Andrew F. Davis" <afd@ti.com> (reviewer:TI BQ27XXX POWER SUPPLY DRIVER)
Sebastian Reichel <sre@kernel.org> (maintainer:POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS)
linux-pm@vger.kernel.org (open list:POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS)
linux-kernel@vger.kernel.org (open list)

-- Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] media: Add video bus switch
  2016-12-24 15:20         ` [PATCH] " Pavel Machek
  2016-12-24 18:35           ` kbuild test robot
@ 2017-01-12 11:17           ` Pavel Machek
  2017-02-03 22:25             ` Sakari Ailus
  2017-02-03 12:35           ` [PATCH] devicetree: " Pavel Machek
  2 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-01-12 11:17 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 17055 bytes --]

On Sat 2016-12-24 16:20:31, Pavel Machek wrote:
> 
> N900 contains front and back camera, with a switch between the
> two. This adds support for the switch component, and it is now
> possible to select between front and back cameras during runtime.
> 
> Signed-off-by: Sebastian Reichel <sre@kernel.org>
> Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>

Can I get some feedback here? This works for me -- I can use both
cameras during one boot -- can I get it applied?

Thanks,
								Pavel

> diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> new file mode 100644
> index 0000000..1b9f8e0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> @@ -0,0 +1,63 @@
> +Video Bus Switch Binding
> +========================
> +
> +This is a binding for a gpio controlled switch for camera interfaces. Such a
> +device is used on some embedded devices to connect two cameras to the same
> +interface of a image signal processor.
> +
> +Required properties
> +===================
> +
> +compatible	: must contain "video-bus-switch"
> +switch-gpios	: GPIO specifier for the gpio, which can toggle the
> +		  selected camera. The GPIO should be configured, so
> +		  that a disabled GPIO means, that the first port is
> +		  selected.
> +
> +Required Port nodes
> +===================
> +
> +More documentation on these bindings is available in
> +video-interfaces.txt in the same directory.
> +
> +reg		: The interface:
> +		  0 - port for image signal processor
> +		  1 - port for first camera sensor
> +		  2 - port for second camera sensor
> +
> +Example
> +=======
> +
> +video-bus-switch {
> +	compatible = "video-bus-switch"
> +	switch-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
> +
> +	ports {
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		port@0 {
> +			reg = <0>;
> +
> +			csi_switch_in: endpoint {
> +				remote-endpoint = <&csi_isp>;
> +			};
> +		};
> +
> +		port@1 {
> +			reg = <1>;
> +
> +			csi_switch_out1: endpoint {
> +				remote-endpoint = <&csi_cam1>;
> +			};
> +		};
> +
> +		port@2 {
> +			reg = <2>;
> +
> +			csi_switch_out2: endpoint {
> +				remote-endpoint = <&csi_cam2>;
> +			};
> +		};
> +	};
> +};
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index ce4a96f..a4b509e 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -91,6 +91,16 @@ config VIDEO_OMAP3_DEBUG
>  	---help---
>  	  Enable debug messages on OMAP 3 camera controller driver.
>  
> +config VIDEO_BUS_SWITCH
> +	tristate "Video Bus switch"
> +	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> +	depends on MEDIA_CONTROLLER
> +	depends on OF
> +	---help---
> +	  Driver for a GPIO controlled video bus switch, which is used to
> +	  connect two camera sensors to the same port a the image signal
> +	  processor.
> +
>  config VIDEO_PXA27x
>  	tristate "PXA27x Quick Capture Interface driver"
>  	depends on VIDEO_DEV && HAS_DMA
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 40b18d1..8eafc27 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -11,6 +11,8 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/
>  obj-$(CONFIG_VIDEO_OMAP3)	+= omap3isp/
>  obj-$(CONFIG_VIDEO_PXA27x)	+= pxa_camera.o
>  
> +obj-$(CONFIG_VIDEO_BUS_SWITCH) += video-bus-switch.o
> +
>  obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o
>  
>  obj-$(CONFIG_VIDEO_VIVID)		+= vivid/
> diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
> new file mode 100644
> index 0000000..6400cfc
> --- /dev/null
> +++ b/drivers/media/platform/video-bus-switch.c
> @@ -0,0 +1,387 @@
> +/*
> + * Generic driver for video bus switches
> + *
> + * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
> + * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz>
> + *
> + * 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.
> + */
> +
> +#define DEBUG
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/gpio/consumer.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-of.h>
> +
> +/*
> + * TODO:
> + * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
> + */
> +
> +#define CSI_SWITCH_SUBDEVS 2
> +#define CSI_SWITCH_PORTS 3
> +
> +enum vbs_state {
> +	CSI_SWITCH_DISABLED = 0,
> +	CSI_SWITCH_PORT_1 = 1,
> +	CSI_SWITCH_PORT_2 = 2,
> +};
> +
> +struct vbs_src_pads {
> +	struct media_entity *src;
> +	int src_pad;
> +};
> +
> +struct vbs_data {
> +	struct gpio_desc *swgpio;
> +	struct v4l2_subdev subdev;
> +	struct v4l2_async_notifier notifier;
> +	struct media_pad pads[CSI_SWITCH_PORTS];
> +	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
> +	struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS];
> +	enum vbs_state state;
> +};
> +
> +struct vbs_async_subdev {
> +	struct v4l2_subdev *sd;
> +	struct v4l2_async_subdev asd;
> +	u8 port;
> +};
> +
> +static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
> +{
> +	struct v4l2_async_notifier *notifier = &pdata->notifier;
> +	struct device_node *node = NULL;
> +
> +	notifier->subdevs = devm_kcalloc(dev, CSI_SWITCH_SUBDEVS,
> +		sizeof(*notifier->subdevs), GFP_KERNEL);
> +	if (!notifier->subdevs)
> +		return -ENOMEM;
> +
> +	notifier->num_subdevs = 0;
> +	while (notifier->num_subdevs < CSI_SWITCH_SUBDEVS &&
> +	       (node = of_graph_get_next_endpoint(dev->of_node, node))) {
> +		struct v4l2_of_endpoint vep;
> +		struct vbs_async_subdev *ssd;
> +
> +		/* skip first port (connected to isp) */
> +		v4l2_of_parse_endpoint(node, &vep);
> +		if (vep.base.port == 0) {
> +			struct device_node *ispnode;
> +
> +			ispnode = of_graph_get_remote_port_parent(node);
> +			if (!ispnode) {
> +				dev_warn(dev, "bad remote port parent\n");
> +				return -EINVAL;
> +			}
> +
> +			of_node_put(node);
> +			continue;
> +		}
> +
> +		ssd = devm_kzalloc(dev, sizeof(*ssd), GFP_KERNEL);
> +		if (!ssd) {
> +			of_node_put(node);
> +			return -ENOMEM;
> +		}
> +
> +		ssd->port = vep.base.port;
> +
> +		notifier->subdevs[notifier->num_subdevs] = &ssd->asd;
> +
> +		ssd->asd.match.of.node = of_graph_get_remote_port_parent(node);
> +		of_node_put(node);
> +		if (!ssd->asd.match.of.node) {
> +			dev_warn(dev, "bad remote port parent\n");
> +			return -EINVAL;
> +		}
> +
> +		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
> +		pdata->vep[notifier->num_subdevs] = vep;
> +		notifier->num_subdevs++;
> +	}
> +
> +	return notifier->num_subdevs;
> +}
> +
> +static int vbs_registered(struct v4l2_subdev *sd)
> +{
> +	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
> +	struct vbs_data *pdata;
> +	int err;
> +
> +	dev_dbg(sd->dev, "registered, init notifier...\n");
> +
> +	pdata = v4l2_get_subdevdata(sd);
> +
> +	err = v4l2_async_notifier_register(v4l2_dev, &pdata->notifier);
> +	if (err)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static struct v4l2_subdev *vbs_get_remote_subdev(struct v4l2_subdev *sd)
> +{
> +	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
> +	struct media_entity *src;
> +
> +	if (pdata->state == CSI_SWITCH_DISABLED)
> +		return ERR_PTR(-ENXIO);
> +
> +	src = pdata->src_pads[pdata->state].src;
> +
> +	return media_entity_to_v4l2_subdev(src);
> +}
> +
> +static int vbs_link_setup(struct media_entity *entity,
> +			  const struct media_pad *local,
> +			  const struct media_pad *remote, u32 flags)
> +{
> +	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
> +	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
> +	bool enable = flags & MEDIA_LNK_FL_ENABLED;
> +
> +	if (local->index > CSI_SWITCH_PORTS - 1)
> +		return -ENXIO;
> +
> +	/* no configuration needed on source port */
> +	if (local->index == 0)
> +		return 0;
> +
> +	if (!enable) {
> +		if (local->index == pdata->state) {
> +			pdata->state = CSI_SWITCH_DISABLED;
> +
> +			/* Make sure we have both cameras enabled */
> +			gpiod_set_value(pdata->swgpio, 1);
> +			return 0;
> +		} else {
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* there can only be one active sink at the same time */
> +	if (pdata->state != CSI_SWITCH_DISABLED)
> +		return -EBUSY;
> +
> +	dev_dbg(sd->dev, "Link setup: going to config %d\n", local->index);
> +
> +	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
> +	pdata->state = local->index;
> +
> +	sd = vbs_get_remote_subdev(sd);
> +	if (IS_ERR(sd))
> +		return PTR_ERR(sd);
> +
> +	pdata->subdev.ctrl_handler = sd->ctrl_handler;
> +
> +	return 0;
> +}
> +
> +static int vbs_subdev_notifier_bound(struct v4l2_async_notifier *async,
> +				     struct v4l2_subdev *subdev,
> +				     struct v4l2_async_subdev *asd)
> +{
> +	struct vbs_data *pdata = container_of(async,
> +		struct vbs_data, notifier);
> +	struct vbs_async_subdev *ssd =
> +		container_of(asd, struct vbs_async_subdev, asd);
> +	struct media_entity *sink = &pdata->subdev.entity;
> +	struct media_entity *src = &subdev->entity;
> +	int sink_pad = ssd->port;
> +	int src_pad;
> +
> +	if (sink_pad >= sink->num_pads) {
> +		dev_err(pdata->subdev.dev, "no sink pad in internal entity!\n");
> +		return -EINVAL;
> +	}
> +
> +	for (src_pad = 0; src_pad < subdev->entity.num_pads; src_pad++) {
> +		if (subdev->entity.pads[src_pad].flags & MEDIA_PAD_FL_SOURCE)
> +			break;
> +	}
> +
> +	if (src_pad >= src->num_pads) {
> +		dev_err(pdata->subdev.dev, "no source pad in external entity\n");
> +		return -EINVAL;
> +	}
> +
> +	pdata->src_pads[sink_pad].src = src;
> +	pdata->src_pads[sink_pad].src_pad = src_pad;
> +	ssd->sd = subdev;
> +
> +	return 0;
> +}
> +
> +static int vbs_subdev_notifier_complete(struct v4l2_async_notifier *async)
> +{
> +	struct vbs_data *pdata = container_of(async, struct vbs_data, notifier);
> +	struct media_entity *sink = &pdata->subdev.entity;
> +	int sink_pad;
> +
> +	for (sink_pad = 1; sink_pad < CSI_SWITCH_PORTS; sink_pad++) {
> +		struct media_entity *src = pdata->src_pads[sink_pad].src;
> +		int src_pad = pdata->src_pads[sink_pad].src_pad;
> +		int err;
> +
> +		err = media_create_pad_link(src, src_pad, sink, sink_pad, 0);
> +		if (err < 0)
> +			return err;
> +
> +		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
> +			src->name, src_pad, sink->name, sink_pad);
> +	}
> +
> +	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);
> +}
> +
> +static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
> +
> +	if (IS_ERR(subdev))
> +		return PTR_ERR(subdev);
> +
> +	return v4l2_subdev_call(subdev, video, s_stream, enable);
> +}
> +
> +static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg)
> +{
> +	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
> +	dev_dbg(sd->dev, "vbs_g_endpoint_config... active port is %d\n", pdata->state);
> +	*cfg = pdata->vep[pdata->state - 1];
> +
> +	return 0;
> +}
> +
> +
> +static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
> +	.registered = &vbs_registered,
> +};
> +
> +static const struct media_entity_operations vbs_media_ops = {
> +	.link_setup = vbs_link_setup,
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/* subdev video operations */
> +static const struct v4l2_subdev_video_ops vbs_video_ops = {
> +	.s_stream = vbs_s_stream,
> +	.g_endpoint_config = vbs_g_endpoint_config,
> +};
> +
> +static const struct v4l2_subdev_ops vbs_ops = {
> +	.video = &vbs_video_ops,
> +};
> +
> +static int video_bus_switch_probe(struct platform_device *pdev)
> +{
> +	struct vbs_data *pdata;
> +	int err = 0;
> +
> +	/* platform data */
> +	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata) {
> +		dev_dbg(&pdev->dev, "video-bus-switch: not enough memory\n");
> +		return -ENOMEM;
> +	}
> +	platform_set_drvdata(pdev, pdata);
> +
> +	/* switch gpio */
> +	pdata->swgpio = devm_gpiod_get(&pdev->dev, "switch", GPIOD_OUT_HIGH);
> +	if (IS_ERR(pdata->swgpio)) {
> +		err = PTR_ERR(pdata->swgpio);
> +		dev_err(&pdev->dev, "Failed to request gpio: %d\n", err);
> +		return err;
> +	}
> +
> +	/* find sub-devices */
> +	err = vbs_of_parse_nodes(&pdev->dev, pdata);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "Failed to parse nodes: %d\n", err);
> +		return err;
> +	}
> +
> +	pdata->state = CSI_SWITCH_DISABLED;
> +	pdata->notifier.bound = vbs_subdev_notifier_bound;
> +	pdata->notifier.complete = vbs_subdev_notifier_complete;
> +
> +	/* setup subdev */
> +	pdata->pads[0].flags = MEDIA_PAD_FL_SOURCE;
> +	pdata->pads[1].flags = MEDIA_PAD_FL_SINK;
> +	pdata->pads[2].flags = MEDIA_PAD_FL_SINK;
> +
> +	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
> +	pdata->subdev.dev = &pdev->dev;
> +	pdata->subdev.owner = pdev->dev.driver->owner;
> +	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
> +	v4l2_set_subdevdata(&pdata->subdev, pdata);
> +	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
> +	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;
> +	pdata->subdev.entity.ops = &vbs_media_ops;
> +	pdata->subdev.internal_ops = &vbs_internal_ops;
> +	err = media_entity_pads_init(&pdata->subdev.entity, CSI_SWITCH_PORTS,
> +				pdata->pads);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "Failed to init media entity: %d\n", err);
> +		return err;
> +	}
> +
> +	/* register subdev */
> +	err = v4l2_async_register_subdev(&pdata->subdev);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
> +		media_entity_cleanup(&pdata->subdev.entity);
> +		return err;
> +	}
> +
> +	dev_info(&pdev->dev, "video-bus-switch registered\n");
> +
> +	return 0;
> +}
> +
> +static int video_bus_switch_remove(struct platform_device *pdev)
> +{
> +	struct vbs_data *pdata = platform_get_drvdata(pdev);
> +
> +	v4l2_async_notifier_unregister(&pdata->notifier);
> +	v4l2_async_unregister_subdev(&pdata->subdev);
> +	media_entity_cleanup(&pdata->subdev.entity);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id video_bus_switch_of_match[] = {
> +	{ .compatible = "video-bus-switch" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, video_bus_switch_of_match);
> +
> +static struct platform_driver video_bus_switch_driver = {
> +	.driver = {
> +		.name	= "video-bus-switch",
> +		.of_match_table = video_bus_switch_of_match,
> +	},
> +	.probe		= video_bus_switch_probe,
> +	.remove		= video_bus_switch_remove,
> +};
> +
> +module_platform_driver(video_bus_switch_driver);
> +
> +MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
> +MODULE_DESCRIPTION("Video Bus Switch");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:video-bus-switch");
> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
> index cf778c5..448dbb5 100644
> --- a/include/media/v4l2-subdev.h
> +++ b/include/media/v4l2-subdev.h
> @@ -25,6 +25,7 @@
>  #include <media/v4l2-dev.h>
>  #include <media/v4l2-fh.h>
>  #include <media/v4l2-mediabus.h>
> +#include <media/v4l2-of.h>
>  
>  /* generic v4l2_device notify callback notification values */
>  #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
> @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
>  			     const struct v4l2_mbus_config *cfg);
>  	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
>  			   unsigned int *size);
> +	int (*g_endpoint_config)(struct v4l2_subdev *sd,
> +			    struct v4l2_of_endpoint *cfg);
>  };
>  
>  /**
> diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
> index 4890787..94648ab 100644
> --- a/include/uapi/linux/media.h
> +++ b/include/uapi/linux/media.h
> @@ -147,6 +147,7 @@ struct media_device_info {
>   * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER.
>   */
>  #define MEDIA_ENT_F_TUNER		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 5)
> +#define MEDIA_ENT_F_SWITCH		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 6)
>  
>  #define MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN	MEDIA_ENT_F_OLD_SUBDEV_BASE
>  
> 
> 



-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] mark myself as mainainer for camera on N900
  2016-12-27 23:57             ` Sebastian Reichel
@ 2017-01-25 13:48               ` Sakari Ailus
  0 siblings, 0 replies; 97+ messages in thread
From: Sakari Ailus @ 2017-01-25 13:48 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Pavel Machek, ivo.g.dimitrov.75, pali.rohar, linux-media, galak,
	mchehab, linux-kernel

Hi Sebastian and Pavel,

On Wed, Dec 28, 2016 at 12:57:21AM +0100, Sebastian Reichel wrote:
> Hi,
> 
> On Tue, Dec 27, 2016 at 09:59:23PM +0100, Pavel Machek wrote:
> > Mark and Sakari as maintainers for Nokia N900 camera pieces.
> 
> ^^^ missing me after Mark. Otherwise Mark looks like a name :)
> 
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> > 
> > ---
> > 
> > Hi!
> > 
> > > Yeah, there was big flamewar about the permissions. In the end Linus
> > > decided that everyone knows the octal numbers, but the constants are
> > > tricky. It began with patch series with 1000 patches...
> > > 
> > > > Btw. should we update maintainers as well? Would you like to put yourself
> > > > there? Feel free to add me, too...
> > > 
> > > Ok, will do.
> > 
> > Something like this? Actually, I guess we could merge ADP1653 entry
> > there. Yes, it is random collection of devices, but are usually tested
> > "together", so I believe one entry makes sense.
> > 
> > (But I have no problem with having multiple entries, too.)
> > 
> > Thanks,
> > 								Pavel
> > 
> > 
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 63cefa6..1cb1d97 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -8613,6 +8613,14 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/lftan/nios2.git
> >  S:	Maintained
> >  F:	arch/nios2/
> >  
> > +NOKIA N900 CAMERA SUPPORT (ET8EK8 SENSOR, AD5820 FOCUS)
> > +M:	Pavel Machek <pavel@ucw.cz>
> > +M:	Sakari Ailus <sakari.ailus@iki.fi>
> > +L:	linux-media@vger.kernel.org
> > +S:	Maintained
> > +F:	drivers/media/i2c/et8ek8
> > +F:	drivers/media/i2c/ad5820.c
> 
> Not sure if this is useful information, but I solved it like this
> for N900 power supply drivers:
> 
> NOKIA N900 POWER SUPPLY DRIVERS
> R:	Pali Rohár <pali.rohar@gmail.com>
> F:	include/linux/power/bq2415x_charger.h
> F:	include/linux/power/bq27xxx_battery.h
> F:	include/linux/power/isp1704_charger.h
> F:	drivers/power/supply/bq2415x_charger.c
> F:	drivers/power/supply/bq27xxx_battery.c
> F:	drivers/power/supply/bq27xxx_battery_i2c.c
> F:	drivers/power/supply/isp1704_charger.c
> F:	drivers/power/supply/rx51_battery.c
> 
> TI BQ27XXX POWER SUPPLY DRIVER
> R:	Andrew F. Davis <afd@ti.com>
> F:	include/linux/power/bq27xxx_battery.h
> F:	drivers/power/supply/bq27xxx_battery.c
> F:	drivers/power/supply/bq27xxx_battery_i2c.c
> 
> POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS
> M:	Sebastian Reichel <sre@kernel.org>
> L:	linux-pm@vger.kernel.org
> T:	git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
> S:	Maintained
> F:	Documentation/devicetree/bindings/power/supply/
> F:	include/linux/power_supply.h
> F:	drivers/power/supply/
> 
> This makes it quite easy to see who applies patches and who should
> be Cc'd for what reason:
> 
> $ ./scripts/get_maintainer.pl -f drivers/power/supply/bq27xxx_battery.c 
> "Pali Rohár" <pali.rohar@gmail.com> (reviewer:NOKIA N900 POWER SUPPLY DRIVERS)
> "Andrew F. Davis" <afd@ti.com> (reviewer:TI BQ27XXX POWER SUPPLY DRIVER)
> Sebastian Reichel <sre@kernel.org> (maintainer:POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS)
> linux-pm@vger.kernel.org (open list:POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS)
> linux-kernel@vger.kernel.org (open list)

I'm adding Pavel's patch to my tree and then send a pull req to Mauro. If
further changes are needed then let's write more patches.

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [PATCH] devicetree: Add video bus switch
  2016-12-24 15:20         ` [PATCH] " Pavel Machek
  2016-12-24 18:35           ` kbuild test robot
  2017-01-12 11:17           ` Pavel Machek
@ 2017-02-03 12:35           ` Pavel Machek
  2017-02-03 13:07             ` Sakari Ailus
                               ` (2 more replies)
  2 siblings, 3 replies; 97+ messages in thread
From: Pavel Machek @ 2017-02-03 12:35 UTC (permalink / raw)
  To: Sakari Ailus, robh+dt, devicetree
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2252 bytes --]


N900 contains front and back camera, with a switch between the
two. This adds support for the switch component, and it is now
possible to select between front and back cameras during runtime.

This adds documentation for the devicetree binding.

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


diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
new file mode 100644
index 0000000..1b9f8e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
@@ -0,0 +1,63 @@
+Video Bus Switch Binding
+========================
+
+This is a binding for a gpio controlled switch for camera interfaces. Such a
+device is used on some embedded devices to connect two cameras to the same
+interface of a image signal processor.
+
+Required properties
+===================
+
+compatible	: must contain "video-bus-switch"
+switch-gpios	: GPIO specifier for the gpio, which can toggle the
+		  selected camera. The GPIO should be configured, so
+		  that a disabled GPIO means, that the first port is
+		  selected.
+
+Required Port nodes
+===================
+
+More documentation on these bindings is available in
+video-interfaces.txt in the same directory.
+
+reg		: The interface:
+		  0 - port for image signal processor
+		  1 - port for first camera sensor
+		  2 - port for second camera sensor
+
+Example
+=======
+
+video-bus-switch {
+	compatible = "video-bus-switch"
+	switch-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			csi_switch_in: endpoint {
+				remote-endpoint = <&csi_isp>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			csi_switch_out1: endpoint {
+				remote-endpoint = <&csi_cam1>;
+			};
+		};
+
+		port@2 {
+			reg = <2>;
+
+			csi_switch_out2: endpoint {
+				remote-endpoint = <&csi_cam2>;
+			};
+		};
+	};
+};



-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 12:35           ` [PATCH] devicetree: " Pavel Machek
@ 2017-02-03 13:07             ` Sakari Ailus
  2017-02-03 21:06               ` Pavel Machek
  2017-02-03 13:32             ` Pali Rohár
  2017-02-08 21:36             ` Rob Herring
  2 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2017-02-03 13:07 UTC (permalink / raw)
  To: Pavel Machek
  Cc: robh+dt, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

Hi Pavel,

My apologies for the delays in reviewing. Feel free to ping me in the future
if this happens. :-)

On Fri, Feb 03, 2017 at 01:35:08PM +0100, Pavel Machek wrote:
> 
> N900 contains front and back camera, with a switch between the
> two. This adds support for the switch component, and it is now
> possible to select between front and back cameras during runtime.
> 
> This adds documentation for the devicetree binding.
> 
> Signed-off-by: Sebastian Reichel <sre@kernel.org>
> Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> 
> diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> new file mode 100644
> index 0000000..1b9f8e0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> @@ -0,0 +1,63 @@
> +Video Bus Switch Binding
> +========================
> +
> +This is a binding for a gpio controlled switch for camera interfaces. Such a
> +device is used on some embedded devices to connect two cameras to the same
> +interface of a image signal processor.
> +
> +Required properties
> +===================
> +
> +compatible	: must contain "video-bus-switch"

How generic is this? Should we have e.g. nokia,video-bus-switch? And if so,
change the file name accordingly.

> +switch-gpios	: GPIO specifier for the gpio, which can toggle the
> +		  selected camera. The GPIO should be configured, so
> +		  that a disabled GPIO means, that the first port is
> +		  selected.
> +
> +Required Port nodes
> +===================
> +
> +More documentation on these bindings is available in
> +video-interfaces.txt in the same directory.
> +
> +reg		: The interface:
> +		  0 - port for image signal processor
> +		  1 - port for first camera sensor
> +		  2 - port for second camera sensor

I'd say this must be pretty much specific to the one in N900. You could have
more ports. Or you could say that ports beyond 0 are camera sensors. I guess
this is good enough for now though, it can be changed later on with the
source if a need arises.

Btw. was it still considered a problem that the endpoint properties for the
sensors can be different? With the g_routing() pad op which is to be added,
the ISP driver (should actually go to a framework somewhere) could parse the
graph and find the proper endpoint there.

I don't think we need to wait for that now, but this is how the problem
could be solved going forward.

> +
> +Example
> +=======
> +
> +video-bus-switch {
> +	compatible = "video-bus-switch"
> +	switch-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
> +
> +	ports {
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		port@0 {
> +			reg = <0>;
> +
> +			csi_switch_in: endpoint {
> +				remote-endpoint = <&csi_isp>;
> +			};
> +		};
> +
> +		port@1 {
> +			reg = <1>;
> +
> +			csi_switch_out1: endpoint {
> +				remote-endpoint = <&csi_cam1>;
> +			};
> +		};
> +
> +		port@2 {
> +			reg = <2>;
> +
> +			csi_switch_out2: endpoint {
> +				remote-endpoint = <&csi_cam2>;
> +			};
> +		};
> +	};
> +};
> 
> 
> 

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 12:35           ` [PATCH] devicetree: " Pavel Machek
  2017-02-03 13:07             ` Sakari Ailus
@ 2017-02-03 13:32             ` Pali Rohár
  2017-02-03 21:07               ` Pavel Machek
  2017-02-08 21:36             ` Rob Herring
  2 siblings, 1 reply; 97+ messages in thread
From: Pali Rohár @ 2017-02-03 13:32 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	linux-media, galak, mchehab, linux-kernel

On Friday 03 February 2017 13:35:08 Pavel Machek wrote:
> N900 contains front and back camera, with a switch between the
> two. This adds support for the switch component, and it is now
> possible to select between front and back cameras during runtime.

IIRC for controlling cameras on N900 there are two GPIOs. Should not you
have both in switch driver?

-- 
Pali Rohár
pali.rohar@gmail.com

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 13:07             ` Sakari Ailus
@ 2017-02-03 21:06               ` Pavel Machek
  2017-02-03 21:34                 ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-03 21:06 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: robh+dt, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 15970 bytes --]

Hi!

> My apologies for the delays in reviewing. Feel free to ping me in the future
> if this happens. :-)

No problem :-). If you could review the C-code, too... that would be
nice. It should be in your inbox somewhere (and I attached it below,
without the dts part).


> > +Required properties
> > +===================
> > +
> > +compatible	: must contain "video-bus-switch"
> 
> How generic is this? Should we have e.g. nokia,video-bus-switch? And if so,
> change the file name accordingly.

Generic for "single GPIO controls the switch", AFAICT. But that should
be common enough...

> > +reg		: The interface:
> > +		  0 - port for image signal processor
> > +		  1 - port for first camera sensor
> > +		  2 - port for second camera sensor
> 
> I'd say this must be pretty much specific to the one in N900. You could have
> more ports. Or you could say that ports beyond 0 are camera sensors. I guess
> this is good enough for now though, it can be changed later on with the
> source if a need arises.

Well, I'd say that selecting between two sensors is going to be the
common case. If someone needs more than two, it will no longer be
simple GPIO, so we'll have some fixing to do.

> Btw. was it still considered a problem that the endpoint properties for the
> sensors can be different? With the g_routing() pad op which is to be added,
> the ISP driver (should actually go to a framework somewhere) could parse the
> graph and find the proper endpoint there.

I don't know about g_routing. I added g_endpoint_config method that
passes the configuration, and that seems to work for me.

I don't see g_routing in next-20170201 . Is there place to look?

Best regards,
								Pavel

---

N900 contains front and back camera, with a switch between the
two. This adds support for the switch component, and it is now
possible to select between front and back cameras during runtime.

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


diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index ce4a96f..a4b509e 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -91,6 +91,16 @@ config VIDEO_OMAP3_DEBUG
 	---help---
 	  Enable debug messages on OMAP 3 camera controller driver.
 
+config VIDEO_BUS_SWITCH
+	tristate "Video Bus switch"
+	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	depends on MEDIA_CONTROLLER
+	depends on OF
+	---help---
+	  Driver for a GPIO controlled video bus switch, which is used to
+	  connect two camera sensors to the same port a the image signal
+	  processor.
+
 config VIDEO_PXA27x
 	tristate "PXA27x Quick Capture Interface driver"
 	depends on VIDEO_DEV && HAS_DMA
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 40b18d1..8eafc27 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/
 obj-$(CONFIG_VIDEO_OMAP3)	+= omap3isp/
 obj-$(CONFIG_VIDEO_PXA27x)	+= pxa_camera.o
 
+obj-$(CONFIG_VIDEO_BUS_SWITCH) += video-bus-switch.o
+
 obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o
 
 obj-$(CONFIG_VIDEO_VIVID)		+= vivid/
diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
new file mode 100644
index 0000000..6400cfc
--- /dev/null
+++ b/drivers/media/platform/video-bus-switch.c
@@ -0,0 +1,387 @@
+/*
+ * Generic driver for video bus switches
+ *
+ * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
+ * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz>
+ *
+ * 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.
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/gpio/consumer.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-of.h>
+
+/*
+ * TODO:
+ * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
+ */
+
+#define CSI_SWITCH_SUBDEVS 2
+#define CSI_SWITCH_PORTS 3
+
+enum vbs_state {
+	CSI_SWITCH_DISABLED = 0,
+	CSI_SWITCH_PORT_1 = 1,
+	CSI_SWITCH_PORT_2 = 2,
+};
+
+struct vbs_src_pads {
+	struct media_entity *src;
+	int src_pad;
+};
+
+struct vbs_data {
+	struct gpio_desc *swgpio;
+	struct v4l2_subdev subdev;
+	struct v4l2_async_notifier notifier;
+	struct media_pad pads[CSI_SWITCH_PORTS];
+	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
+	struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS];
+	enum vbs_state state;
+};
+
+struct vbs_async_subdev {
+	struct v4l2_subdev *sd;
+	struct v4l2_async_subdev asd;
+	u8 port;
+};
+
+static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
+{
+	struct v4l2_async_notifier *notifier = &pdata->notifier;
+	struct device_node *node = NULL;
+
+	notifier->subdevs = devm_kcalloc(dev, CSI_SWITCH_SUBDEVS,
+		sizeof(*notifier->subdevs), GFP_KERNEL);
+	if (!notifier->subdevs)
+		return -ENOMEM;
+
+	notifier->num_subdevs = 0;
+	while (notifier->num_subdevs < CSI_SWITCH_SUBDEVS &&
+	       (node = of_graph_get_next_endpoint(dev->of_node, node))) {
+		struct v4l2_of_endpoint vep;
+		struct vbs_async_subdev *ssd;
+
+		/* skip first port (connected to isp) */
+		v4l2_of_parse_endpoint(node, &vep);
+		if (vep.base.port == 0) {
+			struct device_node *ispnode;
+
+			ispnode = of_graph_get_remote_port_parent(node);
+			if (!ispnode) {
+				dev_warn(dev, "bad remote port parent\n");
+				return -EINVAL;
+			}
+
+			of_node_put(node);
+			continue;
+		}
+
+		ssd = devm_kzalloc(dev, sizeof(*ssd), GFP_KERNEL);
+		if (!ssd) {
+			of_node_put(node);
+			return -ENOMEM;
+		}
+
+		ssd->port = vep.base.port;
+
+		notifier->subdevs[notifier->num_subdevs] = &ssd->asd;
+
+		ssd->asd.match.of.node = of_graph_get_remote_port_parent(node);
+		of_node_put(node);
+		if (!ssd->asd.match.of.node) {
+			dev_warn(dev, "bad remote port parent\n");
+			return -EINVAL;
+		}
+
+		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+		pdata->vep[notifier->num_subdevs] = vep;
+		notifier->num_subdevs++;
+	}
+
+	return notifier->num_subdevs;
+}
+
+static int vbs_registered(struct v4l2_subdev *sd)
+{
+	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
+	struct vbs_data *pdata;
+	int err;
+
+	dev_dbg(sd->dev, "registered, init notifier...\n");
+
+	pdata = v4l2_get_subdevdata(sd);
+
+	err = v4l2_async_notifier_register(v4l2_dev, &pdata->notifier);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct v4l2_subdev *vbs_get_remote_subdev(struct v4l2_subdev *sd)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	struct media_entity *src;
+
+	if (pdata->state == CSI_SWITCH_DISABLED)
+		return ERR_PTR(-ENXIO);
+
+	src = pdata->src_pads[pdata->state].src;
+
+	return media_entity_to_v4l2_subdev(src);
+}
+
+static int vbs_link_setup(struct media_entity *entity,
+			  const struct media_pad *local,
+			  const struct media_pad *remote, u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	bool enable = flags & MEDIA_LNK_FL_ENABLED;
+
+	if (local->index > CSI_SWITCH_PORTS - 1)
+		return -ENXIO;
+
+	/* no configuration needed on source port */
+	if (local->index == 0)
+		return 0;
+
+	if (!enable) {
+		if (local->index == pdata->state) {
+			pdata->state = CSI_SWITCH_DISABLED;
+
+			/* Make sure we have both cameras enabled */
+			gpiod_set_value(pdata->swgpio, 1);
+			return 0;
+		} else {
+			return -EINVAL;
+		}
+	}
+
+	/* there can only be one active sink at the same time */
+	if (pdata->state != CSI_SWITCH_DISABLED)
+		return -EBUSY;
+
+	dev_dbg(sd->dev, "Link setup: going to config %d\n", local->index);
+
+	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
+	pdata->state = local->index;
+
+	sd = vbs_get_remote_subdev(sd);
+	if (IS_ERR(sd))
+		return PTR_ERR(sd);
+
+	pdata->subdev.ctrl_handler = sd->ctrl_handler;
+
+	return 0;
+}
+
+static int vbs_subdev_notifier_bound(struct v4l2_async_notifier *async,
+				     struct v4l2_subdev *subdev,
+				     struct v4l2_async_subdev *asd)
+{
+	struct vbs_data *pdata = container_of(async,
+		struct vbs_data, notifier);
+	struct vbs_async_subdev *ssd =
+		container_of(asd, struct vbs_async_subdev, asd);
+	struct media_entity *sink = &pdata->subdev.entity;
+	struct media_entity *src = &subdev->entity;
+	int sink_pad = ssd->port;
+	int src_pad;
+
+	if (sink_pad >= sink->num_pads) {
+		dev_err(pdata->subdev.dev, "no sink pad in internal entity!\n");
+		return -EINVAL;
+	}
+
+	for (src_pad = 0; src_pad < subdev->entity.num_pads; src_pad++) {
+		if (subdev->entity.pads[src_pad].flags & MEDIA_PAD_FL_SOURCE)
+			break;
+	}
+
+	if (src_pad >= src->num_pads) {
+		dev_err(pdata->subdev.dev, "no source pad in external entity\n");
+		return -EINVAL;
+	}
+
+	pdata->src_pads[sink_pad].src = src;
+	pdata->src_pads[sink_pad].src_pad = src_pad;
+	ssd->sd = subdev;
+
+	return 0;
+}
+
+static int vbs_subdev_notifier_complete(struct v4l2_async_notifier *async)
+{
+	struct vbs_data *pdata = container_of(async, struct vbs_data, notifier);
+	struct media_entity *sink = &pdata->subdev.entity;
+	int sink_pad;
+
+	for (sink_pad = 1; sink_pad < CSI_SWITCH_PORTS; sink_pad++) {
+		struct media_entity *src = pdata->src_pads[sink_pad].src;
+		int src_pad = pdata->src_pads[sink_pad].src_pad;
+		int err;
+
+		err = media_create_pad_link(src, src_pad, sink, sink_pad, 0);
+		if (err < 0)
+			return err;
+
+		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
+			src->name, src_pad, sink->name, sink_pad);
+	}
+
+	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);
+}
+
+static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
+
+	if (IS_ERR(subdev))
+		return PTR_ERR(subdev);
+
+	return v4l2_subdev_call(subdev, video, s_stream, enable);
+}
+
+static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg)
+{
+	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
+	dev_dbg(sd->dev, "vbs_g_endpoint_config... active port is %d\n", pdata->state);
+	*cfg = pdata->vep[pdata->state - 1];
+
+	return 0;
+}
+
+
+static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
+	.registered = &vbs_registered,
+};
+
+static const struct media_entity_operations vbs_media_ops = {
+	.link_setup = vbs_link_setup,
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops vbs_video_ops = {
+	.s_stream = vbs_s_stream,
+	.g_endpoint_config = vbs_g_endpoint_config,
+};
+
+static const struct v4l2_subdev_ops vbs_ops = {
+	.video = &vbs_video_ops,
+};
+
+static int video_bus_switch_probe(struct platform_device *pdev)
+{
+	struct vbs_data *pdata;
+	int err = 0;
+
+	/* platform data */
+	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		dev_dbg(&pdev->dev, "video-bus-switch: not enough memory\n");
+		return -ENOMEM;
+	}
+	platform_set_drvdata(pdev, pdata);
+
+	/* switch gpio */
+	pdata->swgpio = devm_gpiod_get(&pdev->dev, "switch", GPIOD_OUT_HIGH);
+	if (IS_ERR(pdata->swgpio)) {
+		err = PTR_ERR(pdata->swgpio);
+		dev_err(&pdev->dev, "Failed to request gpio: %d\n", err);
+		return err;
+	}
+
+	/* find sub-devices */
+	err = vbs_of_parse_nodes(&pdev->dev, pdata);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to parse nodes: %d\n", err);
+		return err;
+	}
+
+	pdata->state = CSI_SWITCH_DISABLED;
+	pdata->notifier.bound = vbs_subdev_notifier_bound;
+	pdata->notifier.complete = vbs_subdev_notifier_complete;
+
+	/* setup subdev */
+	pdata->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+	pdata->pads[1].flags = MEDIA_PAD_FL_SINK;
+	pdata->pads[2].flags = MEDIA_PAD_FL_SINK;
+
+	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
+	pdata->subdev.dev = &pdev->dev;
+	pdata->subdev.owner = pdev->dev.driver->owner;
+	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
+	v4l2_set_subdevdata(&pdata->subdev, pdata);
+	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
+	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;
+	pdata->subdev.entity.ops = &vbs_media_ops;
+	pdata->subdev.internal_ops = &vbs_internal_ops;
+	err = media_entity_pads_init(&pdata->subdev.entity, CSI_SWITCH_PORTS,
+				pdata->pads);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to init media entity: %d\n", err);
+		return err;
+	}
+
+	/* register subdev */
+	err = v4l2_async_register_subdev(&pdata->subdev);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
+		media_entity_cleanup(&pdata->subdev.entity);
+		return err;
+	}
+
+	dev_info(&pdev->dev, "video-bus-switch registered\n");
+
+	return 0;
+}
+
+static int video_bus_switch_remove(struct platform_device *pdev)
+{
+	struct vbs_data *pdata = platform_get_drvdata(pdev);
+
+	v4l2_async_notifier_unregister(&pdata->notifier);
+	v4l2_async_unregister_subdev(&pdata->subdev);
+	media_entity_cleanup(&pdata->subdev.entity);
+
+	return 0;
+}
+
+static const struct of_device_id video_bus_switch_of_match[] = {
+	{ .compatible = "video-bus-switch" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, video_bus_switch_of_match);
+
+static struct platform_driver video_bus_switch_driver = {
+	.driver = {
+		.name	= "video-bus-switch",
+		.of_match_table = video_bus_switch_of_match,
+	},
+	.probe		= video_bus_switch_probe,
+	.remove		= video_bus_switch_remove,
+};
+
+module_platform_driver(video_bus_switch_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("Video Bus Switch");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:video-bus-switch");
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..448dbb5 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -25,6 +25,7 @@
 #include <media/v4l2-dev.h>
 #include <media/v4l2-fh.h>
 #include <media/v4l2-mediabus.h>
+#include <media/v4l2-of.h>
 
 /* generic v4l2_device notify callback notification values */
 #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
@@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**
diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
index 4890787..94648ab 100644
--- a/include/uapi/linux/media.h
+++ b/include/uapi/linux/media.h
@@ -147,6 +147,7 @@ struct media_device_info {
  * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER.
  */
 #define MEDIA_ENT_F_TUNER		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 5)
+#define MEDIA_ENT_F_SWITCH		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 6)
 
 #define MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN	MEDIA_ENT_F_OLD_SUBDEV_BASE
 


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 13:32             ` Pali Rohár
@ 2017-02-03 21:07               ` Pavel Machek
  2017-02-04  1:04                 ` Sebastian Reichel
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-03 21:07 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Sakari Ailus, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 782 bytes --]

On Fri 2017-02-03 14:32:19, Pali Rohár wrote:
> On Friday 03 February 2017 13:35:08 Pavel Machek wrote:
> > N900 contains front and back camera, with a switch between the
> > two. This adds support for the switch component, and it is now
> > possible to select between front and back cameras during runtime.
> 
> IIRC for controlling cameras on N900 there are two GPIOs. Should not you
> have both in switch driver?

I guess you recall wrongly :-). Switch seems to work. The issue was
with switch GPIO also serving as reset GPIO for one sensor, or
something like that, if _I_ recall correctly ;-).

Best regards,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 21:06               ` Pavel Machek
@ 2017-02-03 21:34                 ` Sakari Ailus
  2017-02-04 21:56                   ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2017-02-03 21:34 UTC (permalink / raw)
  To: Pavel Machek
  Cc: robh+dt, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

Hi Pavel,

On Fri, Feb 03, 2017 at 10:06:10PM +0100, Pavel Machek wrote:
> Hi!
> 
> > My apologies for the delays in reviewing. Feel free to ping me in the future
> > if this happens. :-)
> 
> No problem :-). If you could review the C-code, too... that would be
> nice. It should be in your inbox somewhere (and I attached it below,
> without the dts part).
> 
> 
> > > +Required properties
> > > +===================
> > > +
> > > +compatible	: must contain "video-bus-switch"
> > 
> > How generic is this? Should we have e.g. nokia,video-bus-switch? And if so,
> > change the file name accordingly.
> 
> Generic for "single GPIO controls the switch", AFAICT. But that should
> be common enough...

Um, yes. Then... how about: video-bus-switch-gpio? No Nokia prefix.

> 
> > > +reg		: The interface:
> > > +		  0 - port for image signal processor
> > > +		  1 - port for first camera sensor
> > > +		  2 - port for second camera sensor
> > 
> > I'd say this must be pretty much specific to the one in N900. You could have
> > more ports. Or you could say that ports beyond 0 are camera sensors. I guess
> > this is good enough for now though, it can be changed later on with the
> > source if a need arises.
> 
> Well, I'd say that selecting between two sensors is going to be the
> common case. If someone needs more than two, it will no longer be
> simple GPIO, so we'll have some fixing to do.

It could be two GPIOs --- that's how the GPIO I2C mux works.

But I'd be surprised if someone ever uses something like that again. ;-)

> 
> > Btw. was it still considered a problem that the endpoint properties for the
> > sensors can be different? With the g_routing() pad op which is to be added,
> > the ISP driver (should actually go to a framework somewhere) could parse the
> > graph and find the proper endpoint there.
> 
> I don't know about g_routing. I added g_endpoint_config method that
> passes the configuration, and that seems to work for me.
> 
> I don't see g_routing in next-20170201 . Is there place to look?

I think there was a patch by Laurent to LMML quite some time ago. I suppose
that set will be repicked soonish.

I don't really object using g_endpoint_config() as a temporary solution; I'd
like to have Laurent's opinion on that though. Another option is to wait,
but we've already waited a looong time (as in total).

I'll reply to the other patch containing the code.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] media: Add video bus switch
  2017-01-12 11:17           ` Pavel Machek
@ 2017-02-03 22:25             ` Sakari Ailus
  2017-02-05 22:16               ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2017-02-03 22:25 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

Hi Pavel,

On Thu, Jan 12, 2017 at 12:17:31PM +0100, Pavel Machek wrote:
> On Sat 2016-12-24 16:20:31, Pavel Machek wrote:
> > 
> > N900 contains front and back camera, with a switch between the
> > two. This adds support for the switch component, and it is now
> > possible to select between front and back cameras during runtime.
> > 
> > Signed-off-by: Sebastian Reichel <sre@kernel.org>
> > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> Can I get some feedback here? This works for me -- I can use both
> cameras during one boot -- can I get it applied?
> 
> Thanks,
> 								Pavel
> 
> > diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> > new file mode 100644
> > index 0000000..1b9f8e0
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> > @@ -0,0 +1,63 @@
> > +Video Bus Switch Binding
> > +========================
> > +
> > +This is a binding for a gpio controlled switch for camera interfaces. Such a
> > +device is used on some embedded devices to connect two cameras to the same
> > +interface of a image signal processor.
> > +
> > +Required properties
> > +===================
> > +
> > +compatible	: must contain "video-bus-switch"
> > +switch-gpios	: GPIO specifier for the gpio, which can toggle the
> > +		  selected camera. The GPIO should be configured, so
> > +		  that a disabled GPIO means, that the first port is
> > +		  selected.
> > +
> > +Required Port nodes
> > +===================
> > +
> > +More documentation on these bindings is available in
> > +video-interfaces.txt in the same directory.
> > +
> > +reg		: The interface:
> > +		  0 - port for image signal processor
> > +		  1 - port for first camera sensor
> > +		  2 - port for second camera sensor
> > +
> > +Example
> > +=======
> > +
> > +video-bus-switch {
> > +	compatible = "video-bus-switch"
> > +	switch-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
> > +
> > +	ports {
> > +		#address-cells = <1>;
> > +		#size-cells = <0>;
> > +
> > +		port@0 {
> > +			reg = <0>;
> > +
> > +			csi_switch_in: endpoint {
> > +				remote-endpoint = <&csi_isp>;
> > +			};
> > +		};
> > +
> > +		port@1 {
> > +			reg = <1>;
> > +
> > +			csi_switch_out1: endpoint {
> > +				remote-endpoint = <&csi_cam1>;
> > +			};
> > +		};
> > +
> > +		port@2 {
> > +			reg = <2>;
> > +
> > +			csi_switch_out2: endpoint {
> > +				remote-endpoint = <&csi_cam2>;
> > +			};
> > +		};
> > +	};
> > +};
> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> > index ce4a96f..a4b509e 100644
> > --- a/drivers/media/platform/Kconfig
> > +++ b/drivers/media/platform/Kconfig
> > @@ -91,6 +91,16 @@ config VIDEO_OMAP3_DEBUG
> >  	---help---
> >  	  Enable debug messages on OMAP 3 camera controller driver.
> >  
> > +config VIDEO_BUS_SWITCH
> > +	tristate "Video Bus switch"
> > +	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> > +	depends on MEDIA_CONTROLLER
> > +	depends on OF
> > +	---help---
> > +	  Driver for a GPIO controlled video bus switch, which is used to
> > +	  connect two camera sensors to the same port a the image signal
> > +	  processor.
> > +
> >  config VIDEO_PXA27x
> >  	tristate "PXA27x Quick Capture Interface driver"
> >  	depends on VIDEO_DEV && HAS_DMA
> > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> > index 40b18d1..8eafc27 100644
> > --- a/drivers/media/platform/Makefile
> > +++ b/drivers/media/platform/Makefile
> > @@ -11,6 +11,8 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/
> >  obj-$(CONFIG_VIDEO_OMAP3)	+= omap3isp/
> >  obj-$(CONFIG_VIDEO_PXA27x)	+= pxa_camera.o
> >  
> > +obj-$(CONFIG_VIDEO_BUS_SWITCH) += video-bus-switch.o
> > +
> >  obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o
> >  
> >  obj-$(CONFIG_VIDEO_VIVID)		+= vivid/
> > diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c
> > new file mode 100644
> > index 0000000..6400cfc
> > --- /dev/null
> > +++ b/drivers/media/platform/video-bus-switch.c
> > @@ -0,0 +1,387 @@
> > +/*
> > + * Generic driver for video bus switches
> > + *
> > + * Copyright (C) 2015 Sebastian Reichel <sre@kernel.org>
> > + * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz>
> > + *
> > + * 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.
> > + */
> > +
> > +#define DEBUG

Please remove.

> > +
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/gpio/consumer.h>

Alphabetical order, please.

> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-of.h>
> > +
> > +/*
> > + * TODO:
> > + * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
> > + */
> > +
> > +#define CSI_SWITCH_SUBDEVS 2
> > +#define CSI_SWITCH_PORTS 3

This could go to the enum below.

I guess the CSI_SWITCH_SUBDEVS could be (CSI_SWITCH_PORTS - 1).

I'd just replace CSI_SWITCH by VBS. The bus could be called differently.

> > +
> > +enum vbs_state {
> > +	CSI_SWITCH_DISABLED = 0,
> > +	CSI_SWITCH_PORT_1 = 1,
> > +	CSI_SWITCH_PORT_2 = 2,
> > +};
> > +
> > +struct vbs_src_pads {
> > +	struct media_entity *src;
> > +	int src_pad;
> > +};
> > +
> > +struct vbs_data {
> > +	struct gpio_desc *swgpio;
> > +	struct v4l2_subdev subdev;
> > +	struct v4l2_async_notifier notifier;
> > +	struct media_pad pads[CSI_SWITCH_PORTS];
> > +	struct vbs_src_pads src_pads[CSI_SWITCH_PORTS];
> > +	struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS];
> > +	enum vbs_state state;
> > +};
> > +
> > +struct vbs_async_subdev {
> > +	struct v4l2_subdev *sd;
> > +	struct v4l2_async_subdev asd;
> > +	u8 port;
> > +};
> > +
> > +static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata)
> > +{
> > +	struct v4l2_async_notifier *notifier = &pdata->notifier;
> > +	struct device_node *node = NULL;
> > +
> > +	notifier->subdevs = devm_kcalloc(dev, CSI_SWITCH_SUBDEVS,
> > +		sizeof(*notifier->subdevs), GFP_KERNEL);
> > +	if (!notifier->subdevs)
> > +		return -ENOMEM;
> > +
> > +	notifier->num_subdevs = 0;
> > +	while (notifier->num_subdevs < CSI_SWITCH_SUBDEVS &&
> > +	       (node = of_graph_get_next_endpoint(dev->of_node, node))) {
> > +		struct v4l2_of_endpoint vep;
> > +		struct vbs_async_subdev *ssd;
> > +
> > +		/* skip first port (connected to isp) */
> > +		v4l2_of_parse_endpoint(node, &vep);
> > +		if (vep.base.port == 0) {
> > +			struct device_node *ispnode;
> > +
> > +			ispnode = of_graph_get_remote_port_parent(node);
> > +			if (!ispnode) {
> > +				dev_warn(dev, "bad remote port parent\n");
> > +				return -EINVAL;
> > +			}
> > +
> > +			of_node_put(node);
> > +			continue;
> > +		}
> > +
> > +		ssd = devm_kzalloc(dev, sizeof(*ssd), GFP_KERNEL);
> > +		if (!ssd) {
> > +			of_node_put(node);
> > +			return -ENOMEM;
> > +		}
> > +
> > +		ssd->port = vep.base.port;
> > +
> > +		notifier->subdevs[notifier->num_subdevs] = &ssd->asd;
> > +
> > +		ssd->asd.match.of.node = of_graph_get_remote_port_parent(node);
> > +		of_node_put(node);
> > +		if (!ssd->asd.match.of.node) {
> > +			dev_warn(dev, "bad remote port parent\n");
> > +			return -EINVAL;
> > +		}
> > +
> > +		ssd->asd.match_type = V4L2_ASYNC_MATCH_OF;
> > +		pdata->vep[notifier->num_subdevs] = vep;
> > +		notifier->num_subdevs++;
> > +	}
> > +
> > +	return notifier->num_subdevs;
> > +}
> > +
> > +static int vbs_registered(struct v4l2_subdev *sd)
> > +{
> > +	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
> > +	struct vbs_data *pdata;
> > +	int err;
> > +
> > +	dev_dbg(sd->dev, "registered, init notifier...\n");

Looks like a development time debug message. :-)

> > +
> > +	pdata = v4l2_get_subdevdata(sd);
> > +
> > +	err = v4l2_async_notifier_register(v4l2_dev, &pdata->notifier);
> > +	if (err)
> > +		return err;
> > +
> > +	return 0;
> > +}
> > +
> > +static struct v4l2_subdev *vbs_get_remote_subdev(struct v4l2_subdev *sd)
> > +{
> > +	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
> > +	struct media_entity *src;
> > +
> > +	if (pdata->state == CSI_SWITCH_DISABLED)
> > +		return ERR_PTR(-ENXIO);
> > +
> > +	src = pdata->src_pads[pdata->state].src;
> > +
> > +	return media_entity_to_v4l2_subdev(src);
> > +}
> > +
> > +static int vbs_link_setup(struct media_entity *entity,
> > +			  const struct media_pad *local,
> > +			  const struct media_pad *remote, u32 flags)
> > +{
> > +	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
> > +	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
> > +	bool enable = flags & MEDIA_LNK_FL_ENABLED;
> > +
> > +	if (local->index > CSI_SWITCH_PORTS - 1)
> > +		return -ENXIO;
> > +
> > +	/* no configuration needed on source port */
> > +	if (local->index == 0)
> > +		return 0;
> > +
> > +	if (!enable) {
> > +		if (local->index == pdata->state) {
> > +			pdata->state = CSI_SWITCH_DISABLED;
> > +
> > +			/* Make sure we have both cameras enabled */
> > +			gpiod_set_value(pdata->swgpio, 1);
> > +			return 0;
> > +		} else {
> > +			return -EINVAL;
> > +		}
> > +	}
> > +
> > +	/* there can only be one active sink at the same time */
> > +	if (pdata->state != CSI_SWITCH_DISABLED)
> > +		return -EBUSY;
> > +
> > +	dev_dbg(sd->dev, "Link setup: going to config %d\n", local->index);
> > +
> > +	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
> > +	pdata->state = local->index;
> > +
> > +	sd = vbs_get_remote_subdev(sd);
> > +	if (IS_ERR(sd))
> > +		return PTR_ERR(sd);
> > +
> > +	pdata->subdev.ctrl_handler = sd->ctrl_handler;

This is ugly. You're exposing all the controls through another sub-device.

How does link validation work now?

I wonder if it'd be less so if you just pass through the LINK_FREQ and
PIXEL_RATE controls. It'll certainly be more code though.

I think the link frequency could be something that goes to the frame
descriptor as well. Then we wouldn't need to worry about the controls
separately, just passing the frame descriptor would be enough.

I apologise that I don't have patches quite ready for posting to the list.

> > +
> > +	return 0;
> > +}
> > +
> > +static int vbs_subdev_notifier_bound(struct v4l2_async_notifier *async,
> > +				     struct v4l2_subdev *subdev,
> > +				     struct v4l2_async_subdev *asd)
> > +{
> > +	struct vbs_data *pdata = container_of(async,
> > +		struct vbs_data, notifier);
> > +	struct vbs_async_subdev *ssd =
> > +		container_of(asd, struct vbs_async_subdev, asd);
> > +	struct media_entity *sink = &pdata->subdev.entity;
> > +	struct media_entity *src = &subdev->entity;
> > +	int sink_pad = ssd->port;
> > +	int src_pad;
> > +
> > +	if (sink_pad >= sink->num_pads) {
> > +		dev_err(pdata->subdev.dev, "no sink pad in internal entity!\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	for (src_pad = 0; src_pad < subdev->entity.num_pads; src_pad++) {
> > +		if (subdev->entity.pads[src_pad].flags & MEDIA_PAD_FL_SOURCE)
> > +			break;
> > +	}
> > +
> > +	if (src_pad >= src->num_pads) {
> > +		dev_err(pdata->subdev.dev, "no source pad in external entity\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	pdata->src_pads[sink_pad].src = src;
> > +	pdata->src_pads[sink_pad].src_pad = src_pad;
> > +	ssd->sd = subdev;
> > +
> > +	return 0;
> > +}
> > +
> > +static int vbs_subdev_notifier_complete(struct v4l2_async_notifier *async)
> > +{
> > +	struct vbs_data *pdata = container_of(async, struct vbs_data, notifier);
> > +	struct media_entity *sink = &pdata->subdev.entity;
> > +	int sink_pad;
> > +
> > +	for (sink_pad = 1; sink_pad < CSI_SWITCH_PORTS; sink_pad++) {
> > +		struct media_entity *src = pdata->src_pads[sink_pad].src;
> > +		int src_pad = pdata->src_pads[sink_pad].src_pad;
> > +		int err;
> > +
> > +		err = media_create_pad_link(src, src_pad, sink, sink_pad, 0);
> > +		if (err < 0)
> > +			return err;
> > +
> > +		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
> > +			src->name, src_pad, sink->name, sink_pad);
> > +	}
> > +
> > +	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);

The ISP driver's complete() callback calls
v4l2_device_register_subdev_nodes() already. Currently it cannot handle
being called more than once --- that needs to be fixed.

> > +}
> > +
> > +static int vbs_s_stream(struct v4l2_subdev *sd, int enable)
> > +{
> > +	struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd);
> > +
> > +	if (IS_ERR(subdev))
> > +		return PTR_ERR(subdev);
> > +
> > +	return v4l2_subdev_call(subdev, video, s_stream, enable);
> > +}
> > +
> > +static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg)
> > +{
> > +	struct vbs_data *pdata = v4l2_get_subdevdata(sd);
> > +	dev_dbg(sd->dev, "vbs_g_endpoint_config... active port is %d\n", pdata->state);
> > +	*cfg = pdata->vep[pdata->state - 1];
> > +
> > +	return 0;
> > +}
> > +
> > +

I'd say that's an extra newline.

> > +static const struct v4l2_subdev_internal_ops vbs_internal_ops = {
> > +	.registered = &vbs_registered,
> > +};
> > +
> > +static const struct media_entity_operations vbs_media_ops = {
> > +	.link_setup = vbs_link_setup,
> > +	.link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +/* subdev video operations */
> > +static const struct v4l2_subdev_video_ops vbs_video_ops = {
> > +	.s_stream = vbs_s_stream,
> > +	.g_endpoint_config = vbs_g_endpoint_config,
> > +};
> > +
> > +static const struct v4l2_subdev_ops vbs_ops = {
> > +	.video = &vbs_video_ops,
> > +};
> > +
> > +static int video_bus_switch_probe(struct platform_device *pdev)
> > +{
> > +	struct vbs_data *pdata;
> > +	int err = 0;
> > +
> > +	/* platform data */
> > +	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
> > +	if (!pdata) {
> > +		dev_dbg(&pdev->dev, "video-bus-switch: not enough memory\n");
> > +		return -ENOMEM;
> > +	}
> > +	platform_set_drvdata(pdev, pdata);
> > +
> > +	/* switch gpio */
> > +	pdata->swgpio = devm_gpiod_get(&pdev->dev, "switch", GPIOD_OUT_HIGH);
> > +	if (IS_ERR(pdata->swgpio)) {
> > +		err = PTR_ERR(pdata->swgpio);
> > +		dev_err(&pdev->dev, "Failed to request gpio: %d\n", err);
> > +		return err;
> > +	}
> > +
> > +	/* find sub-devices */
> > +	err = vbs_of_parse_nodes(&pdev->dev, pdata);
> > +	if (err < 0) {
> > +		dev_err(&pdev->dev, "Failed to parse nodes: %d\n", err);
> > +		return err;
> > +	}
> > +
> > +	pdata->state = CSI_SWITCH_DISABLED;
> > +	pdata->notifier.bound = vbs_subdev_notifier_bound;
> > +	pdata->notifier.complete = vbs_subdev_notifier_complete;
> > +
> > +	/* setup subdev */
> > +	pdata->pads[0].flags = MEDIA_PAD_FL_SOURCE;
> > +	pdata->pads[1].flags = MEDIA_PAD_FL_SINK;
> > +	pdata->pads[2].flags = MEDIA_PAD_FL_SINK;
> > +
> > +	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
> > +	pdata->subdev.dev = &pdev->dev;
> > +	pdata->subdev.owner = pdev->dev.driver->owner;
> > +	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);

How about sizeof(pdata->subdev.name) ?

I'm not sure I like V4L2_SUBDEV_NAME_SIZE in general. It could be even
removed. But not by this patch. :-)

> > +	v4l2_set_subdevdata(&pdata->subdev, pdata);
> > +	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
> > +	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;

MEDIA_ENT_FL_*

> > +	pdata->subdev.entity.ops = &vbs_media_ops;
> > +	pdata->subdev.internal_ops = &vbs_internal_ops;
> > +	err = media_entity_pads_init(&pdata->subdev.entity, CSI_SWITCH_PORTS,
> > +				pdata->pads);
> > +	if (err < 0) {
> > +		dev_err(&pdev->dev, "Failed to init media entity: %d\n", err);
> > +		return err;
> > +	}
> > +
> > +	/* register subdev */
> > +	err = v4l2_async_register_subdev(&pdata->subdev);
> > +	if (err < 0) {
> > +		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
> > +		media_entity_cleanup(&pdata->subdev.entity);
> > +		return err;
> > +	}
> > +
> > +	dev_info(&pdev->dev, "video-bus-switch registered\n");

How about dev_dbg()?

> > +
> > +	return 0;
> > +}
> > +
> > +static int video_bus_switch_remove(struct platform_device *pdev)
> > +{
> > +	struct vbs_data *pdata = platform_get_drvdata(pdev);
> > +
> > +	v4l2_async_notifier_unregister(&pdata->notifier);

Shouldn't you unregister the notifier in the .unregister() callback?

> > +	v4l2_async_unregister_subdev(&pdata->subdev);
> > +	media_entity_cleanup(&pdata->subdev.entity);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id video_bus_switch_of_match[] = {
> > +	{ .compatible = "video-bus-switch" },
> > +	{ },
> > +};
> > +MODULE_DEVICE_TABLE(of, video_bus_switch_of_match);
> > +
> > +static struct platform_driver video_bus_switch_driver = {
> > +	.driver = {
> > +		.name	= "video-bus-switch",
> > +		.of_match_table = video_bus_switch_of_match,
> > +	},
> > +	.probe		= video_bus_switch_probe,
> > +	.remove		= video_bus_switch_remove,
> > +};
> > +
> > +module_platform_driver(video_bus_switch_driver);
> > +
> > +MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
> > +MODULE_DESCRIPTION("Video Bus Switch");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_ALIAS("platform:video-bus-switch");
> > diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
> > index cf778c5..448dbb5 100644
> > --- a/include/media/v4l2-subdev.h
> > +++ b/include/media/v4l2-subdev.h
> > @@ -25,6 +25,7 @@
> >  #include <media/v4l2-dev.h>
> >  #include <media/v4l2-fh.h>
> >  #include <media/v4l2-mediabus.h>
> > +#include <media/v4l2-of.h>
> >  
> >  /* generic v4l2_device notify callback notification values */
> >  #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
> > @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> >  			     const struct v4l2_mbus_config *cfg);
> >  	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> >  			   unsigned int *size);
> > +	int (*g_endpoint_config)(struct v4l2_subdev *sd,
> > +			    struct v4l2_of_endpoint *cfg);

This should be in a separate patch --- assuming we'll add this one.

> >  };
> >  
> >  /**
> > diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
> > index 4890787..94648ab 100644
> > --- a/include/uapi/linux/media.h
> > +++ b/include/uapi/linux/media.h
> > @@ -147,6 +147,7 @@ struct media_device_info {
> >   * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER.
> >   */
> >  #define MEDIA_ENT_F_TUNER		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 5)
> > +#define MEDIA_ENT_F_SWITCH		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 6)

I wonder if MEDIA_ENT_F_PROC_ would be a better prefix.
We shouldn't have new entries in MEDIA_ENT_F_OLD_SUBDEV_BASE anymore.

> >  
> >  #define MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN	MEDIA_ENT_F_OLD_SUBDEV_BASE
> >  
> > 
> > 
> 

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 21:07               ` Pavel Machek
@ 2017-02-04  1:04                 ` Sebastian Reichel
  0 siblings, 0 replies; 97+ messages in thread
From: Sebastian Reichel @ 2017-02-04  1:04 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Pali Rohár, Sakari Ailus, robh+dt, devicetree,
	ivo.g.dimitrov.75, linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1074 bytes --]

Hi,

On Fri, Feb 03, 2017 at 10:07:28PM +0100, Pavel Machek wrote:
> On Fri 2017-02-03 14:32:19, Pali Rohár wrote:
> > On Friday 03 February 2017 13:35:08 Pavel Machek wrote:
> > > N900 contains front and back camera, with a switch between the
> > > two. This adds support for the switch component, and it is now
> > > possible to select between front and back cameras during runtime.
> > 
> > IIRC for controlling cameras on N900 there are two GPIOs. Should
> > not you have both in switch driver?
>
> I guess you recall wrongly :-). Switch seems to work. The issue was
> with switch GPIO also serving as reset GPIO for one sensor, or
> something like that, if _I_ recall correctly ;-).

I have a schematic in my master thesis, which shows how the camera
sensors are connected to the SoC. The PDF is available here:

https://www.uni-oldenburg.de/fileadmin/user_upload/informatik/ag/svs/download/thesis/Reichel_Sebastian.pdf

The schematic is on page 37 (or 45 if your PDF reader does not
use different numbers for the preamble stuff).

--Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 21:34                 ` Sakari Ailus
@ 2017-02-04 21:56                   ` Pavel Machek
  2017-02-04 22:33                     ` Sakari Ailus
  2017-12-20 17:54                     ` [PATCH] devicetree: Add video bus switch Laurent Pinchart
  0 siblings, 2 replies; 97+ messages in thread
From: Pavel Machek @ 2017-02-04 21:56 UTC (permalink / raw)
  To: Sakari Ailus, laurent.pinchart
  Cc: robh+dt, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3770 bytes --]

Hi!

> > > > +Required properties
> > > > +===================
> > > > +
> > > > +compatible	: must contain "video-bus-switch"
> > > 
> > > How generic is this? Should we have e.g. nokia,video-bus-switch? And if so,
> > > change the file name accordingly.
> > 
> > Generic for "single GPIO controls the switch", AFAICT. But that should
> > be common enough...
> 
> Um, yes. Then... how about: video-bus-switch-gpio? No Nokia prefix.

Ok, done. I also fixed the english a bit.

> > > > +reg		: The interface:
> > > > +		  0 - port for image signal processor
> > > > +		  1 - port for first camera sensor
> > > > +		  2 - port for second camera sensor
> > > 
> > > I'd say this must be pretty much specific to the one in N900. You could have
> > > more ports. Or you could say that ports beyond 0 are camera sensors. I guess
> > > this is good enough for now though, it can be changed later on with the
> > > source if a need arises.
> > 
> > Well, I'd say that selecting between two sensors is going to be the
> > common case. If someone needs more than two, it will no longer be
> > simple GPIO, so we'll have some fixing to do.
> 
> It could be two GPIOs --- that's how the GPIO I2C mux works.
> 
> But I'd be surprised if someone ever uses something like that
> again. ;-)

I'd say.. lets handle that when we see hardware like that.

> > > Btw. was it still considered a problem that the endpoint properties for the
> > > sensors can be different? With the g_routing() pad op which is to be added,
> > > the ISP driver (should actually go to a framework somewhere) could parse the
> > > graph and find the proper endpoint there.
> > 
> > I don't know about g_routing. I added g_endpoint_config method that
> > passes the configuration, and that seems to work for me.
> > 
> > I don't see g_routing in next-20170201 . Is there place to look?
> 
> I think there was a patch by Laurent to LMML quite some time ago. I suppose
> that set will be repicked soonish.
> 
> I don't really object using g_endpoint_config() as a temporary solution; I'd
> like to have Laurent's opinion on that though. Another option is to wait,
> but we've already waited a looong time (as in total).

Laurent, do you have some input here? We have simple "2 cameras
connected to one signal processor" situation here. We need some way of
passing endpoint configuration from the sensors through the switch. I
did this:

> > @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> >                          const struct v4l2_mbus_config *cfg);
> >     int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> >                        unsigned int *size);
> > +   int (*g_endpoint_config)(struct v4l2_subdev *sd,
> > +                       struct v4l2_of_endpoint *cfg);

Google of g_routing tells me:

9) Highly reconfigurable hardware - Julien Beraud

- 44 sub-devices connected with an interconnect.
- As long as formats match, any sub-device could be connected to any
- other sub-device through a link.
- The result is 44 * 44 links at worst.
- A switch sub-device proposed as the solution to model the
- interconnect. The sub-devices are connected to the switch
- sub-devices through the hardware links that connect to the
- interconnect.
- The switch would be controlled through new IOCTLs S_ROUTING and
- G_ROUTING.
- Patches available:
 http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip

but the patches are from 2005. So I guess I'll need some guidance here...

> I'll reply to the other patch containing the code.

Ok, thanks.
								Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-04 21:56                   ` Pavel Machek
@ 2017-02-04 22:33                     ` Sakari Ailus
  2017-02-05 21:12                       ` Pavel Machek
  2017-12-20 17:54                     ` [PATCH] devicetree: Add video bus switch Laurent Pinchart
  1 sibling, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2017-02-04 22:33 UTC (permalink / raw)
  To: Pavel Machek
  Cc: laurent.pinchart, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	pali.rohar, linux-media, galak, mchehab, linux-kernel

Hi Pavel,

On Sat, Feb 04, 2017 at 10:56:10PM +0100, Pavel Machek wrote:
> Hi!
> 
> > > > > +Required properties
> > > > > +===================
> > > > > +
> > > > > +compatible	: must contain "video-bus-switch"
> > > > 
> > > > How generic is this? Should we have e.g. nokia,video-bus-switch? And if so,
> > > > change the file name accordingly.
> > > 
> > > Generic for "single GPIO controls the switch", AFAICT. But that should
> > > be common enough...
> > 
> > Um, yes. Then... how about: video-bus-switch-gpio? No Nokia prefix.
> 
> Ok, done. I also fixed the english a bit.
> 
> > > > > +reg		: The interface:
> > > > > +		  0 - port for image signal processor
> > > > > +		  1 - port for first camera sensor
> > > > > +		  2 - port for second camera sensor
> > > > 
> > > > I'd say this must be pretty much specific to the one in N900. You could have
> > > > more ports. Or you could say that ports beyond 0 are camera sensors. I guess
> > > > this is good enough for now though, it can be changed later on with the
> > > > source if a need arises.
> > > 
> > > Well, I'd say that selecting between two sensors is going to be the
> > > common case. If someone needs more than two, it will no longer be
> > > simple GPIO, so we'll have some fixing to do.
> > 
> > It could be two GPIOs --- that's how the GPIO I2C mux works.
> > 
> > But I'd be surprised if someone ever uses something like that
> > again. ;-)
> 
> I'd say.. lets handle that when we see hardware like that.

Yes. :-)

> 
> > > > Btw. was it still considered a problem that the endpoint properties for the
> > > > sensors can be different? With the g_routing() pad op which is to be added,
> > > > the ISP driver (should actually go to a framework somewhere) could parse the
> > > > graph and find the proper endpoint there.
> > > 
> > > I don't know about g_routing. I added g_endpoint_config method that
> > > passes the configuration, and that seems to work for me.
> > > 
> > > I don't see g_routing in next-20170201 . Is there place to look?
> > 
> > I think there was a patch by Laurent to LMML quite some time ago. I suppose
> > that set will be repicked soonish.
> > 
> > I don't really object using g_endpoint_config() as a temporary solution; I'd
> > like to have Laurent's opinion on that though. Another option is to wait,
> > but we've already waited a looong time (as in total).
> 
> Laurent, do you have some input here? We have simple "2 cameras
> connected to one signal processor" situation here. We need some way of
> passing endpoint configuration from the sensors through the switch. I
> did this:
> 
> > > @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> > >                          const struct v4l2_mbus_config *cfg);
> > >     int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> > >                        unsigned int *size);
> > > +   int (*g_endpoint_config)(struct v4l2_subdev *sd,
> > > +                       struct v4l2_of_endpoint *cfg);
> 
> Google of g_routing tells me:
> 
> 9) Highly reconfigurable hardware - Julien Beraud
> 
> - 44 sub-devices connected with an interconnect.
> - As long as formats match, any sub-device could be connected to any
> - other sub-device through a link.
> - The result is 44 * 44 links at worst.
> - A switch sub-device proposed as the solution to model the
> - interconnect. The sub-devices are connected to the switch
> - sub-devices through the hardware links that connect to the
> - interconnect.
> - The switch would be controlled through new IOCTLs S_ROUTING and
> - G_ROUTING.
> - Patches available:
>  http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> 
> but the patches are from 2005. So I guess I'll need some guidance here...

Yeah, that's where it began (2015?), but right now I can only suggest to
wait until there's more. My estimate is within next couple of weeks /
months. But it won't be years.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-04 22:33                     ` Sakari Ailus
@ 2017-02-05 21:12                       ` Pavel Machek
  2017-02-05 23:40                         ` Sebastian Reichel
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-05 21:12 UTC (permalink / raw)
  To: Sakari Ailus, mchehab
  Cc: laurent.pinchart, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	pali.rohar, linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1609 bytes --]

Hi!

> > 9) Highly reconfigurable hardware - Julien Beraud
> > 
> > - 44 sub-devices connected with an interconnect.
> > - As long as formats match, any sub-device could be connected to any
> > - other sub-device through a link.
> > - The result is 44 * 44 links at worst.
> > - A switch sub-device proposed as the solution to model the
> > - interconnect. The sub-devices are connected to the switch
> > - sub-devices through the hardware links that connect to the
> > - interconnect.
> > - The switch would be controlled through new IOCTLs S_ROUTING and
> > - G_ROUTING.
> > - Patches available:
> >  http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> > 
> > but the patches are from 2005. So I guess I'll need some guidance here...
> 
> Yeah, that's where it began (2015?), but right now I can only suggest to
> wait until there's more. My estimate is within next couple of weeks /
> months. But it won't be years.

Ok, week or two would be ok, couple of months is not. And all I need
is single hook in common structure.

So if g_endpoint_config hook looks sane to _you_, I suggest we simply
proceed. Now, maybe Mauro Carvalho Chehab <mchehab@s-opensource.com>
or Laurent or Julien will want a different solution, but
then... they'll have to suggest something doable now, not in couple of
months.

Does that sound like a plan?

Mauro added to cc list, so we can get some input.

Best regards,
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] media: Add video bus switch
  2017-02-03 22:25             ` Sakari Ailus
@ 2017-02-05 22:16               ` Pavel Machek
  2017-02-05 22:44                 ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-05 22:16 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 5573 bytes --]

Hi!

I lost my original reply... so this will be slightly brief.

> > > + * 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.
> > > + */
> > > +
> > > +#define DEBUG
> 
> Please remove.

Ok.

> > > +#include <linux/of_graph.h>
> > > +#include <linux/gpio/consumer.h>
> 
> Alphabetical order, please.

Ok. (But let me make unhappy noise, because these rules are
inconsistent across kernel.)

> > > + * TODO:
> > > + * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
> > > + */
> > > +
> > > +#define CSI_SWITCH_SUBDEVS 2
> > > +#define CSI_SWITCH_PORTS 3
> 
> This could go to the enum below.
> 
> I guess the CSI_SWITCH_SUBDEVS could be (CSI_SWITCH_PORTS - 1).
> 
> I'd just replace CSI_SWITCH by VBS. The bus could be called
> differently.

Ok.

> > > +static int vbs_registered(struct v4l2_subdev *sd)
> > > +{
> > > +	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
> > > +	struct vbs_data *pdata;
> > > +	int err;
> > > +
> > > +	dev_dbg(sd->dev, "registered, init notifier...\n");
> 
> Looks like a development time debug message. :-)

ex-development message ;-).

> > > +	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
> > > +	pdata->state = local->index;
> > > +
> > > +	sd = vbs_get_remote_subdev(sd);
> > > +	if (IS_ERR(sd))
> > > +		return PTR_ERR(sd);
> > > +
> > > +	pdata->subdev.ctrl_handler = sd->ctrl_handler;
> 
> This is ugly. You're exposing all the controls through another sub-device.
> 
> How does link validation work now?
> 
> I wonder if it'd be less so if you just pass through the LINK_FREQ and
> PIXEL_RATE controls. It'll certainly be more code though.
> 
> I think the link frequency could be something that goes to the frame
> descriptor as well. Then we wouldn't need to worry about the controls
> separately, just passing the frame descriptor would be enough.
> 
> I apologise that I don't have patches quite ready for posting to the
> list.

(Plus of course question is "what is link validation".)

Ok, let me play with this one. Solution you are suggesting is to make
a custom harndler that only passes certain data through, right?

> > > +		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
> > > +			src->name, src_pad, sink->name, sink_pad);
> > > +	}
> > > +
> > > +	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);
> 
> The ISP driver's complete() callback calls
> v4l2_device_register_subdev_nodes() already. Currently it cannot handle
> being called more than once --- that needs to be fixed.

I may have patches for that. Let me check.

> > > +}
> > > +
> > > +
> 
> I'd say that's an extra newline.

Not any more.

> > > +	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
> > > +	pdata->subdev.dev = &pdev->dev;
> > > +	pdata->subdev.owner = pdev->dev.driver->owner;
> > > +	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
> 
> How about sizeof(pdata->subdev.name) ?

Ok.

> I'm not sure I like V4L2_SUBDEV_NAME_SIZE in general. It could be even
> removed. But not by this patch. :-)
> 
> > > +	v4l2_set_subdevdata(&pdata->subdev, pdata);
> > > +	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
> > > +	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;
> 
> MEDIA_ENT_FL_*

Do we actually have a flag here? We already have .function, so this
looks like a duplicate.


> > > +	if (err < 0) {
> > > +		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
> > > +		media_entity_cleanup(&pdata->subdev.entity);
> > > +		return err;
> > > +	}
> > > +
> > > +	dev_info(&pdev->dev, "video-bus-switch registered\n");
> 
> How about dev_dbg()?

Ok.

> > > +static int video_bus_switch_remove(struct platform_device *pdev)
> > > +{
> > > +	struct vbs_data *pdata = platform_get_drvdata(pdev);
> > > +
> > > +	v4l2_async_notifier_unregister(&pdata->notifier);
> 
> Shouldn't you unregister the notifier in the .unregister() callback?

Ok, I guess we can do that for symetry.

> > >  /* generic v4l2_device notify callback notification values */
> > >  #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
> > > @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> > >  			     const struct v4l2_mbus_config *cfg);
> > >  	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> > >  			   unsigned int *size);
> > > +	int (*g_endpoint_config)(struct v4l2_subdev *sd,
> > > +			    struct v4l2_of_endpoint *cfg);
> 
> This should be in a separate patch --- assuming we'll add this one.

Hmm. I believe the rest of the driver is quite useful in understanding
this. Ok, lets get the discussion started.

> > > --- a/include/uapi/linux/media.h
> > > +++ b/include/uapi/linux/media.h
> > > @@ -147,6 +147,7 @@ struct media_device_info {
> > >   * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER.
> > >   */
> > >  #define MEDIA_ENT_F_TUNER		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 5)
> > > +#define MEDIA_ENT_F_SWITCH		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 6)
> 
> I wonder if MEDIA_ENT_F_PROC_ would be a better prefix.
> We shouldn't have new entries in MEDIA_ENT_F_OLD_SUBDEV_BASE anymore.

Ok.
									Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] media: Add video bus switch
  2017-02-05 22:16               ` Pavel Machek
@ 2017-02-05 22:44                 ` Sakari Ailus
  0 siblings, 0 replies; 97+ messages in thread
From: Sakari Ailus @ 2017-02-05 22:44 UTC (permalink / raw)
  To: Pavel Machek
  Cc: ivo.g.dimitrov.75, sre, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

Hi, Pavel!

On Sun, Feb 05, 2017 at 11:16:22PM +0100, Pavel Machek wrote:
> Hi!
> 
> I lost my original reply... so this will be slightly brief.

:-o

> 
> > > > + * 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.
> > > > + */
> > > > +
> > > > +#define DEBUG
> > 
> > Please remove.
> 
> Ok.
> 
> > > > +#include <linux/of_graph.h>
> > > > +#include <linux/gpio/consumer.h>
> > 
> > Alphabetical order, please.
> 
> Ok. (But let me make unhappy noise, because these rules are
> inconsistent across kernel.)
> 
> > > > + * TODO:
> > > > + * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes()
> > > > + */
> > > > +
> > > > +#define CSI_SWITCH_SUBDEVS 2
> > > > +#define CSI_SWITCH_PORTS 3
> > 
> > This could go to the enum below.
> > 
> > I guess the CSI_SWITCH_SUBDEVS could be (CSI_SWITCH_PORTS - 1).
> > 
> > I'd just replace CSI_SWITCH by VBS. The bus could be called
> > differently.
> 
> Ok.
> 
> > > > +static int vbs_registered(struct v4l2_subdev *sd)
> > > > +{
> > > > +	struct v4l2_device *v4l2_dev = sd->v4l2_dev;
> > > > +	struct vbs_data *pdata;
> > > > +	int err;
> > > > +
> > > > +	dev_dbg(sd->dev, "registered, init notifier...\n");
> > 
> > Looks like a development time debug message. :-)
> 
> ex-development message ;-).
> 
> > > > +	gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2);
> > > > +	pdata->state = local->index;
> > > > +
> > > > +	sd = vbs_get_remote_subdev(sd);
> > > > +	if (IS_ERR(sd))
> > > > +		return PTR_ERR(sd);
> > > > +
> > > > +	pdata->subdev.ctrl_handler = sd->ctrl_handler;
> > 
> > This is ugly. You're exposing all the controls through another sub-device.
> > 
> > How does link validation work now?
> > 
> > I wonder if it'd be less so if you just pass through the LINK_FREQ and
> > PIXEL_RATE controls. It'll certainly be more code though.
> > 
> > I think the link frequency could be something that goes to the frame
> > descriptor as well. Then we wouldn't need to worry about the controls
> > separately, just passing the frame descriptor would be enough.
> > 
> > I apologise that I don't have patches quite ready for posting to the
> > list.
> 
> (Plus of course question is "what is link validation".)

The links along the pipeline are validated for matching width, height, media
bus code and possibly other matters. The aim is to make sure that the
hardware configuration is a valid one before streaming starts.

> 
> Ok, let me play with this one. Solution you are suggesting is to make
> a custom harndler that only passes certain data through, right?

That's an option. But supposing we'll add that to the frame desciptors [1],
there won't be need for a custom handler either.

> 
> > > > +		dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n",
> > > > +			src->name, src_pad, sink->name, sink_pad);
> > > > +	}
> > > > +
> > > > +	return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev);
> > 
> > The ISP driver's complete() callback calls
> > v4l2_device_register_subdev_nodes() already. Currently it cannot handle
> > being called more than once --- that needs to be fixed.
> 
> I may have patches for that. Let me check.
> 
> > > > +}
> > > > +
> > > > +
> > 
> > I'd say that's an extra newline.
> 
> Not any more.
> 
> > > > +	v4l2_subdev_init(&pdata->subdev, &vbs_ops);
> > > > +	pdata->subdev.dev = &pdev->dev;
> > > > +	pdata->subdev.owner = pdev->dev.driver->owner;
> > > > +	strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
> > 
> > How about sizeof(pdata->subdev.name) ?
> 
> Ok.
> 
> > I'm not sure I like V4L2_SUBDEV_NAME_SIZE in general. It could be even
> > removed. But not by this patch. :-)
> > 
> > > > +	v4l2_set_subdevdata(&pdata->subdev, pdata);
> > > > +	pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH;
> > > > +	pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH;
> > 
> > MEDIA_ENT_FL_*
> 
> Do we actually have a flag here? We already have .function, so this
> looks like a duplicate.

You can skip setting this. We only have flags for DEFAULT and CONNECTOR and
neither is relevant here.

> 
> 
> > > > +	if (err < 0) {
> > > > +		dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err);
> > > > +		media_entity_cleanup(&pdata->subdev.entity);
> > > > +		return err;
> > > > +	}
> > > > +
> > > > +	dev_info(&pdev->dev, "video-bus-switch registered\n");
> > 
> > How about dev_dbg()?
> 
> Ok.
> 
> > > > +static int video_bus_switch_remove(struct platform_device *pdev)
> > > > +{
> > > > +	struct vbs_data *pdata = platform_get_drvdata(pdev);
> > > > +
> > > > +	v4l2_async_notifier_unregister(&pdata->notifier);
> > 
> > Shouldn't you unregister the notifier in the .unregister() callback?
> 
> Ok, I guess we can do that for symetry.

The sub-device may be bound and unbound without the driver being probed or
removed. That's why the notifier unregistration should take place in the
.unregister callback.

> 
> > > >  /* generic v4l2_device notify callback notification values */
> > > >  #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
> > > > @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> > > >  			     const struct v4l2_mbus_config *cfg);
> > > >  	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> > > >  			   unsigned int *size);
> > > > +	int (*g_endpoint_config)(struct v4l2_subdev *sd,
> > > > +			    struct v4l2_of_endpoint *cfg);
> > 
> > This should be in a separate patch --- assuming we'll add this one.
> 
> Hmm. I believe the rest of the driver is quite useful in understanding
> this. Ok, lets get the discussion started.

Please add field documentation as well in the comment above.

> 
> > > > --- a/include/uapi/linux/media.h
> > > > +++ b/include/uapi/linux/media.h
> > > > @@ -147,6 +147,7 @@ struct media_device_info {
> > > >   * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER.
> > > >   */
> > > >  #define MEDIA_ENT_F_TUNER		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 5)
> > > > +#define MEDIA_ENT_F_SWITCH		(MEDIA_ENT_F_OLD_SUBDEV_BASE + 6)
> > 
> > I wonder if MEDIA_ENT_F_PROC_ would be a better prefix.
> > We shouldn't have new entries in MEDIA_ENT_F_OLD_SUBDEV_BASE anymore.
> 
> Ok.
> 									Pavel
> 

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-05 21:12                       ` Pavel Machek
@ 2017-02-05 23:40                         ` Sebastian Reichel
  2017-02-06  9:37                           ` [PATCH] media: add operation to get configuration of "the other side" of the link Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sebastian Reichel @ 2017-02-05 23:40 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, mchehab, laurent.pinchart, robh+dt, devicetree,
	ivo.g.dimitrov.75, pali.rohar, linux-media, galak, mchehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1723 bytes --]

Hi,

On Sun, Feb 05, 2017 at 10:12:20PM +0100, Pavel Machek wrote:
> > > 9) Highly reconfigurable hardware - Julien Beraud
> > > 
> > > - 44 sub-devices connected with an interconnect.
> > > - As long as formats match, any sub-device could be connected to any
> > > - other sub-device through a link.
> > > - The result is 44 * 44 links at worst.
> > > - A switch sub-device proposed as the solution to model the
> > > - interconnect. The sub-devices are connected to the switch
> > > - sub-devices through the hardware links that connect to the
> > > - interconnect.
> > > - The switch would be controlled through new IOCTLs S_ROUTING and
> > > - G_ROUTING.
> > > - Patches available:
> > >  http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> > > 
> > > but the patches are from 2005. So I guess I'll need some guidance here...
> > 
> > Yeah, that's where it began (2015?), but right now I can only suggest to
> > wait until there's more. My estimate is within next couple of weeks /
> > months. But it won't be years.
> 
> Ok, week or two would be ok, couple of months is not. And all I need
> is single hook in common structure.
> 
> So if g_endpoint_config hook looks sane to _you_, I suggest we simply
> proceed. Now, maybe Mauro Carvalho Chehab <mchehab@s-opensource.com>
> or Laurent or Julien will want a different solution, but
> then... they'll have to suggest something doable now, not in couple of
> months.
> 
> Does that sound like a plan?
> 
> Mauro added to cc list, so we can get some input.

side note: It's an kernel-internal API only used by the media
subsystem. Code can be easily updated to use an improved API
at any time.

-- Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* [PATCH] media: add operation to get configuration of "the other side" of the link
  2017-02-05 23:40                         ` Sebastian Reichel
@ 2017-02-06  9:37                           ` Pavel Machek
  2017-12-19 15:43                             ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-06  9:37 UTC (permalink / raw)
  To: Sebastian Reichel, mchehab
  Cc: Sakari Ailus, laurent.pinchart, ivo.g.dimitrov.75, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1619 bytes --]


Normally, link configuration can be determined at probe time... but
Nokia N900 has two cameras, and can switch between them at runtime, so
that mechanism is not suitable here.

Add a hook that tells us link configuration.

Signed-off-by: Pavel Machek <pavel@ucw.cz>

diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index cf778c5..74148b9 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -25,6 +25,7 @@
 #include <media/v4l2-dev.h>
 #include <media/v4l2-fh.h>
 #include <media/v4l2-mediabus.h>
+#include <media/v4l2-of.h>
 
 /* generic v4l2_device notify callback notification values */
 #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
@@ -383,6 +384,8 @@ struct v4l2_mbus_frame_desc {
  * @s_rx_buffer: set a host allocated memory buffer for the subdev. The subdev
  *	can adjust @size to a lower value and must not write more data to the
  *	buffer starting at @data than the original value of @size.
+ *
+ * @g_endpoint_config: get link configuration required by this device.
  */
 struct v4l2_subdev_video_ops {
 	int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);
@@ -415,6 +418,8 @@ struct v4l2_subdev_video_ops {
 			     const struct v4l2_mbus_config *cfg);
 	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
 			   unsigned int *size);
+	int (*g_endpoint_config)(struct v4l2_subdev *sd,
+			    struct v4l2_of_endpoint *cfg);
 };
 
 /**




-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-03 12:35           ` [PATCH] devicetree: " Pavel Machek
  2017-02-03 13:07             ` Sakari Ailus
  2017-02-03 13:32             ` Pali Rohár
@ 2017-02-08 21:36             ` Rob Herring
  2017-02-08 22:30               ` Pavel Machek
  2017-02-08 22:34               ` Pavel Machek
  2 siblings, 2 replies; 97+ messages in thread
From: Rob Herring @ 2017-02-08 21:36 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

On Fri, Feb 03, 2017 at 01:35:08PM +0100, Pavel Machek wrote:
> 
> N900 contains front and back camera, with a switch between the
> two. This adds support for the switch component, and it is now
> possible to select between front and back cameras during runtime.
> 
> This adds documentation for the devicetree binding.
> 
> Signed-off-by: Sebastian Reichel <sre@kernel.org>
> Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> 
> diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> new file mode 100644
> index 0000000..1b9f8e0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> @@ -0,0 +1,63 @@
> +Video Bus Switch Binding
> +========================

I'd call it a mux rather than switch.

BTW, there's a new mux-controller binding under review you might look 
at. It would only be needed here if the mux ctrl also controls other 
things.

> +
> +This is a binding for a gpio controlled switch for camera interfaces. Such a
> +device is used on some embedded devices to connect two cameras to the same
> +interface of a image signal processor.
> +
> +Required properties
> +===================
> +
> +compatible	: must contain "video-bus-switch"

video-bus-gpio-mux

> +switch-gpios	: GPIO specifier for the gpio, which can toggle the

mux-gpios to align with existing GPIO controlled muxes.

> +		  selected camera. The GPIO should be configured, so
> +		  that a disabled GPIO means, that the first port is
> +		  selected.
> +
> +Required Port nodes
> +===================
> +
> +More documentation on these bindings is available in
> +video-interfaces.txt in the same directory.
> +
> +reg		: The interface:
> +		  0 - port for image signal processor
> +		  1 - port for first camera sensor
> +		  2 - port for second camera sensor

This could be used for display side as well. So describe these just as 
inputs and outputs.

Rob

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-08 21:36             ` Rob Herring
@ 2017-02-08 22:30               ` Pavel Machek
  2017-02-09 23:02                 ` Rob Herring
  2017-02-08 22:34               ` Pavel Machek
  1 sibling, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-08 22:30 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sakari Ailus, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2072 bytes --]

On Wed 2017-02-08 15:36:09, Rob Herring wrote:
> On Fri, Feb 03, 2017 at 01:35:08PM +0100, Pavel Machek wrote:
> > 
> > N900 contains front and back camera, with a switch between the
> > two. This adds support for the switch component, and it is now
> > possible to select between front and back cameras during runtime.
> > 
> > This adds documentation for the devicetree binding.
> > 
> > Signed-off-by: Sebastian Reichel <sre@kernel.org>
> > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> > 
> > 
> > diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> > new file mode 100644
> > index 0000000..1b9f8e0
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> > @@ -0,0 +1,63 @@
> > +Video Bus Switch Binding
> > +========================
> 
> I'd call it a mux rather than switch.

It is a switch, not a multiplexor (
https://en.wikipedia.org/wiki/Multiplexing ). Only one camera can
operate at a time.

> BTW, there's a new mux-controller binding under review you might look 
> at. It would only be needed here if the mux ctrl also controls other 
> things.

Do you have a pointer?

> > +Required Port nodes
> > +===================
> > +
> > +More documentation on these bindings is available in
> > +video-interfaces.txt in the same directory.
> > +
> > +reg		: The interface:
> > +		  0 - port for image signal processor
> > +		  1 - port for first camera sensor
> > +		  2 - port for second camera sensor
> 
> This could be used for display side as well. So describe these just as 
> inputs and outputs.

I'd prefer not to confuse people. I guess that would be 0 -- output
port, 1, 2 -- input ports... But this is media data, are you sure it
is good idea to change this?

									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-08 21:36             ` Rob Herring
  2017-02-08 22:30               ` Pavel Machek
@ 2017-02-08 22:34               ` Pavel Machek
  2017-02-09 22:58                 ` Rob Herring
  1 sibling, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-08 22:34 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sakari Ailus, devicetree, ivo.g.dimitrov.75, sre, pali.rohar,
	linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 802 bytes --]

> > +
> > +This is a binding for a gpio controlled switch for camera interfaces. Such a
> > +device is used on some embedded devices to connect two cameras to the same
> > +interface of a image signal processor.
> > +
> > +Required properties
> > +===================
> > +
> > +compatible	: must contain "video-bus-switch"
> 
> video-bus-gpio-mux

Sakari already asked for rename here. I believe I waited reasonable
time, but got no input from you, so I did rename it. Now you decide on
different name.

Can we either get timely reactions or less bikeshedding?

Thanks,

                                                                Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-08 22:34               ` Pavel Machek
@ 2017-02-09 22:58                 ` Rob Herring
  2017-02-10 21:17                   ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Rob Herring @ 2017-02-09 22:58 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

On Wed, Feb 8, 2017 at 4:34 PM, Pavel Machek <pavel@ucw.cz> wrote:
>> > +
>> > +This is a binding for a gpio controlled switch for camera interfaces. Such a
>> > +device is used on some embedded devices to connect two cameras to the same
>> > +interface of a image signal processor.
>> > +
>> > +Required properties
>> > +===================
>> > +
>> > +compatible : must contain "video-bus-switch"
>>
>> video-bus-gpio-mux
>
> Sakari already asked for rename here. I believe I waited reasonable
> time, but got no input from you, so I did rename it. Now you decide on
> different name.
>
> Can we either get timely reactions or less bikeshedding?

You mean less than 5 days because I don't see any other version of
this? But in short, no, you can't.

Rob

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-08 22:30               ` Pavel Machek
@ 2017-02-09 23:02                 ` Rob Herring
  2017-02-09 23:03                   ` Rob Herring
  0 siblings, 1 reply; 97+ messages in thread
From: Rob Herring @ 2017-02-09 23:02 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

On Wed, Feb 8, 2017 at 4:30 PM, Pavel Machek <pavel@ucw.cz> wrote:
> On Wed 2017-02-08 15:36:09, Rob Herring wrote:
>> On Fri, Feb 03, 2017 at 01:35:08PM +0100, Pavel Machek wrote:
>> >
>> > N900 contains front and back camera, with a switch between the
>> > two. This adds support for the switch component, and it is now
>> > possible to select between front and back cameras during runtime.
>> >
>> > This adds documentation for the devicetree binding.
>> >
>> > Signed-off-by: Sebastian Reichel <sre@kernel.org>
>> > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
>> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
>> >
>> >
>> > diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
>> > new file mode 100644
>> > index 0000000..1b9f8e0
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
>> > @@ -0,0 +1,63 @@
>> > +Video Bus Switch Binding
>> > +========================
>>
>> I'd call it a mux rather than switch.
>
> It is a switch, not a multiplexor (
> https://en.wikipedia.org/wiki/Multiplexing ). Only one camera can
> operate at a time.

It's no different than an i2c mux. It's one at a time.

>
>> BTW, there's a new mux-controller binding under review you might look
>> at. It would only be needed here if the mux ctrl also controls other
>> things.
>
> Do you have a pointer?

Let me Google that for you:

>
>> > +Required Port nodes
>> > +===================
>> > +
>> > +More documentation on these bindings is available in
>> > +video-interfaces.txt in the same directory.
>> > +
>> > +reg                : The interface:
>> > +             0 - port for image signal processor
>> > +             1 - port for first camera sensor
>> > +             2 - port for second camera sensor
>>
>> This could be used for display side as well. So describe these just as
>> inputs and outputs.
>
> I'd prefer not to confuse people. I guess that would be 0 -- output
> port, 1, 2 -- input ports... But this is media data, are you sure it
> is good idea to change this?

And I'd prefer something that can be reused by others.

>
>                                                                         Pavel
> --
> (english) http://www.livejournal.com/~pavelmachek
> (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-09 23:02                 ` Rob Herring
@ 2017-02-09 23:03                   ` Rob Herring
  2017-02-10 19:54                     ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Rob Herring @ 2017-02-09 23:03 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

On Thu, Feb 9, 2017 at 5:02 PM, Rob Herring <robh@kernel.org> wrote:
> On Wed, Feb 8, 2017 at 4:30 PM, Pavel Machek <pavel@ucw.cz> wrote:
>> On Wed 2017-02-08 15:36:09, Rob Herring wrote:
>>> On Fri, Feb 03, 2017 at 01:35:08PM +0100, Pavel Machek wrote:
>>> >
>>> > N900 contains front and back camera, with a switch between the
>>> > two. This adds support for the switch component, and it is now
>>> > possible to select between front and back cameras during runtime.
>>> >
>>> > This adds documentation for the devicetree binding.
>>> >
>>> > Signed-off-by: Sebastian Reichel <sre@kernel.org>
>>> > Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
>>> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
>>> >
>>> >
>>> > diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
>>> > new file mode 100644
>>> > index 0000000..1b9f8e0
>>> > --- /dev/null
>>> > +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
>>> > @@ -0,0 +1,63 @@
>>> > +Video Bus Switch Binding
>>> > +========================
>>>
>>> I'd call it a mux rather than switch.
>>
>> It is a switch, not a multiplexor (
>> https://en.wikipedia.org/wiki/Multiplexing ). Only one camera can
>> operate at a time.
>
> It's no different than an i2c mux. It's one at a time.
>
>>
>>> BTW, there's a new mux-controller binding under review you might look
>>> at. It would only be needed here if the mux ctrl also controls other
>>> things.
>>
>> Do you have a pointer?
>
> Let me Google that for you:

https://lwn.net/Articles/713971/

>
>>
>>> > +Required Port nodes
>>> > +===================
>>> > +
>>> > +More documentation on these bindings is available in
>>> > +video-interfaces.txt in the same directory.
>>> > +
>>> > +reg                : The interface:
>>> > +             0 - port for image signal processor
>>> > +             1 - port for first camera sensor
>>> > +             2 - port for second camera sensor
>>>
>>> This could be used for display side as well. So describe these just as
>>> inputs and outputs.
>>
>> I'd prefer not to confuse people. I guess that would be 0 -- output
>> port, 1, 2 -- input ports... But this is media data, are you sure it
>> is good idea to change this?
>
> And I'd prefer something that can be reused by others.
>
>>
>>                                                                         Pavel
>> --
>> (english) http://www.livejournal.com/~pavelmachek
>> (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-09 23:03                   ` Rob Herring
@ 2017-02-10 19:54                     ` Pavel Machek
  2017-02-10 22:17                       ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-10 19:54 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sakari Ailus, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1051 bytes --]


> >>> > diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> >>> > new file mode 100644
> >>> > index 0000000..1b9f8e0
> >>> > --- /dev/null
> >>> > +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> >>> > @@ -0,0 +1,63 @@
> >>> > +Video Bus Switch Binding
> >>> > +========================
> >>>
> >>> I'd call it a mux rather than switch.
> >>
> >> It is a switch, not a multiplexor (
> >> https://en.wikipedia.org/wiki/Multiplexing ). Only one camera can
> >> operate at a time.
> >
> > It's no different than an i2c mux. It's one at a time.

Take a look at the wikipedia. If you do "one at a time" at 100Hz, you
can claim it is time-domain multiplex. But we are plain switching the
cameras. It takes second (or so) to setup the pipeline.

This is not multiplex.

									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-09 22:58                 ` Rob Herring
@ 2017-02-10 21:17                   ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2017-02-10 21:17 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sakari Ailus, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1273 bytes --]

On Thu 2017-02-09 16:58:29, Rob Herring wrote:
> On Wed, Feb 8, 2017 at 4:34 PM, Pavel Machek <pavel@ucw.cz> wrote:
> >> > +
> >> > +This is a binding for a gpio controlled switch for camera interfaces. Such a
> >> > +device is used on some embedded devices to connect two cameras to the same
> >> > +interface of a image signal processor.
> >> > +
> >> > +Required properties
> >> > +===================
> >> > +
> >> > +compatible : must contain "video-bus-switch"
> >>
> >> video-bus-gpio-mux
> >
> > Sakari already asked for rename here. I believe I waited reasonable
> > time, but got no input from you, so I did rename it. Now you decide on
> > different name.
> >
> > Can we either get timely reactions or less bikeshedding?
> 
> You mean less than 5 days because I don't see any other version of
> this? But in short, no, you can't.

Could we switch device tree bindings from "cc: subsystem, to: device
tree" to "to: subsystem, cc: device tree" mode? Currently it takes
more effort to merge the device tree parts than the relevant driver,
and that is not quite good.

Best regards,
								Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-10 19:54                     ` Pavel Machek
@ 2017-02-10 22:17                       ` Sakari Ailus
  2017-02-13  9:54                         ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2017-02-10 22:17 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Rob Herring, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

Hi Pavel,

On Fri, Feb 10, 2017 at 08:54:35PM +0100, Pavel Machek wrote:
> 
> > >>> > diff --git a/Documentation/devicetree/bindings/media/video-bus-switch.txt b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> > >>> > new file mode 100644
> > >>> > index 0000000..1b9f8e0
> > >>> > --- /dev/null
> > >>> > +++ b/Documentation/devicetree/bindings/media/video-bus-switch.txt
> > >>> > @@ -0,0 +1,63 @@
> > >>> > +Video Bus Switch Binding
> > >>> > +========================
> > >>>
> > >>> I'd call it a mux rather than switch.
> > >>
> > >> It is a switch, not a multiplexor (
> > >> https://en.wikipedia.org/wiki/Multiplexing ). Only one camera can
> > >> operate at a time.
> > >
> > > It's no different than an i2c mux. It's one at a time.
> 
> Take a look at the wikipedia. If you do "one at a time" at 100Hz, you
> can claim it is time-domain multiplex. But we are plain switching the
> cameras. It takes second (or so) to setup the pipeline.
> 
> This is not multiplex.

The functionality is still the same, isn't it? Does it change what it is if
the frequency might be 100 Hz or 0,01 Hz?

I was a bit annoyed for having to have two drivers for switching the source
(one for GPIO, another for syscon / register), where both of the drivers
would be essentially the same with the minor exception of having a slightly
different means to toggle the mux setting.

The MUX framework adds an API for controlling the MUX. Thus we'll need only
a single driver that uses the MUX framework API for V4L2. As an added bonus,
V4L2 would be in line with the rest of the MUX usage in the kernel.

The set appears to already contain a GPIO MUX. What's needed would be to use
the MUX API instead of direct GPIOs usage.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-10 22:17                       ` Sakari Ailus
@ 2017-02-13  9:54                         ` Pavel Machek
  2017-02-13 10:20                           ` Sakari Ailus
  0 siblings, 1 reply; 97+ messages in thread
From: Pavel Machek @ 2017-02-13  9:54 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Rob Herring, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1682 bytes --]

Hi!

> > Take a look at the wikipedia. If you do "one at a time" at 100Hz, you
> > can claim it is time-domain multiplex. But we are plain switching the
> > cameras. It takes second (or so) to setup the pipeline.
> > 
> > This is not multiplex.
> 
> The functionality is still the same, isn't it? Does it change what it is if
> the frequency might be 100 Hz or 0,01 Hz?

Well. In your living your you can have a switch, which is switch at
much less than 0.01Hz. You can also have a dimmer, which is a PWM,
which is switch at 100Hz or so. So yes, I'd say switch and mux are
different things.

> I was a bit annoyed for having to have two drivers for switching the source
> (one for GPIO, another for syscon / register), where both of the drivers
> would be essentially the same with the minor exception of having a slightly
> different means to toggle the mux setting.

Well, most of the video-bus-switch is the video4linux glue. Actual
switching is very very small part. So.. where is the other driver?
Looks like we have the same problem.

> The MUX framework adds an API for controlling the MUX. Thus we'll need only
> a single driver that uses the MUX framework API for V4L2. As an added bonus,
> V4L2 would be in line with the rest of the MUX usage in the kernel.
> 
> The set appears to already contain a GPIO MUX. What's needed would be to use
> the MUX API instead of direct GPIOs usage.

If there's a driver that already does switching for video4linux
devices? Do you have a pointer?

								Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-13  9:54                         ` Pavel Machek
@ 2017-02-13 10:20                           ` Sakari Ailus
  2017-03-02  8:54                             ` Pavel Machek
  0 siblings, 1 reply; 97+ messages in thread
From: Sakari Ailus @ 2017-02-13 10:20 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Rob Herring, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel, Steve Longerbeam, p.zabel

Hi Pavel,

On Mon, Feb 13, 2017 at 10:54:20AM +0100, Pavel Machek wrote:
> Hi!
> 
> > > Take a look at the wikipedia. If you do "one at a time" at 100Hz, you
> > > can claim it is time-domain multiplex. But we are plain switching the
> > > cameras. It takes second (or so) to setup the pipeline.
> > > 
> > > This is not multiplex.
> > 
> > The functionality is still the same, isn't it? Does it change what it is if
> > the frequency might be 100 Hz or 0,01 Hz?
> 
> Well. In your living your you can have a switch, which is switch at
> much less than 0.01Hz. You can also have a dimmer, which is a PWM,
> which is switch at 100Hz or so. So yes, I'd say switch and mux are
> different things.

Light switches are mostly on/off switches. It'd be interesting to have a
light switch that you could use to light either of the light bulbs in a room
but not to switch both of them on at the same time. Or off... :-)

I wonder if everyone would be happy with a statement saying that it's a
on / on switch which is used to implement a multiplexer?

> 
> > I was a bit annoyed for having to have two drivers for switching the source
> > (one for GPIO, another for syscon / register), where both of the drivers
> > would be essentially the same with the minor exception of having a slightly
> > different means to toggle the mux setting.
> 
> Well, most of the video-bus-switch is the video4linux glue. Actual
> switching is very very small part. So.. where is the other driver?
> Looks like we have the same problem.

It's here, slightly hidden in plain sight in the same patch with the MUX
framework:

<URL:https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg1328763.html>

> 
> > The MUX framework adds an API for controlling the MUX. Thus we'll need only
> > a single driver that uses the MUX framework API for V4L2. As an added bonus,
> > V4L2 would be in line with the rest of the MUX usage in the kernel.
> > 
> > The set appears to already contain a GPIO MUX. What's needed would be to use
> > the MUX API instead of direct GPIOs usage.
> 
> If there's a driver that already does switching for video4linux
> devices? Do you have a pointer?

I don't think there's one. But with MUX API, we'll be fine using a single
driver instead of two (other one for syscon on iMX).

Cc Steve and Philipp.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi	XMPP: sailus@retiisi.org.uk

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-13 10:20                           ` Sakari Ailus
@ 2017-03-02  8:54                             ` Pavel Machek
  0 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2017-03-02  8:54 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Rob Herring, devicetree, Ivaylo Dimitrov, Sebastian Reichel,
	Pali Rohár, linux-media, Kumar Gala, Mauro Carvalho Chehab,
	linux-kernel, Steve Longerbeam, p.zabel

[-- Attachment #1: Type: text/plain, Size: 1542 bytes --]

On Mon 2017-02-13 12:20:35, Sakari Ailus wrote:
> Hi Pavel,
> 
> On Mon, Feb 13, 2017 at 10:54:20AM +0100, Pavel Machek wrote:
> > Hi!
> > 
> > > > Take a look at the wikipedia. If you do "one at a time" at 100Hz, you
> > > > can claim it is time-domain multiplex. But we are plain switching the
> > > > cameras. It takes second (or so) to setup the pipeline.
> > > > 
> > > > This is not multiplex.
> > > 
> > > The functionality is still the same, isn't it? Does it change what it is if
> > > the frequency might be 100 Hz or 0,01 Hz?
> > 
> > Well. In your living your you can have a switch, which is switch at
> > much less than 0.01Hz. You can also have a dimmer, which is a PWM,
> > which is switch at 100Hz or so. So yes, I'd say switch and mux are
> > different things.
> 
> Light switches are mostly on/off switches. It'd be interesting to have a
> light switch that you could use to light either of the light bulbs in a room
> but not to switch both of them on at the same time. Or off... :-)
> 
> I wonder if everyone would be happy with a statement saying that it's a
> on / on switch which is used to implement a multiplexer?

I believe the difference is the timescale. If it switches "slow" it is
a switch. If it switches fast, it is a dimmer, mux, or something....

Anyway, someone else was faster, so they get to name their creation...

									Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] media: add operation to get configuration of "the other side" of the link
  2017-02-06  9:37                           ` [PATCH] media: add operation to get configuration of "the other side" of the link Pavel Machek
@ 2017-12-19 15:43                             ` Sakari Ailus
  0 siblings, 0 replies; 97+ messages in thread
From: Sakari Ailus @ 2017-12-19 15:43 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sebastian Reichel, mchehab, laurent.pinchart, ivo.g.dimitrov.75,
	pali.rohar, linux-media, galak, mchehab, linux-kernel,
	niklas.soderlund

Hi Pavel,

On Mon, Feb 06, 2017 at 10:37:48AM +0100, Pavel Machek wrote:
> 
> Normally, link configuration can be determined at probe time... but
> Nokia N900 has two cameras, and can switch between them at runtime, so
> that mechanism is not suitable here.
> 
> Add a hook that tells us link configuration.
> 
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
> index cf778c5..74148b9 100644
> --- a/include/media/v4l2-subdev.h
> +++ b/include/media/v4l2-subdev.h
> @@ -25,6 +25,7 @@
>  #include <media/v4l2-dev.h>
>  #include <media/v4l2-fh.h>
>  #include <media/v4l2-mediabus.h>
> +#include <media/v4l2-of.h>
>  
>  /* generic v4l2_device notify callback notification values */
>  #define V4L2_SUBDEV_IR_RX_NOTIFY		_IOW('v', 0, u32)
> @@ -383,6 +384,8 @@ struct v4l2_mbus_frame_desc {
>   * @s_rx_buffer: set a host allocated memory buffer for the subdev. The subdev
>   *	can adjust @size to a lower value and must not write more data to the
>   *	buffer starting at @data than the original value of @size.
> + *
> + * @g_endpoint_config: get link configuration required by this device.
>   */
>  struct v4l2_subdev_video_ops {
>  	int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);
> @@ -415,6 +418,8 @@ struct v4l2_subdev_video_ops {
>  			     const struct v4l2_mbus_config *cfg);
>  	int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
>  			   unsigned int *size);
> +	int (*g_endpoint_config)(struct v4l2_subdev *sd,
> +			    struct v4l2_of_endpoint *cfg);
>  };
>  
>  /**
> 
> 
> 
> 

I think Laurent has a board that has a similar issue.

I'd like to address such issues in conjunction with the CSI-2 virtual
channel and data type support, with the patches in the vc branch here:

<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=vc>

V4L2 OF (or fwnode) endpoint alone doesn't contain all the related
information, and it'd be nice if the solution was indeed independent of OF
(or fwnode).

Niklas has been working on more driver support for this so we're getting
closer to having these merged.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-02-04 21:56                   ` Pavel Machek
  2017-02-04 22:33                     ` Sakari Ailus
@ 2017-12-20 17:54                     ` Laurent Pinchart
  2017-12-21  9:05                       ` Sakari Ailus
                                         ` (2 more replies)
  1 sibling, 3 replies; 97+ messages in thread
From: Laurent Pinchart @ 2017-12-20 17:54 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Sakari Ailus, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	pali.rohar, linux-media, galak, mchehab, linux-kernel

Hi Pavel,

On Saturday, 4 February 2017 23:56:10 EET Pavel Machek wrote:
> Hi!
> 
> >>>> +Required properties
> >>>> +===================
> >>>> +
> >>>> +compatible	: must contain "video-bus-switch"
> >>> 
> >>> How generic is this? Should we have e.g. nokia,video-bus-switch? And
> >>> if so, change the file name accordingly.
> >> 
> >> Generic for "single GPIO controls the switch", AFAICT. But that should
> >> be common enough...
> > 
> > Um, yes. Then... how about: video-bus-switch-gpio? No Nokia prefix.
> 
> Ok, done. I also fixed the english a bit.
> 
> >>>> +reg		: The interface:
> >>>> +		  0 - port for image signal processor
> >>>> +		  1 - port for first camera sensor
> >>>> +		  2 - port for second camera sensor
> >>> 
> >>> I'd say this must be pretty much specific to the one in N900. You
> >>> could have more ports. Or you could say that ports beyond 0 are
> >>> camera sensors. I guess this is good enough for now though, it can be
> >>> changed later on with the source if a need arises.
> >> 
> >> Well, I'd say that selecting between two sensors is going to be the
> >> common case. If someone needs more than two, it will no longer be
> >> simple GPIO, so we'll have some fixing to do.
> > 
> > It could be two GPIOs --- that's how the GPIO I2C mux works.
> > 
> > But I'd be surprised if someone ever uses something like that
> > again. ;-)
> 
> I'd say.. lets handle that when we see hardware like that.
> 
> >>> Btw. was it still considered a problem that the endpoint properties
> >>> for the sensors can be different? With the g_routing() pad op which is
> >>> to be added, the ISP driver (should actually go to a framework
> >>> somewhere) could parse the graph and find the proper endpoint there.
> >> 
> >> I don't know about g_routing. I added g_endpoint_config method that
> >> passes the configuration, and that seems to work for me.
> >> 
> >> I don't see g_routing in next-20170201 . Is there place to look?
> > 
> > I think there was a patch by Laurent to LMML quite some time ago. I
> > suppose that set will be repicked soonish.
> > 
> > I don't really object using g_endpoint_config() as a temporary solution;
> > I'd like to have Laurent's opinion on that though. Another option is to
> > wait, but we've already waited a looong time (as in total).
> 
> Laurent, do you have some input here? We have simple "2 cameras
> connected to one signal processor" situation here. We need some way of
> passing endpoint configuration from the sensors through the switch. I
> did this:

Could you give me a bit more information about the platform you're targeting: 
how the switch is connected, what kind of switch it is, and what endpoint 
configuration data you need ?

> >> @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> >>                          const struct v4l2_mbus_config *cfg);
> >>     int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> >>                        unsigned int *size);
> >> +   int (*g_endpoint_config)(struct v4l2_subdev *sd,
> >> +                       struct v4l2_of_endpoint *cfg);
> 
> Google of g_routing tells me:
> 
> 9) Highly reconfigurable hardware - Julien Beraud
> 
> - 44 sub-devices connected with an interconnect.
> - As long as formats match, any sub-device could be connected to any
> - other sub-device through a link.
> - The result is 44 * 44 links at worst.
> - A switch sub-device proposed as the solution to model the
> - interconnect. The sub-devices are connected to the switch
> - sub-devices through the hardware links that connect to the
> - interconnect.
> - The switch would be controlled through new IOCTLs S_ROUTING and
> - G_ROUTING.
> - Patches available:
>  http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> 
> but the patches are from 2005. So I guess I'll need some guidance here...

You made me feel very old for a moment. The patches are from 2015 :-)

> > I'll reply to the other patch containing the code.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-12-20 17:54                     ` [PATCH] devicetree: Add video bus switch Laurent Pinchart
@ 2017-12-21  9:05                       ` Sakari Ailus
  2017-12-21 16:36                       ` Ivaylo Dimitrov
  2017-12-22  9:24                       ` Pavel Machek
  2 siblings, 0 replies; 97+ messages in thread
From: Sakari Ailus @ 2017-12-21  9:05 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Pavel Machek, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	pali.rohar, linux-media, galak, mchehab, linux-kernel

On Wed, Dec 20, 2017 at 07:54:12PM +0200, Laurent Pinchart wrote:
> Hi Pavel,
> 
> On Saturday, 4 February 2017 23:56:10 EET Pavel Machek wrote:
> > Hi!
> > 
> > >>>> +Required properties
> > >>>> +===================
> > >>>> +
> > >>>> +compatible	: must contain "video-bus-switch"
> > >>> 
> > >>> How generic is this? Should we have e.g. nokia,video-bus-switch? And
> > >>> if so, change the file name accordingly.
> > >> 
> > >> Generic for "single GPIO controls the switch", AFAICT. But that should
> > >> be common enough...
> > > 
> > > Um, yes. Then... how about: video-bus-switch-gpio? No Nokia prefix.
> > 
> > Ok, done. I also fixed the english a bit.
> > 
> > >>>> +reg		: The interface:
> > >>>> +		  0 - port for image signal processor
> > >>>> +		  1 - port for first camera sensor
> > >>>> +		  2 - port for second camera sensor
> > >>> 
> > >>> I'd say this must be pretty much specific to the one in N900. You
> > >>> could have more ports. Or you could say that ports beyond 0 are
> > >>> camera sensors. I guess this is good enough for now though, it can be
> > >>> changed later on with the source if a need arises.
> > >> 
> > >> Well, I'd say that selecting between two sensors is going to be the
> > >> common case. If someone needs more than two, it will no longer be
> > >> simple GPIO, so we'll have some fixing to do.
> > > 
> > > It could be two GPIOs --- that's how the GPIO I2C mux works.
> > > 
> > > But I'd be surprised if someone ever uses something like that
> > > again. ;-)
> > 
> > I'd say.. lets handle that when we see hardware like that.
> > 
> > >>> Btw. was it still considered a problem that the endpoint properties
> > >>> for the sensors can be different? With the g_routing() pad op which is
> > >>> to be added, the ISP driver (should actually go to a framework
> > >>> somewhere) could parse the graph and find the proper endpoint there.
> > >> 
> > >> I don't know about g_routing. I added g_endpoint_config method that
> > >> passes the configuration, and that seems to work for me.
> > >> 
> > >> I don't see g_routing in next-20170201 . Is there place to look?
> > > 
> > > I think there was a patch by Laurent to LMML quite some time ago. I
> > > suppose that set will be repicked soonish.
> > > 
> > > I don't really object using g_endpoint_config() as a temporary solution;
> > > I'd like to have Laurent's opinion on that though. Another option is to
> > > wait, but we've already waited a looong time (as in total).
> > 
> > Laurent, do you have some input here? We have simple "2 cameras
> > connected to one signal processor" situation here. We need some way of
> > passing endpoint configuration from the sensors through the switch. I
> > did this:
> 
> Could you give me a bit more information about the platform you're targeting: 
> how the switch is connected, what kind of switch it is, and what endpoint 
> configuration data you need ?
> 
> > >> @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
> > >>                          const struct v4l2_mbus_config *cfg);
> > >>     int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
> > >>                        unsigned int *size);
> > >> +   int (*g_endpoint_config)(struct v4l2_subdev *sd,
> > >> +                       struct v4l2_of_endpoint *cfg);
> > 
> > Google of g_routing tells me:
> > 
> > 9) Highly reconfigurable hardware - Julien Beraud
> > 
> > - 44 sub-devices connected with an interconnect.
> > - As long as formats match, any sub-device could be connected to any
> > - other sub-device through a link.
> > - The result is 44 * 44 links at worst.
> > - A switch sub-device proposed as the solution to model the
> > - interconnect. The sub-devices are connected to the switch
> > - sub-devices through the hardware links that connect to the
> > - interconnect.
> > - The switch would be controlled through new IOCTLs S_ROUTING and
> > - G_ROUTING.
> > - Patches available:
> >  http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> > 
> > but the patches are from 2005. So I guess I'll need some guidance here...
> 
> You made me feel very old for a moment. The patches are from 2015 :-)

There are up-to-date patches here:

<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=vc>

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-12-20 17:54                     ` [PATCH] devicetree: Add video bus switch Laurent Pinchart
  2017-12-21  9:05                       ` Sakari Ailus
@ 2017-12-21 16:36                       ` Ivaylo Dimitrov
  2017-12-22  9:24                       ` Pavel Machek
  2 siblings, 0 replies; 97+ messages in thread
From: Ivaylo Dimitrov @ 2017-12-21 16:36 UTC (permalink / raw)
  To: Laurent Pinchart, Pavel Machek
  Cc: Sakari Ailus, robh+dt, devicetree, sre, pali.rohar, linux-media,
	galak, mchehab, linux-kernel

Hi,

On 20.12.2017 19:54, Laurent Pinchart wrote:
> Hi Pavel,
> 
> On Saturday, 4 February 2017 23:56:10 EET Pavel Machek wrote:
>> Hi!
>>
>>>>>> +Required properties
>>>>>> +===================
>>>>>> +
>>>>>> +compatible	: must contain "video-bus-switch"
>>>>>
>>>>> How generic is this? Should we have e.g. nokia,video-bus-switch? And
>>>>> if so, change the file name accordingly.
>>>>
>>>> Generic for "single GPIO controls the switch", AFAICT. But that should
>>>> be common enough...
>>>
>>> Um, yes. Then... how about: video-bus-switch-gpio? No Nokia prefix.
>>
>> Ok, done. I also fixed the english a bit.
>>
>>>>>> +reg		: The interface:
>>>>>> +		  0 - port for image signal processor
>>>>>> +		  1 - port for first camera sensor
>>>>>> +		  2 - port for second camera sensor
>>>>>
>>>>> I'd say this must be pretty much specific to the one in N900. You
>>>>> could have more ports. Or you could say that ports beyond 0 are
>>>>> camera sensors. I guess this is good enough for now though, it can be
>>>>> changed later on with the source if a need arises.
>>>>
>>>> Well, I'd say that selecting between two sensors is going to be the
>>>> common case. If someone needs more than two, it will no longer be
>>>> simple GPIO, so we'll have some fixing to do.
>>>
>>> It could be two GPIOs --- that's how the GPIO I2C mux works.
>>>
>>> But I'd be surprised if someone ever uses something like that
>>> again. ;-)
>>
>> I'd say.. lets handle that when we see hardware like that.
>>
>>>>> Btw. was it still considered a problem that the endpoint properties
>>>>> for the sensors can be different? With the g_routing() pad op which is
>>>>> to be added, the ISP driver (should actually go to a framework
>>>>> somewhere) could parse the graph and find the proper endpoint there.
>>>>
>>>> I don't know about g_routing. I added g_endpoint_config method that
>>>> passes the configuration, and that seems to work for me.
>>>>
>>>> I don't see g_routing in next-20170201 . Is there place to look?
>>>
>>> I think there was a patch by Laurent to LMML quite some time ago. I
>>> suppose that set will be repicked soonish.
>>>
>>> I don't really object using g_endpoint_config() as a temporary solution;
>>> I'd like to have Laurent's opinion on that though. Another option is to
>>> wait, but we've already waited a looong time (as in total).
>>
>> Laurent, do you have some input here? We have simple "2 cameras
>> connected to one signal processor" situation here. We need some way of
>> passing endpoint configuration from the sensors through the switch. I
>> did this:
> 
> Could you give me a bit more information about the platform you're targeting:
> how the switch is connected, what kind of switch it is, and what endpoint


http://plan9.stanleylieber.com/hardware/n900/n900.schematics.pdf, on 
page 2, see N5801 and N5802.

> configuration data you need ?
> 
>>>> @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops {
>>>>                           const struct v4l2_mbus_config *cfg);
>>>>      int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,
>>>>                         unsigned int *size);
>>>> +   int (*g_endpoint_config)(struct v4l2_subdev *sd,
>>>> +                       struct v4l2_of_endpoint *cfg);
>>
>> Google of g_routing tells me:
>>
>> 9) Highly reconfigurable hardware - Julien Beraud
>>
>> - 44 sub-devices connected with an interconnect.
>> - As long as formats match, any sub-device could be connected to any
>> - other sub-device through a link.
>> - The result is 44 * 44 links at worst.
>> - A switch sub-device proposed as the solution to model the
>> - interconnect. The sub-devices are connected to the switch
>> - sub-devices through the hardware links that connect to the
>> - interconnect.
>> - The switch would be controlled through new IOCTLs S_ROUTING and
>> - G_ROUTING.
>> - Patches available:
>>   http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
>>
>> but the patches are from 2005. So I guess I'll need some guidance here...
> 
> You made me feel very old for a moment. The patches are from 2015 :-)
> 
>>> I'll reply to the other patch containing the code.
> 

Regards,
Ivo

^ permalink raw reply	[flat|nested] 97+ messages in thread

* Re: [PATCH] devicetree: Add video bus switch
  2017-12-20 17:54                     ` [PATCH] devicetree: Add video bus switch Laurent Pinchart
  2017-12-21  9:05                       ` Sakari Ailus
  2017-12-21 16:36                       ` Ivaylo Dimitrov
@ 2017-12-22  9:24                       ` Pavel Machek
  2 siblings, 0 replies; 97+ messages in thread
From: Pavel Machek @ 2017-12-22  9:24 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Sakari Ailus, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
	pali.rohar, linux-media, galak, mchehab, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1945 bytes --]

Hi!

> > > I don't really object using g_endpoint_config() as a temporary solution;
> > > I'd like to have Laurent's opinion on that though. Another option is to
> > > wait, but we've already waited a looong time (as in total).
> > 
> > Laurent, do you have some input here? We have simple "2 cameras
> > connected to one signal processor" situation here. We need some way of
> > passing endpoint configuration from the sensors through the switch. I
> > did this:
> 
> Could you give me a bit more information about the platform you're targeting: 
> how the switch is connected, what kind of switch it is, and what endpoint 
> configuration data you need ?

Platform is Nokia N900, Ivaylo already gave pointer to schematics.

Switch is controlled using GPIO, and basically there's CSI
configuration that would normally be in the device tree, but now we
have two CSI configurations to select from...

> > 9) Highly reconfigurable hardware - Julien Beraud
> > 
> > - 44 sub-devices connected with an interconnect.
> > - As long as formats match, any sub-device could be connected to any
> > - other sub-device through a link.
> > - The result is 44 * 44 links at worst.
> > - A switch sub-device proposed as the solution to model the
> > - interconnect. The sub-devices are connected to the switch
> > - sub-devices through the hardware links that connect to the
> > - interconnect.
> > - The switch would be controlled through new IOCTLs S_ROUTING and
> > - G_ROUTING.
> > - Patches available:
> >  http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> > 
> > but the patches are from 2005. So I guess I'll need some guidance here...
> 
> You made me feel very old for a moment. The patches are from 2015 :-)

Sorry about that :-).
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 97+ messages in thread

end of thread, other threads:[~2017-12-22  9:25 UTC | newest]

Thread overview: 97+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-10-23 20:03 [PATCH v4] media: Driver for Toshiba et8ek8 5MP sensor Pavel Machek
2016-10-23 20:19 ` Sakari Ailus
2016-10-23 20:33   ` Pavel Machek
2016-10-31 22:54     ` Sakari Ailus
2016-11-01  6:36       ` Ivaylo Dimitrov
2016-11-01 20:11         ` Sakari Ailus
2016-11-01 22:14           ` Ivaylo Dimitrov
2016-11-02  8:15         ` Pavel Machek
2016-11-02  8:16           ` Ivaylo Dimitrov
2016-11-01 15:39       ` Pavel Machek
2016-11-01 20:08         ` Sakari Ailus
2016-11-03  8:14           ` Pavel Machek
2016-11-03 22:48       ` Sebastian Reichel
2016-11-03 23:05         ` Sakari Ailus
2016-11-03 23:40           ` Ivaylo Dimitrov
2016-11-04  0:05           ` Sebastian Reichel
2016-11-14 21:58             ` Sakari Ailus
2016-11-15  0:53               ` Sebastian Reichel
2016-11-15 10:54         ` Pavel Machek
2016-11-15 22:55           ` Sakari Ailus
2016-10-23 20:40   ` Pavel Machek
2016-10-31 22:58     ` Sakari Ailus
2016-11-02  0:45       ` Laurent Pinchart
2016-10-23 20:47   ` Pavel Machek
2016-12-13 21:05   ` Pavel Machek
2016-12-18 21:56     ` Sakari Ailus
2016-11-19 23:29 ` Sakari Ailus
2016-11-20 10:02   ` Pavel Machek
2016-11-20 15:20   ` Pavel Machek
2016-11-20 15:21   ` Pavel Machek
2016-11-20 15:31   ` Pavel Machek
2016-12-14 12:24   ` [PATCH v5] " Pavel Machek
2016-12-14 13:03     ` Pali Rohár
2016-12-14 15:52       ` Ivaylo Dimitrov
2016-12-14 20:12       ` Pavel Machek
2016-12-14 22:07         ` Pali Rohár
2016-12-14 22:35           ` Pavel Machek
2016-12-18 22:01         ` Sakari Ailus
2016-12-20 12:37           ` Pavel Machek
2016-12-20 14:01             ` Sakari Ailus
2016-12-20 22:42               ` Pavel Machek
2016-12-21 13:42     ` Sakari Ailus
2016-12-21 22:42       ` Pavel Machek
2016-12-21 23:29         ` Sakari Ailus
2016-12-22  9:34           ` Pavel Machek
2016-12-22 10:01     ` [PATCH v6] " Pavel Machek
2016-12-22 13:39       ` [RFC/PATCH] media: Add video bus switch Pavel Machek
2016-12-22 14:32         ` Sebastian Reichel
2016-12-22 20:53           ` Pavel Machek
2016-12-22 23:11             ` Sebastian Reichel
2016-12-22 22:42           ` Pavel Machek
2016-12-22 23:40             ` Sebastian Reichel
2016-12-23 11:42               ` Pavel Machek
2016-12-23 18:53                 ` Ivaylo Dimitrov
2016-12-23 20:56               ` Pavel Machek
2016-12-24 14:26               ` Pavel Machek
2016-12-24 14:43                 ` Pavel Machek
2016-12-24 15:20         ` [PATCH] " Pavel Machek
2016-12-24 18:35           ` kbuild test robot
2017-01-12 11:17           ` Pavel Machek
2017-02-03 22:25             ` Sakari Ailus
2017-02-05 22:16               ` Pavel Machek
2017-02-05 22:44                 ` Sakari Ailus
2017-02-03 12:35           ` [PATCH] devicetree: " Pavel Machek
2017-02-03 13:07             ` Sakari Ailus
2017-02-03 21:06               ` Pavel Machek
2017-02-03 21:34                 ` Sakari Ailus
2017-02-04 21:56                   ` Pavel Machek
2017-02-04 22:33                     ` Sakari Ailus
2017-02-05 21:12                       ` Pavel Machek
2017-02-05 23:40                         ` Sebastian Reichel
2017-02-06  9:37                           ` [PATCH] media: add operation to get configuration of "the other side" of the link Pavel Machek
2017-12-19 15:43                             ` Sakari Ailus
2017-12-20 17:54                     ` [PATCH] devicetree: Add video bus switch Laurent Pinchart
2017-12-21  9:05                       ` Sakari Ailus
2017-12-21 16:36                       ` Ivaylo Dimitrov
2017-12-22  9:24                       ` Pavel Machek
2017-02-03 13:32             ` Pali Rohár
2017-02-03 21:07               ` Pavel Machek
2017-02-04  1:04                 ` Sebastian Reichel
2017-02-08 21:36             ` Rob Herring
2017-02-08 22:30               ` Pavel Machek
2017-02-09 23:02                 ` Rob Herring
2017-02-09 23:03                   ` Rob Herring
2017-02-10 19:54                     ` Pavel Machek
2017-02-10 22:17                       ` Sakari Ailus
2017-02-13  9:54                         ` Pavel Machek
2017-02-13 10:20                           ` Sakari Ailus
2017-03-02  8:54                             ` Pavel Machek
2017-02-08 22:34               ` Pavel Machek
2017-02-09 22:58                 ` Rob Herring
2017-02-10 21:17                   ` Pavel Machek
2016-12-27  9:26       ` [PATCH v6] media: Driver for Toshiba et8ek8 5MP sensor Sakari Ailus
2016-12-27 20:45         ` Pavel Machek
2016-12-27 20:59           ` [PATCH] mark myself as mainainer for camera on N900 Pavel Machek
2016-12-27 23:57             ` Sebastian Reichel
2017-01-25 13:48               ` Sakari Ailus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).