From 5c4b2dfb06a275237855fe86e6d3d7952a75d013 Mon Sep 17 00:00:00 2001
From: Vladimir Barinov <vladimir.barinov@cogentembedded.com>
Date: Fri, 7 Jun 2019 20:51:35 +0300
Subject: [PATCH 4/5] lvds: add OV2311 imager

This add OV2311 lvds imager

Signed-off-by: Vladimir Barinov <vladimir.barinov@cogentembedded.com>
---
 drivers/media/i2c/soc_camera/ov106xx.c |  13 +-
 drivers/media/i2c/soc_camera/ov2311.c  | 637 +++++++++++++++++++++++++++++++++
 drivers/media/i2c/soc_camera/ov2311.h  | 217 +++++++++++
 3 files changed, 866 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/i2c/soc_camera/ov2311.c
 create mode 100644 drivers/media/i2c/soc_camera/ov2311.h

diff --git a/drivers/media/i2c/soc_camera/ov106xx.c b/drivers/media/i2c/soc_camera/ov106xx.c
index 36aa88a..70067d7 100644
--- a/drivers/media/i2c/soc_camera/ov106xx.c
+++ b/drivers/media/i2c/soc_camera/ov106xx.c
@@ -28,6 +28,7 @@
 #include "ox03a.c"
 #include "isx016.c"
 #include "isx019.c"
+#include "ov2311.c"
 
 static enum {
 	ID_OV10635,
@@ -49,12 +50,13 @@ static enum {
 	ID_OX03A,
 	ID_ISX016,
 	ID_ISX019,
+	ID_OV2311,
 } chip_id;
 
 static int ov106xx_probe(struct i2c_client *client,
 			 const struct i2c_device_id *did)
 {
-	int ret;
+	int ret = -1;
 	chip_id = -EINVAL;
 
 	ret = ar0231_probe(client, did);
@@ -135,6 +137,12 @@ static int ov106xx_probe(struct i2c_client *client,
 		goto out;
 	}
 
+	ret = ov2311_probe(client, did);
+	if (!ret) {
+		chip_id = ID_OV2311;
+		goto out;
+	}
+
 	ret = imx390_probe(client, did);
 	if (!ret) {
 		chip_id = ID_IMX390;
@@ -237,6 +245,9 @@ static int ov106xx_remove(struct i2c_client *client)
 	case ID_ISX019:
 		isx019_remove(client);
 		break;
+	case ID_OV2311:
+		ov2311_remove(client);
+		break;
 	};
 
 	return 0;
diff --git a/drivers/media/i2c/soc_camera/ov2311.c b/drivers/media/i2c/soc_camera/ov2311.c
new file mode 100644
index 0000000..06e57dd
--- /dev/null
+++ b/drivers/media/i2c/soc_camera/ov2311.c
@@ -0,0 +1,637 @@
+/*
+ * OmniVision ov2311 sensor camera driver
+ *
+ * Copyright (C) 2015-2019 Cogent Embedded, Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/videodev2.h>
+
+#include <media/soc_camera.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+
+#include "max9286.h"
+#include "ov2311.h"
+
+#define OV2311_I2C_ADDR			0x60
+
+#define OV2311_PID			0x300a
+#define OV2311_VER			0x300b
+#define OV2311_REV			0x300c
+#define OV2311_VERSION_REG		0x2311
+
+#define OV2311_MEDIA_BUS_FMT		MEDIA_BUS_FMT_Y8_1X8
+
+struct ov2311_priv {
+	struct v4l2_subdev		sd;
+	struct v4l2_ctrl_handler	hdl;
+	struct media_pad		pad;
+	struct v4l2_rect		rect;
+	int				subsampling;
+	int				fps_denominator;
+	int				init_complete;
+	u8				id[6];
+	int				dvp_order;
+	/* serializers */
+	int				max9286_addr;
+	int				max9271_addr;
+	int				ti9x4_addr;
+	int				ti9x3_addr;
+	int				port;
+	int				gpio_resetb;
+	int				gpio_fsin;
+};
+
+static inline struct ov2311_priv *to_ov2311(const struct i2c_client *client)
+{
+	return container_of(i2c_get_clientdata(client), struct ov2311_priv, sd);
+}
+
+static inline struct v4l2_subdev *ov2311_to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct ov2311_priv, hdl)->sd;
+}
+
+static void ov2311_s_port(struct i2c_client *client, int fwd_en)
+{
+	struct ov2311_priv *priv = to_ov2311(client);
+	int tmp_addr;
+
+	if (priv->max9286_addr) {
+		tmp_addr = client->addr;
+		client->addr = priv->max9286_addr;				/* Deserializer I2C address */
+		reg8_write(client, 0x0a, fwd_en ? 0x11 << priv->port : 0);	/* Enable/disable reverse/forward control for this port */
+		usleep_range(5000, 5500);					/* wait 5ms */
+		client->addr = tmp_addr;
+	};
+}
+
+static int ov2311_set_regs(struct i2c_client *client,
+			    const struct ov2311_reg *regs, int nr_regs)
+{
+	int i;
+
+	for (i = 0; i < nr_regs; i++) {
+		if (regs[i].reg == OV2311_DELAY) {
+			mdelay(regs[i].val);
+			continue;
+		}
+
+		if (reg16_write(client, regs[i].reg, regs[i].val)) {
+			usleep_range(100, 150); /* wait 100ns */
+			reg16_write(client, regs[i].reg, regs[i].val);
+		}
+	}
+
+	return 0;
+}
+
+static int ov2311_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	return 0;
+}
+
+static int ov2311_set_window(struct v4l2_subdev *sd)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+
+	dev_dbg(&client->dev, "L=%d T=%d %dx%d\n", priv->rect.left, priv->rect.top, priv->rect.width, priv->rect.height);
+#if 0
+	/* setup resolution */
+	reg16_write(client, 0x3808, priv->rect.width >> 8);
+	reg16_write(client, 0x3809, priv->rect.width & 0xff);
+	reg16_write(client, 0x380a, priv->rect.height >> 8);
+	reg16_write(client, 0x380b, priv->rect.height & 0xff);
+
+	/* horiz isp windowstart */
+	reg16_write(client, 0x3810, priv->rect.left >> 8);
+	reg16_write(client, 0x3811, priv->rect.left & 0xff);
+	reg16_write(client, 0x3812, priv->rect.top >> 8);
+	reg16_write(client, 0x3813, priv->rect.top & 0xff);
+#endif
+	return 0;
+};
+
+static int ov2311_get_fmt(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_pad_config *cfg,
+			   struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *mf = &format->format;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+
+	if (format->pad)
+		return -EINVAL;
+
+	mf->width = priv->rect.width;
+	mf->height = priv->rect.height;
+	mf->code = OV2311_MEDIA_BUS_FMT;
+	mf->colorspace = V4L2_COLORSPACE_SMPTE170M;
+	mf->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int ov2311_set_fmt(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_pad_config *cfg,
+			   struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *mf = &format->format;
+
+	mf->code = OV2311_MEDIA_BUS_FMT;
+	mf->colorspace = V4L2_COLORSPACE_SMPTE170M;
+	mf->field = V4L2_FIELD_NONE;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+		cfg->try_fmt = *mf;
+
+	return 0;
+}
+
+static int ov2311_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_pad_config *cfg,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->pad || code->index > 0)
+		return -EINVAL;
+
+	code->code = OV2311_MEDIA_BUS_FMT;
+
+	return 0;
+}
+
+static int ov2311_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+
+	memcpy(edid->edid, priv->id, 6);
+
+	edid->edid[6] = 0xff;
+	edid->edid[7] = client->addr;
+	edid->edid[8] = OV2311_VERSION_REG >> 8;
+	edid->edid[9] = OV2311_VERSION_REG & 0xff;
+
+	return 0;
+}
+
+static int ov2311_set_selection(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_pad_config *cfg,
+				 struct v4l2_subdev_selection *sel)
+{
+	struct v4l2_rect *rect = &sel->r;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+
+	if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE ||
+	    sel->target != V4L2_SEL_TGT_CROP)
+		return -EINVAL;
+
+	rect->left = ALIGN(rect->left, 2);
+	rect->top = ALIGN(rect->top, 2);
+	rect->width = ALIGN(rect->width, 2);
+	rect->height = ALIGN(rect->height, 2);
+
+	if ((rect->left + rect->width > OV2311_MAX_WIDTH) ||
+	    (rect->top + rect->height > OV2311_MAX_HEIGHT))
+		*rect = priv->rect;
+
+	priv->rect.left = rect->left;
+	priv->rect.top = rect->top;
+	priv->rect.width = rect->width;
+	priv->rect.height = rect->height;
+
+	ov2311_set_window(sd);
+
+	return 0;
+}
+
+static int ov2311_get_selection(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_pad_config *cfg,
+				 struct v4l2_subdev_selection *sel)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+
+	if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
+		return -EINVAL;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = OV2311_MAX_WIDTH;
+		sel->r.height = OV2311_MAX_HEIGHT;
+		return 0;
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = OV2311_MAX_WIDTH;
+		sel->r.height = OV2311_MAX_HEIGHT;
+		return 0;
+	case V4L2_SEL_TGT_CROP:
+		sel->r = priv->rect;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ov2311_g_mbus_config(struct v4l2_subdev *sd,
+				 struct v4l2_mbus_config *cfg)
+{
+	cfg->flags = V4L2_MBUS_CSI2_1_LANE | V4L2_MBUS_CSI2_CHANNEL_0 |
+		     V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+	cfg->type = V4L2_MBUS_CSI2;
+
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ov2311_g_register(struct v4l2_subdev *sd,
+			      struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret;
+	u8 val = 0;
+
+	ret = reg16_read(client, (u16)reg->reg, &val);
+	if (ret < 0)
+		return ret;
+
+	reg->val = val;
+	reg->size = sizeof(u16);
+
+	return 0;
+}
+
+static int ov2311_s_register(struct v4l2_subdev *sd,
+			      const struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return reg16_write(client, (u16)reg->reg, (u8)reg->val);
+}
+#endif
+
+static struct v4l2_subdev_core_ops ov2311_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = ov2311_g_register,
+	.s_register = ov2311_s_register,
+#endif
+};
+
+static int ov2311_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = ov2311_to_sd(ctrl);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+	int ret = 0;
+	u8 val = 0;
+
+	if (!priv->init_complete)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+	case V4L2_CID_CONTRAST:
+	case V4L2_CID_SATURATION:
+	case V4L2_CID_HUE:
+	case V4L2_CID_GAMMA:
+		break;
+	case V4L2_CID_GAIN:
+		reg16_write(client, 0x350A, ctrl->val / 0x3ff); // COARSE: 4.10 format
+		reg16_write(client, 0x350B, (ctrl->val % 0x3ff) >> 2); // FINE: 4.10 format
+		reg16_write(client, 0x350C, (ctrl->val % 0x3ff) << 6); // FINE: 4.10 format
+		break;
+	case V4L2_CID_ANALOGUE_GAIN:
+		reg16_write(client, 0x3508, ctrl->val / 0xf); // COARSE: 5.4 format
+		reg16_write(client, 0x3509, (ctrl->val % 0xf) << 4); // FINE: 5.4 format
+		break;
+	case V4L2_CID_EXPOSURE:
+		reg16_write(client, 0x3501, ctrl->val >> 8);
+		reg16_write(client, 0x3502, ctrl->val & 0xff);
+		break;
+	case V4L2_CID_HFLIP:
+		reg16_read(client, 0x3821, &val);
+		val &= ~0x04;
+		val |= (ctrl->val ? 0x04 : 0);
+		reg16_write(client, 0x3821, val);
+		break;
+	case V4L2_CID_VFLIP:
+		reg16_read(client, 0x3820, &val);
+		val &= ~0x44;
+		val |= (ctrl->val ? 0x44 : 0);
+		reg16_write(client, 0x3820, val);
+		break;
+	case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ov2311_ctrl_ops = {
+	.s_ctrl = ov2311_s_ctrl,
+};
+
+static struct v4l2_subdev_video_ops ov2311_video_ops = {
+	.s_stream	= ov2311_s_stream,
+	.g_mbus_config	= ov2311_g_mbus_config,
+};
+
+static const struct v4l2_subdev_pad_ops ov2311_subdev_pad_ops = {
+	.get_edid	= ov2311_get_edid,
+	.enum_mbus_code	= ov2311_enum_mbus_code,
+	.get_selection	= ov2311_get_selection,
+	.set_selection	= ov2311_set_selection,
+	.get_fmt	= ov2311_get_fmt,
+	.set_fmt	= ov2311_set_fmt,
+};
+
+static struct v4l2_subdev_ops ov2311_subdev_ops = {
+	.core	= &ov2311_core_ops,
+	.video	= &ov2311_video_ops,
+	.pad	= &ov2311_subdev_pad_ops,
+};
+
+static void ov2311_otp_id_read(struct i2c_client *client)
+{
+	struct ov2311_priv *priv = to_ov2311(client);
+	int i;
+
+	reg16_write(client, 0x100, 1);
+	reg16_write(client, 0x3d81, 1);
+	usleep_range(25000, 25500); /* wait 25 ms */
+
+	for (i = 0; i < 6; i++) {
+		/* first 6 bytes are equal on all ov2311 */
+		reg16_read(client, 0x7000 + i + 6, &priv->id[i]);
+	}
+
+	reg16_write(client, 0x100, 0);
+}
+
+static ssize_t ov2311_otp_id_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(to_i2c_client(dev));
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov2311_priv *priv = to_ov2311(client);
+
+	return snprintf(buf, 32, "%02x:%02x:%02x:%02x:%02x:%02x\n",
+			priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]);
+}
+
+static DEVICE_ATTR(otp_id_ov2311, S_IRUGO, ov2311_otp_id_show, NULL);
+
+static int ov2311_initialize(struct i2c_client *client)
+{
+	struct ov2311_priv *priv = to_ov2311(client);
+	u16 pid;
+	u8 val = 0, rev = 0;
+	int ret = 0;
+	int tmp_addr = 0;
+
+	ov2311_s_port(client, 1);
+
+	/* check and show product ID and manufacturer ID */
+	reg16_read(client, OV2311_PID, &val);
+	pid = val;
+	reg16_read(client, OV2311_VER, &val);
+	pid = (pid << 8) | val;
+
+	if (pid != OV2311_VERSION_REG) {
+		dev_dbg(&client->dev, "Product ID error %x\n", pid);
+		ret = -ENODEV;
+		goto out;
+	}
+
+	tmp_addr = client->addr;
+	if (priv->ti9x4_addr) {
+		client->addr = priv->ti9x3_addr;			/* Serializer I2C address */
+		reg8_write(client, 0x02, 0x13);				/* MIPI 2-lanes */
+	}
+	client->addr = tmp_addr;
+
+	/* check revision */
+	reg16_read(client, OV2311_REV, &rev);
+	/* Read OTP IDs */
+	ov2311_otp_id_read(client);
+	/* Program wizard registers */
+	ov2311_set_regs(client, ov2311_regs_wizard_r1c, ARRAY_SIZE(ov2311_regs_wizard_r1c));
+
+	dev_info(&client->dev, "ov2311 PID %x (rev %x), res %dx%d, OTP_ID %02x:%02x:%02x:%02x:%02x:%02x\n",
+		 pid, rev, OV2311_MAX_WIDTH, OV2311_MAX_HEIGHT, priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]);
+out:
+	ov2311_s_port(client, 0);
+
+	return ret;
+}
+
+static int ov2311_parse_dt(struct device_node *np, struct ov2311_priv *priv)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&priv->sd);
+	int i;
+	struct device_node *endpoint = NULL, *rendpoint = NULL;
+	int tmp_addr = 0;
+
+	for (i = 0; ; i++) {
+		endpoint = of_graph_get_next_endpoint(np, endpoint);
+		if (!endpoint)
+			break;
+
+		of_node_put(endpoint);
+
+		of_property_read_u32(endpoint, "dvp-order", &priv->dvp_order);
+
+		rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0);
+		if (!rendpoint)
+			continue;
+
+		if (!of_property_read_u32(rendpoint, "max9271-addr", &priv->max9271_addr) &&
+		    !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->max9286_addr) &&
+		    !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port))
+			break;
+
+		if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) &&
+		    !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti9x4") &&
+		    !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti9x4_addr) &&
+		    !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port))
+			break;
+	}
+
+	if (!priv->max9286_addr && !priv->ti9x4_addr) {
+		dev_err(&client->dev, "deserializer does not present for OV2311\n");
+		return -EINVAL;
+	}
+
+	ov2311_s_port(client, 1);
+
+	/* setup I2C translator address */
+	tmp_addr = client->addr;
+	if (priv->max9286_addr) {
+		client->addr = priv->max9271_addr;			/* Serializer I2C address */
+
+		reg8_write(client, 0x09, tmp_addr << 1);		/* Sensor translated I2C address */
+		reg8_write(client, 0x0A, OV2311_I2C_ADDR << 1);		/* Sensor native I2C address */
+		usleep_range(2000, 2500);				/* wait 2ms */
+	};
+
+	if (priv->ti9x4_addr) {
+		client->addr = priv->ti9x4_addr;			/* Deserializer I2C address */
+
+		reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */
+		usleep_range(2000, 2500);				/* wait 2ms */
+		reg8_write(client, 0x65, tmp_addr << 1);		/* Sensor translated I2C address */
+		reg8_write(client, 0x5d, OV2311_I2C_ADDR << 1);		/* Sensor native I2C address */
+	}
+	client->addr = tmp_addr;
+
+	return 0;
+}
+
+static int ov2311_probe(struct i2c_client *client,
+			 const struct i2c_device_id *did)
+{
+	struct ov2311_priv *priv;
+	struct v4l2_ctrl *ctrl;
+	int ret;
+
+	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&priv->sd, client, &ov2311_subdev_ops);
+	priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+	priv->rect.left = 0;
+	priv->rect.top = 0;
+	priv->rect.width = OV2311_MAX_WIDTH;
+	priv->rect.height = OV2311_MAX_HEIGHT;
+	priv->fps_denominator = 30;
+
+	v4l2_ctrl_handler_init(&priv->hdl, 4);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_BRIGHTNESS, 0, 0xff, 1, 0x30);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_CONTRAST, 0, 4, 1, 2);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_SATURATION, 0, 0xff, 1, 0xff);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_HUE, 0, 255, 1, 0);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_GAMMA, 0, 0xffff, 1, 0x233);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_GAIN, 0, 0x3ff*4, 1, 0x3ff);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_ANALOGUE_GAIN, 0, 0xf*5, 1, 0xf);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_EXPOSURE, 0, 0x580, 1, 0x57c);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_HFLIP, 0, 1, 1, 0);
+	v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov2311_ctrl_ops,
+			  V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 9);
+	if (ctrl)
+		ctrl->flags &= ~V4L2_CTRL_FLAG_READ_ONLY;
+	priv->sd.ctrl_handler = &priv->hdl;
+
+	ret = priv->hdl.error;
+	if (ret)
+		goto cleanup;
+
+	v4l2_ctrl_handler_setup(&priv->hdl);
+
+	priv->pad.flags = MEDIA_PAD_FL_SOURCE;
+	priv->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR;
+	ret = media_entity_pads_init(&priv->sd.entity, 1, &priv->pad);
+	if (ret < 0)
+		goto cleanup;
+
+	ret = ov2311_parse_dt(client->dev.of_node, priv);
+	if (ret)
+		goto cleanup;
+
+	ret = ov2311_initialize(client);
+	if (ret < 0)
+		goto cleanup;
+
+	ret = v4l2_async_register_subdev(&priv->sd);
+	if (ret)
+		goto cleanup;
+
+	if (device_create_file(&client->dev, &dev_attr_otp_id_ov2311) != 0) {
+		dev_err(&client->dev, "sysfs otp_id entry creation failed\n");
+		goto cleanup;
+	}
+
+	priv->init_complete = 1;
+
+	return 0;
+
+cleanup:
+	media_entity_cleanup(&priv->sd.entity);
+	v4l2_ctrl_handler_free(&priv->hdl);
+	v4l2_device_unregister_subdev(&priv->sd);
+#ifdef CONFIG_SOC_CAMERA_OV2311
+	v4l_err(client, "failed to probe @ 0x%02x (%s)\n",
+		client->addr, client->adapter->name);
+#endif
+	return ret;
+}
+
+static int ov2311_remove(struct i2c_client *client)
+{
+	struct ov2311_priv *priv = i2c_get_clientdata(client);
+
+	device_remove_file(&client->dev, &dev_attr_otp_id_ov2311);
+	v4l2_async_unregister_subdev(&priv->sd);
+	media_entity_cleanup(&priv->sd.entity);
+	v4l2_ctrl_handler_free(&priv->hdl);
+	v4l2_device_unregister_subdev(&priv->sd);
+
+	return 0;
+}
+
+#ifdef CONFIG_SOC_CAMERA_OV2311
+static const struct i2c_device_id ov2311_id[] = {
+	{ "ov2311", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ov2311_id);
+
+static const struct of_device_id ov2311_of_ids[] = {
+	{ .compatible = "ovti,ov2311", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ov2311_of_ids);
+
+static struct i2c_driver ov2311_i2c_driver = {
+	.driver	= {
+		.name		= "ov2311",
+		.of_match_table	= ov2311_of_ids,
+	},
+	.probe		= ov2311_probe,
+	.remove		= ov2311_remove,
+	.id_table	= ov2311_id,
+};
+
+module_i2c_driver(ov2311_i2c_driver);
+
+MODULE_DESCRIPTION("SoC Camera driver for OV2311");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
+#endif
diff --git a/drivers/media/i2c/soc_camera/ov2311.h b/drivers/media/i2c/soc_camera/ov2311.h
new file mode 100644
index 0000000..3a56b0b
--- /dev/null
+++ b/drivers/media/i2c/soc_camera/ov2311.h
@@ -0,0 +1,217 @@
+/*
+ * OmniVision ov2311 sensor camera wizard 1600x130@30/GREY8/MIPI
+ *
+ * Copyright (C) 2015-2017 Cogent Embedded, Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+//#define OV2311_DISPLAY_PATTERN
+//#define OV2311_FSIN_ENABLE
+
+#define OV2311_MAX_WIDTH	1600
+#define OV2311_MAX_HEIGHT	1300
+
+#define OV2311_DELAY		0xffff
+
+#define OV2311_SENSOR_WIDTH	1616
+#define OV2311_SENSOR_HEIGHT	1316
+
+#define OV2311_X_START		((OV2311_SENSOR_WIDTH - OV2311_MAX_WIDTH) / 2)
+#define OV2311_Y_START		((OV2311_SENSOR_HEIGHT - OV2311_MAX_HEIGHT) / 2)
+#define OV2311_X_END		(OV2311_X_START + OV2311_MAX_WIDTH - 1)
+#define OV2311_Y_END		(OV2311_Y_START + OV2311_MAX_HEIGHT - 1)
+
+struct ov2311_reg {
+	u16	reg;
+	u8	val;
+};
+
+/* R1600x1300 RAW10 MIPI 60fps */
+static const struct ov2311_reg ov2311_regs_wizard_r1c[] = {
+{0x0103, 0x01},
+{0x0100, 0x00},
+{0x010c, 0x02},
+{0x010b, 0x01},
+{0x0300, 0x01},
+{0x0302, 0x32},
+{0x0303, 0x00},
+{0x0304, 0x03},
+{0x0305, 0x02},
+{0x0306, 0x01},
+{0x030d, 0x5a},
+{0x030e, 0x04},
+{0x3001, 0x02},
+{0x3004, 0x00},
+{0x3005, 0x00},
+{0x3006, 0x0a},
+{0x3011, 0x0d},
+{0x3014, 0x04},
+{0x301c, 0xf0},
+{0x3020, 0x20},
+{0x302c, 0x00},
+{0x302d, 0x00},
+{0x302e, 0x00},
+{0x302f, 0x03},
+{0x3030, 0x10},
+{0x303f, 0x03},
+{0x3103, 0x00},
+{0x3106, 0x08},
+{0x31ff, 0x01},
+{0x3501, 0x05},
+{0x3502, 0x7c},
+{0x3506, 0x00},
+{0x3507, 0x00},
+{0x3620, 0x67},
+{0x3633, 0x78},
+{0x3662, 0x65},
+{0x3664, 0xb0},
+{0x3666, 0x70},
+{0x3670, 0x68},
+{0x3674, 0x10},
+{0x3675, 0x00},
+{0x367e, 0x90},
+{0x3680, 0x84},
+{0x36a2, 0x04},
+{0x36a3, 0x80},
+{0x36b0, 0x00},
+{0x3700, 0x35},
+{0x3704, 0x39},
+{0x370a, 0x50},
+{0x3712, 0x00},
+{0x3713, 0x02},
+{0x3778, 0x00},
+{0x379b, 0x01},
+{0x379c, 0x10},
+{0x3800, 0x00},
+{0x3801, 0x00},
+{0x3802, 0x00},
+{0x3803, 0x00},
+{0x3804, 0x06},
+{0x3805, 0x4f},
+{0x3806, 0x05},
+{0x3807, 0x23},
+{0x3808, OV2311_MAX_WIDTH >> 8},
+{0x3809, OV2311_MAX_WIDTH & 0xff},
+{0x380a, OV2311_MAX_HEIGHT >> 8},
+{0x380b, OV2311_MAX_HEIGHT & 0xff},
+{0x380c, 0x03},
+{0x380d, 0xa8},
+{0x380e, 0x05},
+{0x380f, 0x88},
+{0x3810, OV2311_X_START >> 8},
+{0x3811, OV2311_X_START & 0xff},
+{0x3812, OV2311_Y_START >> 8},
+{0x3813, OV2311_X_START & 0xff},
+{0x3814, 0x11},
+{0x3815, 0x11},
+{0x3816, 0x00},
+{0x3817, 0x01},
+{0x3818, 0x00},
+{0x3819, 0x05},
+{0x3820, 0x00},
+{0x3821, 0x00},
+{0x382b, 0x5a},
+{0x382c, 0x0a},
+{0x382d, 0xf8},
+{0x3881, 0x44},
+{0x3882, 0x02},
+{0x3883, 0x8c},
+{0x3885, 0x07},
+{0x389d, 0x03},
+{0x38a6, 0x00},
+{0x38a7, 0x01},
+{0x38b3, 0x07},
+{0x38b1, 0x00},
+{0x38e5, 0x02},
+{0x38e7, 0x00},
+{0x38e8, 0x00},
+{0x3910, 0xff},
+{0x3911, 0xff},
+{0x3912, 0x08},
+{0x3913, 0x00},
+{0x3914, 0x00},
+{0x3915, 0x00},
+{0x391c, 0x00},
+{0x3920, 0xff},
+{0x3921, 0x80},
+{0x3922, 0x00},
+{0x3923, 0x00},
+{0x3924, 0x05},
+{0x3925, 0x00},
+{0x3926, 0x00},
+{0x3927, 0x00},
+{0x3928, 0x1a},
+{0x392d, 0x03},
+{0x392e, 0xa8},
+{0x392f, 0x08},
+{0x4001, 0x00},
+{0x4003, 0x40},
+{0x4008, 0x04},
+{0x4009, 0x1b},
+{0x400c, 0x04},
+{0x400d, 0x1b},
+{0x4010, 0xf4},
+{0x4011, 0x00},
+{0x4016, 0x00},
+{0x4017, 0x04},
+{0x4042, 0x11},
+{0x4043, 0x70},
+{0x4045, 0x00},
+{0x4409, 0x5f},
+{0x4509, 0x00},
+{0x450b, 0x00},
+{0x4600, 0x00},
+{0x4601, 0xa0},
+{0x4708, 0x09},
+{0x470c, 0x81},
+{0x4710, 0x06},
+{0x4711, 0x00},
+{0x4800, 0x00},
+{0x481f, 0x30},
+{0x4837, 0x14},
+{0x4f00, 0x00},
+{0x4f07, 0x00},
+{0x4f08, 0x03},
+{0x4f09, 0x08},
+{0x4f0c, 0x05},
+{0x4f0d, 0xb4},
+{0x4f10, 0x00},
+{0x4f11, 0x00},
+{0x4f12, 0x07},
+{0x4f13, 0xe2},
+{0x5000, 0x9f},
+{0x5001, 0x20},
+{0x5026, 0x00},
+{0x5c00, 0x00},
+{0x5c01, 0x2c},
+{0x5c02, 0x00},
+{0x5c03, 0x7f},
+{0x5e00, 0x00},
+{0x5e01, 0x41},
+{0x38b1, 0x02},
+{0x3880, 0x00},
+
+#if 1  /* Y8 mode */
+{0x3016, 0xF1},
+{0x0100, 0x01},
+{0x4814, 0x6A}, //; dt_man en, both embed/image data type are 0x2A
+{0x3218, 0x32},
+{0x3216, 0x01},
+{0x3208, 0x04},
+{0x3D81, 0x01},
+{0x4605, 0x02},
+{0x4816, 0x0A},
+{0x3208, 0x14},
+{0x3662, 0x67}, //; [1] raw8
+{0x366F, 0x1A}, //; [6] MSB
+//{0x3674, 0x11}, //; [0] embed_en, add embed data before normal image
+{0x3674, 0x10}, //; [0] embed_dis, add embed data before normal image
+{0x3016, 0xF0},
+#endif
+
+{0x0100, 0x01},
+};
-- 
2.7.4

