summaryrefslogtreecommitdiffstats
path: root/bsp/meta-rcar/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0455-media-i2c-ox01d10-add-imager-support.patch
diff options
context:
space:
mode:
Diffstat (limited to 'bsp/meta-rcar/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0455-media-i2c-ox01d10-add-imager-support.patch')
-rw-r--r--bsp/meta-rcar/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0455-media-i2c-ox01d10-add-imager-support.patch1162
1 files changed, 1162 insertions, 0 deletions
diff --git a/bsp/meta-rcar/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0455-media-i2c-ox01d10-add-imager-support.patch b/bsp/meta-rcar/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0455-media-i2c-ox01d10-add-imager-support.patch
new file mode 100644
index 00000000..3551d572
--- /dev/null
+++ b/bsp/meta-rcar/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0455-media-i2c-ox01d10-add-imager-support.patch
@@ -0,0 +1,1162 @@
+From fc00b90bd8aa659028e73364f6bef6789da95cc6 Mon Sep 17 00:00:00 2001
+From: Vladimir Barinov <vladimir.barinov@cogentembedded.com>
+Date: Wed, 25 Dec 2019 21:03:24 +0300
+Subject: [PATCH] media: i2c: ox01d10: add imager support
+
+This adds OVT OX01D10 imager support
+
+Signed-off-by: Vladimir Barinov <vladimir.barinov@cogentembedded.com>
+---
+ drivers/media/i2c/soc_camera/ov106xx.c | 8 +
+ drivers/media/i2c/soc_camera/ox01d10.c | 611 +++++++++++++++++++++++++++++++++
+ drivers/media/i2c/soc_camera/ox01d10.h | 487 ++++++++++++++++++++++++++
+ 3 files changed, 1106 insertions(+)
+ create mode 100644 drivers/media/i2c/soc_camera/ox01d10.c
+ create mode 100644 drivers/media/i2c/soc_camera/ox01d10.h
+
+diff --git a/drivers/media/i2c/soc_camera/ov106xx.c b/drivers/media/i2c/soc_camera/ov106xx.c
+index 4d5e084..841861c 100644
+--- a/drivers/media/i2c/soc_camera/ov106xx.c
++++ b/drivers/media/i2c/soc_camera/ov106xx.c
+@@ -27,6 +27,7 @@ static enum {
+ ID_GW5200_IMX390,
+ ID_OV2775,
+ ID_IMX390,
++ ID_OX01D10,
+ ID_OX03A,
+ ID_ISX016,
+ ID_ISX019,
+@@ -48,6 +49,7 @@ static enum {
+ #include "gw5200_imx390.c"
+ #include "ov2775.c"
+ #include "imx390.c"
++#include "ox01d10.c"
+ #include "ox03a.c"
+ #include "isx016.c"
+ #include "isx019.c"
+@@ -172,6 +174,12 @@ static int ov106xx_probe(struct i2c_client *client,
+ goto out;
+ }
+
++ ret = ox01d10_probe(client, did);
++ if (!ret) {
++ chip_id = ID_OX01D10;
++ goto out;
++ }
++
+ ret = ox03a_probe(client, did);
+ if (!ret) {
+ chip_id = ID_OX03A;
+diff --git a/drivers/media/i2c/soc_camera/ox01d10.c b/drivers/media/i2c/soc_camera/ox01d10.c
+new file mode 100644
+index 0000000..3ea3fef
+--- /dev/null
++++ b/drivers/media/i2c/soc_camera/ox01d10.c
+@@ -0,0 +1,611 @@
++/*
++ * OmniVision OX01D10 sensor camera driver
++ *
++ * Copyright (C) 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 "ox01d10.h"
++
++#define OX01D10_I2C_ADDR 0x36
++
++#define OX01D10_PID_REGA 0x300A
++#define OX01D10_PID_REGB 0x300B
++#define OX01D10_PID 0x5801
++
++#define OX01D10_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR12_1X12
++
++struct ox01d10_priv {
++ struct v4l2_subdev sd;
++ struct v4l2_ctrl_handler hdl;
++ struct media_pad pad;
++ struct v4l2_rect rect;
++ int init_complete;
++ u8 id[6];
++ int exposure;
++ int gain;
++ int again;
++ int autogain;
++ /* serializers */
++ int ti9x4_addr;
++ int ti9x3_addr;
++ int port;
++ int gpio_resetb;
++ int gpio_fsin;
++};
++
++static inline struct ox01d10_priv *to_ox01d10(const struct i2c_client *client)
++{
++ return container_of(i2c_get_clientdata(client), struct ox01d10_priv, sd);
++}
++
++static int ox01d10_set_regs(struct i2c_client *client,
++ const struct ox01d10_reg *regs, int nr_regs)
++{
++ int i;
++
++ for (i = 0; i < nr_regs; i++) {
++ if (regs[i].reg == OX01D10_DELAY) {
++ mdelay(regs[i].val);
++ continue;
++ }
++
++ reg16_write(client, regs[i].reg, regs[i].val);
++ }
++
++ return 0;
++}
++
++static int ox01d10_s_stream(struct v4l2_subdev *sd, int enable)
++{
++ return 0;
++}
++
++static int ox01d10_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 ox01d10_priv *priv = to_ox01d10(client);
++
++ if (format->pad)
++ return -EINVAL;
++
++ mf->width = priv->rect.width;
++ mf->height = priv->rect.height;
++ mf->code = OX01D10_MEDIA_BUS_FMT;
++ mf->colorspace = V4L2_COLORSPACE_SMPTE170M;
++ mf->field = V4L2_FIELD_NONE;
++
++ return 0;
++}
++
++static int ox01d10_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 = OX01D10_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 ox01d10_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 = OX01D10_MEDIA_BUS_FMT;
++
++ return 0;
++}
++
++static int ox01d10_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
++{
++ struct i2c_client *client = v4l2_get_subdevdata(sd);
++ struct ox01d10_priv *priv = to_ox01d10(client);
++
++ memcpy(edid->edid, priv->id, 6);
++
++ edid->edid[6] = 0xff;
++ edid->edid[7] = client->addr;
++ edid->edid[8] = OX01D10_PID >> 8;
++ edid->edid[9] = OX01D10_PID & 0xff;
++
++ return 0;
++}
++
++static int ox01d10_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 ox01d10_priv *priv = to_ox01d10(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 > OX01D10_MAX_WIDTH) ||
++ (rect->top + rect->height > OX01D10_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;
++
++ return 0;
++}
++
++static int ox01d10_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 ox01d10_priv *priv = to_ox01d10(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 = OX01D10_MAX_WIDTH;
++ sel->r.height = OX01D10_MAX_HEIGHT;
++ return 0;
++ case V4L2_SEL_TGT_CROP_DEFAULT:
++ sel->r.left = 0;
++ sel->r.top = 0;
++ sel->r.width = OX01D10_MAX_WIDTH;
++ sel->r.height = OX01D10_MAX_HEIGHT;
++ return 0;
++ case V4L2_SEL_TGT_CROP:
++ sel->r = priv->rect;
++ return 0;
++ default:
++ return -EINVAL;
++ }
++}
++
++static int ox01d10_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 ox01d10_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(u8);
++
++ return 0;
++}
++
++static int ox01d10_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 ox01d10_core_ops = {
++#ifdef CONFIG_VIDEO_ADV_DEBUG
++ .g_register = ox01d10_g_register,
++ .s_register = ox01d10_s_register,
++#endif
++};
++
++static int ox01d10_s_ctrl(struct v4l2_ctrl *ctrl)
++{
++ struct v4l2_subdev *sd = to_sd(ctrl);
++ struct i2c_client *client = v4l2_get_subdevdata(sd);
++ struct ox01d10_priv *priv = to_ox01d10(client);
++ int ret = -EINVAL;
++ 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:
++ case V4L2_CID_SHARPNESS:
++ case V4L2_CID_AUTOGAIN:
++ break;
++ case V4L2_CID_GAIN:
++ /* start recording group3 */
++ ret = reg16_write(client, 0x3208, 0x03);
++ /* HCG digital gain */
++ ret |= reg16_write(client, 0x350a, ctrl->val >> 8);
++ ret |= reg16_write(client, 0x350b, ctrl->val & 0xff);
++ /* LCG digital gain */
++ ret |= reg16_write(client, 0x354a, ctrl->val/8 >> 8);
++ ret |= reg16_write(client, 0x354b, ctrl->val/8 & 0xff);
++ /* VS digital gain */
++ ret |= reg16_write(client, 0x358a, ctrl->val/64 >> 8);
++ ret |= reg16_write(client, 0x358b, ctrl->val/64 & 0xff);
++ /* stop recording and launch group3 */
++ ret |= reg16_write(client, 0x3208, 0x13);
++ ret |= reg16_write(client, 0x3208, 0xe3);
++ break;
++ case V4L2_CID_ANALOGUE_GAIN:
++ /* start recording group3 */
++ ret = reg16_write(client, 0x3208, 0x03);
++ /* HCG real gain */
++ ret |= reg16_write(client, 0x3508, ctrl->val >> 8);
++ ret |= reg16_write(client, 0x3509, ctrl->val & 0xff);
++ /* LCG real gain */
++ ret |= reg16_write(client, 0x3548, ctrl->val/8 >> 8);
++ ret |= reg16_write(client, 0x3549, ctrl->val/8 & 0xff);
++ /* VS real gain */
++ ret |= reg16_write(client, 0x3588, ctrl->val/64 >> 8);
++ ret |= reg16_write(client, 0x3589, ctrl->val/64 & 0xff);
++ /* stop recording and launch group3 */
++ ret |= reg16_write(client, 0x3208, 0x13);
++ ret |= reg16_write(client, 0x3208, 0xe3);
++ break;
++ case V4L2_CID_EXPOSURE:
++ /* start recording group3 */
++ ret = reg16_write(client, 0x3208, 0x03);
++ /* HCG (long) exposure time */
++ ret |= reg16_write(client, 0x3501, ctrl->val >> 8);
++ ret |= reg16_write(client, 0x3502, ctrl->val & 0xff);
++ /* LCG (short) exposure time */
++ ret |= reg16_write(client, 0x3541, ctrl->val/4 >> 8);
++ ret |= reg16_write(client, 0x3542, ctrl->val/4 & 0xff);
++ /* VS exposure time */
++ ret |= reg16_write(client, 0x3581, ctrl->val/16 >> 8);
++ ret |= reg16_write(client, 0x3582, ctrl->val/16 & 0xff);
++ /* stop recording and launch group3 */
++ ret |= reg16_write(client, 0x3208, 0x13);
++ ret |= reg16_write(client, 0x3208, 0xe3);
++ break;
++ case V4L2_CID_HFLIP:
++ /* start recording group3 */
++ ret = reg16_write(client, 0x3208, 0x03);
++ ret = reg16_read(client, 0x3821, &val);
++ if (ctrl->val)
++ val |= 0x04;
++ else
++ val &= ~0x04;
++ ret |= reg16_write(client, 0x3821, val);
++ /* hflip channges CFA, hence compensate it by moving crop window over bayer matrix */
++ ret |= reg16_read(client, 0x3811, &val);
++ if (ctrl->val)
++ val++;
++ else
++ val--;
++ ret |= reg16_write(client, 0x3811, val);
++ /* stop recording and launch group3 */
++ ret |= reg16_write(client, 0x3208, 0x13);
++ ret |= reg16_write(client, 0x3208, 0xe3);
++ break;
++ case V4L2_CID_VFLIP:
++ ret = reg16_read(client, 0x3820, &val);
++ if (ctrl->val)
++ val |= 0x44;
++ else
++ val &= ~0x44;
++ ret |= reg16_write(client, 0x3820, val);
++ break;
++ }
++
++ return ret;
++}
++
++static const struct v4l2_ctrl_ops ox01d10_ctrl_ops = {
++ .s_ctrl = ox01d10_s_ctrl,
++};
++
++static struct v4l2_subdev_video_ops ox01d10_video_ops = {
++ .s_stream = ox01d10_s_stream,
++ .g_mbus_config = ox01d10_g_mbus_config,
++};
++
++static const struct v4l2_subdev_pad_ops ox01d10_subdev_pad_ops = {
++ .get_edid = ox01d10_get_edid,
++ .enum_mbus_code = ox01d10_enum_mbus_code,
++ .get_selection = ox01d10_get_selection,
++ .set_selection = ox01d10_set_selection,
++ .get_fmt = ox01d10_get_fmt,
++ .set_fmt = ox01d10_set_fmt,
++};
++
++static struct v4l2_subdev_ops ox01d10_subdev_ops = {
++ .core = &ox01d10_core_ops,
++ .video = &ox01d10_video_ops,
++ .pad = &ox01d10_subdev_pad_ops,
++};
++
++static void ox01d10_otp_id_read(struct i2c_client *client)
++{
++}
++
++static ssize_t ox01d10_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 ox01d10_priv *priv = to_ox01d10(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_ox01d10, S_IRUGO, ox01d10_otp_id_show, NULL);
++
++static int ox01d10_initialize(struct i2c_client *client)
++{
++ struct ox01d10_priv *priv = to_ox01d10(client);
++ char chip_name[10] = "unknown";
++ u8 val = 0;
++ u16 pid;
++ int ret = 0;
++ int tmp_addr = 0;
++
++ /* check and show model ID */
++ reg16_read(client, OX01D10_PID_REGA, &val);
++ pid = val;
++ reg16_read(client, OX01D10_PID_REGB, &val);
++ pid = (pid << 8) | val;
++
++ if (pid != OX01D10_PID) {
++ dev_dbg(&client->dev, "Product ID error %x\n", pid);
++ ret = -ENODEV;
++ goto err;
++ }
++
++ tmp_addr = client->addr;
++ if (priv->ti9x4_addr) {
++ client->addr = priv->ti9x3_addr;
++ reg8_write(client, 0x02, 0x13); /* MIPI 2-lanes */
++
++ /* Setup XCLK: CLK_OUT=23MHz*160*M/N/CLKDIV, CLK_OUT=24MHz (desired), CLKDIV=4, M=6, N=230, 23*160/4*6/230 = 24MHz = CLK_OUT */
++ reg8_write(client, 0x06, 0x46); /* Set CLKDIV and M */
++ reg8_write(client, 0x07, 0xe6); /* Set N */
++ }
++ client->addr = tmp_addr;
++
++ /* Program wizard registers */
++ ox01d10_set_regs(client, ox01d10_regs_wizard_r1b_hdr3, ARRAY_SIZE(ox01d10_regs_wizard_r1b_hdr3));
++ /* Read OTP IDs */
++ ox01d10_otp_id_read(client);
++
++ dev_info(&client->dev, "ox01d10 PID %x, res %dx%d, OTP_ID %02x:%02x:%02x:%02x:%02x:%02x\n",
++ pid, OX01D10_MAX_WIDTH, OX01D10_MAX_HEIGHT, priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]);
++err:
++ return ret;
++}
++
++static int ox01d10_parse_dt(struct device_node *np, struct ox01d10_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;
++
++ rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0);
++ if (!rendpoint)
++ continue;
++
++ 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;
++ }
++
++ of_node_put(endpoint);
++
++ if (!priv->ti9x4_addr) {
++ dev_err(&client->dev, "deserializer does not present\n");
++ return -EINVAL;
++ }
++
++ /* setup I2C translator address */
++ tmp_addr = client->addr;
++ 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, OX01D10_I2C_ADDR << 1); /* Sensor native I2C address */
++ }
++ client->addr = tmp_addr;
++
++ mdelay(10);
++
++ return 0;
++}
++
++static int ox01d10_probe(struct i2c_client *client,
++ const struct i2c_device_id *did)
++{
++ struct ox01d10_priv *priv;
++ int ret;
++
++ priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ v4l2_i2c_subdev_init(&priv->sd, client, &ox01d10_subdev_ops);
++ priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
++
++ priv->exposure = 0x200;
++ priv->gain = 0x200;
++ priv->again = 0x200;
++ priv->autogain = 1;
++ v4l2_ctrl_handler_init(&priv->hdl, 4);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_BRIGHTNESS, 0, 16, 1, 7);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_CONTRAST, 0, 16, 1, 7);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_SATURATION, 0, 7, 1, 2);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_HUE, 0, 23, 1, 12);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_GAMMA, -128, 128, 1, 0);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_SHARPNESS, 0, 10, 1, 3);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_AUTOGAIN, 0, 1, 1, priv->autogain);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_GAIN, 1, 0xfff, 1, priv->gain);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_ANALOGUE_GAIN, 1, 0xfff, 1, priv->again);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_EXPOSURE, 1, 0xffff, 1, priv->exposure);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_HFLIP, 0, 1, 1, 0);
++ v4l2_ctrl_new_std(&priv->hdl, &ox01d10_ctrl_ops,
++ V4L2_CID_VFLIP, 0, 1, 1, 1);
++ 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 = ox01d10_parse_dt(client->dev.of_node, priv);
++ if (ret)
++ goto cleanup;
++
++ ret = ox01d10_initialize(client);
++ if (ret < 0)
++ goto cleanup;
++
++ priv->rect.left = 0;
++ priv->rect.top = 0;
++ priv->rect.width = OX01D10_MAX_WIDTH;
++ priv->rect.height = OX01D10_MAX_HEIGHT;
++
++ ret = v4l2_async_register_subdev(&priv->sd);
++ if (ret)
++ goto cleanup;
++
++ if (device_create_file(&client->dev, &dev_attr_otp_id_ox01d10) != 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_OX01D10
++ v4l_err(client, "failed to probe @ 0x%02x (%s)\n",
++ client->addr, client->adapter->name);
++#endif
++ return ret;
++}
++
++static int ox01d10_remove(struct i2c_client *client)
++{
++ struct ox01d10_priv *priv = i2c_get_clientdata(client);
++
++ device_remove_file(&client->dev, &dev_attr_otp_id_ox01d10);
++ 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_OX01D10
++static const struct i2c_device_id ox01d10_id[] = {
++ { "ox01d10", 0 },
++ { }
++};
++MODULE_DEVICE_TABLE(i2c, ox01d10_id);
++
++static const struct of_device_id ox01d10_of_ids[] = {
++ { .compatible = "ovti,ox01d10", },
++ { }
++};
++MODULE_DEVICE_TABLE(of, ox01d10_of_ids);
++
++static struct i2c_driver ox01d10_i2c_driver = {
++ .driver = {
++ .name = "ox01d10",
++ .of_match_table = ox01d10_of_ids,
++ },
++ .probe = ox01d10_probe,
++ .remove = ox01d10_remove,
++ .id_table = ox01d10_id,
++};
++
++module_i2c_driver(ox01d10_i2c_driver);
++
++MODULE_DESCRIPTION("SoC Camera driver for OX01D10");
++MODULE_AUTHOR("Vladimir Barinov");
++MODULE_LICENSE("GPL");
++#endif
+diff --git a/drivers/media/i2c/soc_camera/ox01d10.h b/drivers/media/i2c/soc_camera/ox01d10.h
+new file mode 100644
+index 0000000..fd50e55
+--- /dev/null
++++ b/drivers/media/i2c/soc_camera/ox01d10.h
+@@ -0,0 +1,487 @@
++/*
++ * OmniVision OX01D10 sensor camera wizard 1336x1036@30/RGGB/MIPI
++ *
++ * Copyright (C) 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.
++ */
++
++//#define OX01D10_DISPLAY_PATTERN_COLOR_BAR
++
++#define OX01D10_MAX_WIDTH 1280
++#define OX01D10_MAX_HEIGHT 960
++
++#define OX01D10_SENSOR_WIDTH 1336
++#define OX01D10_SENSOR_HEIGHT 1036
++
++#define OX01D10_X_START ((OX01D10_SENSOR_WIDTH - OX01D10_MAX_WIDTH) / 2)
++#define OX01D10_Y_START ((OX01D10_SENSOR_HEIGHT - OX01D10_MAX_HEIGHT) / 2)
++#define OX01D10_X_END (OX01D10_X_START + OX01D10_MAX_WIDTH - 1)
++#define OX01D10_Y_END (OX01D10_Y_START + OX01D10_MAX_HEIGHT - 1)
++
++#define OX01D10_DELAY 0xffff
++
++struct ox01d10_reg {
++ u16 reg;
++ u8 val;
++};
++
++/* wizard: MIPI 1280x960 HDR3_COMB RAW 30fps A02 */
++static const struct ox01d10_reg ox01d10_regs_wizard_r1b_hdr3[] = {
++{0x107, 0x01}, // s/w reset
++{OX01D10_DELAY, 100}, // Wait 10ms
++{0x3002, 0x00},
++{0x3009, 0x26},
++{0x3012, 0x21}, // MIPI, 2 lanes
++{0x3016, 0xd0},
++{0x3018, 0x50},
++{0x301a, 0xb0},
++{0x301e, 0x30},
++{0x301f, 0x61},
++{0x3020, 0x01},
++{0x3022, 0x88},
++{0x3023, 0x80},
++{0x3024, 0x80},
++{0x3028, 0x10},
++{0x3600, 0x02},
++{0x3602, 0x42},
++{0x3603, 0x13},
++{0x3604, 0xb3},
++{0x3605, 0xff},
++{0x3606, 0x90},
++{0x3607, 0x59},
++{0x360d, 0x88},
++{0x3611, 0x49},
++{0x3612, 0x49},
++{0x3613, 0xbe},
++{0x3614, 0xa9},
++{0x3615, 0x89},
++{0x3619, 0x00},
++{0x3620, 0x60},
++{0x362a, 0x18},
++{0x362b, 0x18},
++{0x362c, 0x18},
++{0x362d, 0x18},
++{0x3643, 0x12},
++{0x3644, 0x00},
++{0x3645, 0x17},
++{0x3646, 0x1c},
++{0x3647, 0x12},
++{0x3648, 0x00},
++{0x3649, 0x17},
++{0x364a, 0x1c},
++{0x364c, 0x18},
++{0x364d, 0x18},
++{0x364e, 0x18},
++{0x364f, 0x18},
++{0x3652, 0xca},
++{0x3660, 0x43},
++{0x3661, 0x31},
++{0x3662, 0x00},
++{0x3663, 0x00},
++{0x3665, 0x13},
++{0x3668, 0x80},
++{0x366f, 0x00},
++{0x3671, 0xe7},
++{0x3674, 0x80},
++{0x3678, 0x00},
++{0x303, 0x02},
++{0x305, 0x50},
++{0x306, 0x03},
++{0x307, 0x00},
++{0x308, 0x07},
++{0x30a, 0x01},
++{0x316, 0x00},
++{0x317, 0x41},
++{0x323, 0x04},
++{0x325, 0x50},
++{0x326, 0x00},
++{0x327, 0x03},
++{0x328, 0x07},
++{0x329, 0x00},
++{0x32a, 0x00},
++{0x32b, 0x00},
++{0x32c, 0x02},
++{0x3106, 0x10},
++{0x3d8a, 0x03},
++{0x3d8b, 0xff},
++{0x4580, 0xf8},
++{0x4581, 0xc7},
++{0x458f, 0x00},
++{0x4590, 0x20},
++{0x4d0a, 0x68},
++{0x4d0b, 0x78},
++{0x4d0c, 0xa6},
++{0x4d0d, 0x0c},
++{0x4f00, 0xfa},
++{0x4f01, 0x3f},
++{0x3501, 0x00},
++{0x3502, 0x18},
++{0x3504, 0x28},
++{0x3508, 0x01},
++{0x3509, 0x00},
++{0x350a, 0x01},
++{0x350b, 0x00},
++{0x350c, 0x00},
++{0x3541, 0x00},
++{0x3542, 0x14},
++{0x3544, 0x28},
++{0x3548, 0x01},
++{0x3549, 0x00},
++{0x354a, 0x01},
++{0x354b, 0x00},
++{0x354c, 0x00},
++{0x3581, 0x00},
++{0x3582, 0x10},
++{0x3584, 0x28},
++{0x3586, 0x01},
++{0x3587, 0x69},
++{0x3588, 0x01},
++{0x3589, 0x00},
++{0x358a, 0x01},
++{0x358b, 0x00},
++{0x358c, 0x00},
++{0x358d, 0x0c},
++{0x3700, 0x16},
++{0x3701, 0x2c},
++{0x3703, 0x19},
++{0x3705, 0x00},
++{0x3706, 0x35},
++{0x3707, 0x16},
++{0x3709, 0x29},
++{0x370a, 0x00},
++{0x370b, 0x85},
++{0x370d, 0x08},
++{0x3717, 0x03},
++{0x3718, 0x08},
++{0x371a, 0x04},
++{0x371b, 0x14},
++{0x371d, 0x00},
++{0x3720, 0x03},
++{0x372c, 0x10},
++{0x372d, 0x04},
++{0x3738, 0x4f},
++{0x3739, 0x4f},
++{0x373a, 0x2b},
++{0x373b, 0x24},
++{0x373f, 0x49},
++{0x3740, 0x49},
++{0x3741, 0x25},
++{0x3742, 0x20},
++{0x3747, 0x16},
++{0x3748, 0x16},
++{0x374b, 0x03},
++{0x374c, 0x14},
++{0x3752, 0x03},
++{0x3756, 0x10},
++{0x3757, 0x2e},
++{0x3758, 0x00},
++{0x3759, 0x35},
++{0x375a, 0x00},
++{0x375b, 0x85},
++{0x375e, 0x12},
++{0x3760, 0x09},
++{0x376c, 0x01},
++{0x376d, 0x08},
++{0x376e, 0x08},
++{0x376f, 0x08},
++{0x3771, 0x08},
++{0x3773, 0x00},
++{0x3777, 0x00},
++{0x3779, 0x02},
++{0x377a, 0x00},
++{0x377b, 0x00},
++{0x377c, 0xc8},
++{0x3785, 0x08},
++{0x3790, 0x10},
++{0x3797, 0x00},
++{0x3798, 0x00},
++{0x3799, 0x00},
++{0x379c, 0x01},
++{0x379d, 0x00},
++{0x37a2, 0x02},
++{0x37a3, 0x02},
++{0x37a4, 0x02},
++{0x37a5, 0x09},
++{0x37a6, 0x09},
++{0x37a7, 0x09},
++{0x37a8, 0x03},
++{0x37a9, 0x03},
++{0x37ab, 0x03},
++{0x37ad, 0x05},
++{0x37ae, 0x05},
++{0x37b0, 0x05},
++{0x37b2, 0x04},
++{0x37b3, 0x04},
++{0x37b4, 0x04},
++{0x37b5, 0x03},
++{0x37b6, 0x03},
++{0x37b7, 0x03},
++{0x37bd, 0x01},
++{0x37be, 0x36},
++{0x37c0, 0xd0},
++{0x37c4, 0x57},
++{0x37c5, 0x57},
++{0x37c6, 0x33},
++{0x37c7, 0x29},
++{0x37ce, 0x01},
++{0x37d0, 0x00},
++{0x37d1, 0x35},
++{0x37d2, 0x00},
++{0x37d3, 0x85},
++{0x37d4, 0x00},
++{0x37d5, 0x35},
++{0x37d6, 0x00},
++{0x37d7, 0x85},
++{0x37d8, 0x01},
++{0x37da, 0x02},
++{0x37db, 0x00},
++{0x37dc, 0x4c},
++{0x3c00, 0x04},
++{0x3c0b, 0x26},
++{0x3c12, 0x88},
++{0x3c1f, 0x12},
++{0x3c20, 0x04},
++{0x3c24, 0x0f},
++{0x3c25, 0x14},
++{0x3c26, 0x07},
++{0x3c27, 0x10},
++{0x3c28, 0x06},
++{0x3c29, 0x0b},
++{0x3c2b, 0x09},
++{0x3c2c, 0x0e},
++{0x3c2d, 0x07},
++{0x3c2e, 0x0a},
++{0x3c2f, 0x05},
++{0x3c30, 0x0c},
++{0x3c31, 0x08},
++{0x3c32, 0x0f},
++{0x3c33, 0x0a},
++{0x3c34, 0x0d},
++{0x3c3c, 0x00},
++{0x3c3d, 0x0b},
++{0x3c53, 0xe8},
++{0x3c55, 0x28},
++{0x3c5b, 0x20},
++{0x3ce0, 0x02},
++{0x3ce1, 0x3a},
++{0x3ce2, 0x00},
++{0x3ce3, 0x03},
++/* window start */
++{0x3800, 0x00},
++{0x3801, 0x14},
++{0x3802, 0x00},
++{0x3803, 0x22},
++{0x3804, 0x05},
++{0x3805, 0x23},
++{0x3806, 0x03},
++{0x3807, 0xf1},
++{0x3808, OX01D10_MAX_WIDTH >> 8}, //0x508=1288
++{0x3809, OX01D10_MAX_WIDTH & 0xff},
++{0x380a, OX01D10_MAX_HEIGHT >> 8}, //0x3c8=968
++{0x380b, OX01D10_MAX_HEIGHT & 0xff},
++{0x380c, 0x06},
++{0x380d, 0xa0},
++{0x380e, 0x03}, // VTS=0x3f2
++{0x380f, 0xf2}, // VTS
++{0x3810, 0x00},
++{0x3811, 0x04},
++{0x3812, 0x00},
++{0x3813, 0x04},
++/* window end */
++{0x381c, 0x08},
++{0x3820, 0x44}, /* VPLIP on */
++{0x3821, 0x00}, /* HFLIP off */
++{0x3832, 0x00},
++{0x3834, 0x00},
++{0x383c, 0x48},
++{0x383d, 0x20},
++{0x384c, 0x03},
++{0x384d, 0x88},
++{0x3850, 0x00},
++{0x3851, 0x42},
++{0x3852, 0x00},
++{0x3853, 0x40},
++{0x3856, 0x04},
++{0x3857, 0x6b},
++{0x3858, 0x04},
++{0x385b, 0x04},
++{0x385c, 0x6a},
++{0x3861, 0x00},
++{0x3862, 0x40},
++{0x388c, 0x03},
++{0x388d, 0x5c},
++{0x4502, 0x00},
++{0x4504, 0x01},
++{0x4507, 0x10},
++{0x450b, 0x14},
++{0x450c, 0x04},
++{0x3b84, 0x45},
++{0x3b85, 0x00},
++{0x3409, 0x02},
++{0x3304, 0x04},
++{0x3306, 0x03},
++{0x3307, 0x00},
++{0x3308, 0x00},
++{0x3309, 0x00},
++{0x330a, 0x00},
++{0x330b, 0x00},
++{0x330c, 0x00},
++{0x330d, 0x00},
++{0x330e, 0x00},
++{0x330f, 0x00},
++{0x3310, 0x06},
++{0x3311, 0x05},
++{0x3312, 0x55},
++{0x3313, 0x02},
++{0x3314, 0xaa},
++{0x3315, 0x07},
++{0x3316, 0xf0},
++{0x3317, 0x00},
++{0x4001, 0x2b},
++{0x4004, 0x00},
++{0x4005, 0x40},
++{0x4008, 0x02},
++{0x4009, 0x09},
++{0x400a, 0x02},
++{0x400b, 0x00},
++{0x4020, 0x00},
++{0x4021, 0x00},
++{0x4022, 0x00},
++{0x4023, 0x00},
++{0x402e, 0x00},
++{0x402f, 0x40},
++{0x4030, 0x00},
++{0x4031, 0x40},
++{0x405c, 0x20},
++{0x5000, 0x3d},
++{0x5001, 0x05},
++{0x5030, 0x00},
++{0x5038, 0x10},
++{0x5039, 0x04},
++{0x503a, 0x04},
++#ifdef OX01D10_DISPLAY_PATTERN_COLOR_BAR
++{0x5080, 0xc0}, /* Rolling test pattern for HCG */
++{0x5083, 0x0f},
++{0x50c0, 0xc0}, /* Rolling test pattern for LCG */
++{0x50c3, 0x0f},
++{0x5100, 0xc0}, /* Rolling test pattern for VS */
++#else
++{0x5080, 0x00},
++{0x5083, 0x0f},
++{0x50c0, 0x00},
++{0x50c3, 0x0f},
++{0x5100, 0x00},
++#endif
++{0x5103, 0x0f},
++{0x5140, 0x10},
++{0x5142, 0x10},
++{0x5144, 0x10},
++{0x5146, 0x10},
++{0x5147, 0x08},
++{0x5148, 0x08},
++{0x5149, 0x08},
++{0x514a, 0x08},
++{0x514b, 0x10},
++{0x514c, 0x10},
++{0x514d, 0x08},
++{0x514e, 0x00},
++{0x5150, 0x08},
++{0x5154, 0x00},
++{0x5157, 0x10},
++{0x5159, 0x08},
++{0x515b, 0x08},
++{0x515d, 0x10},
++{0x5160, 0x10},
++{0x5162, 0x10},
++{0x5163, 0x10},
++{0x5164, 0xaa},
++{0x5165, 0xaa},
++{0x5166, 0xaa},
++{0x5167, 0xaa},
++{0x5169, 0xaa},
++{0x516c, 0x99},
++{0x516d, 0xaa},
++{0x516e, 0xaa},
++{0x5174, 0x99},
++{0x5176, 0x99},
++{0x5179, 0xaa},
++{0x517a, 0xaa},
++{0x517d, 0x88},
++{0x517e, 0x40},
++{0x517f, 0x20},
++{0x5182, 0x00},
++{0x5183, 0xee},
++{0x5184, 0x01},
++{0x5185, 0x3d},
++{0x5186, 0x01},
++{0x5188, 0x00},
++{0x5189, 0xd3},
++{0x5220, 0x4b},
++{0x5240, 0x4b},
++{0x5260, 0x4b},
++{0x5280, 0x00},
++{0x5281, 0x5e},
++{0x5282, 0x01},
++{0x5283, 0xfd},
++{0x5285, 0x01},
++{0x5422, 0x10},
++{0x54a2, 0x10},
++{0x5522, 0x10},
++{0x559e, 0x03},
++{0x559f, 0x00},
++{0x55a0, 0x40},
++{0x6008, 0xcf},
++{0x6009, 0x00},
++{0x4903, 0x80},
++{0x4601, 0x30},
++{0x4602, 0x02},
++{0x4603, 0x01},
++{0x4605, 0x03},
++{0x460a, 0x36},
++{0x460c, 0x60},
++{0x4800, 0x04},
++{0x480e, 0x04},
++{0x4813, 0x12}, // VC
++{0x481b, 0x50},
++{0x481f, 0x30},
++{0x482b, 0x04},
++{0x482e, 0x34},
++{0x4837, 0x10},
++{0x484b, 0x27},
++{0x4880, 0x00},
++{0x4881, 0x00},
++{0x4885, 0x03},
++{0x4886, 0x00},
++{0x4708, 0x00},
++{0x470b, 0x0f},
++{0x380e, 0x04}, // VTS=0x400
++{0x380f, 0x00}, //
++/* patch start */
++//{0x3208, 0x03}, // start recording group3
++{0x3501, 0x02}, // HCG exposure integration time MSB
++{0x3502, 0x00}, // HCG exposure integration time LSB
++{0x3541, 0x00}, // LCG exposure integration time MSB
++{0x3542, 0x40}, // LCG exposure integration time LSB
++{0x3581, 0x00}, // VS exposure integration time MSB
++{0x3582, 0x08}, // VS exposure integration time LSB
++{0x3508, 0x02}, // HCG real gain MSB
++{0x3509, 0x00}, // HCG real gain LSB
++{0x350a, 0x02}, // HCG digital gain MSB
++{0x350b, 0x00}, // HCG digital gain LSB
++{0x3548, 0x00}, // LCG real gain MSB
++{0x3549, 0x40}, // LCG real gain LSB
++{0x354a, 0x00}, // LCG digital gain MSB
++{0x354b, 0x40}, // LCG digital gain LSB
++{0x3588, 0x00}, // VS real gain MSB
++{0x3589, 0x08}, // VS real gain LSB
++{0x358a, 0x00}, // VS digital gain
++{0x358b, 0x08}, // VS digital gain
++//{0x3208, 0x13}, // stop recording group3
++//{0x3208, 0xe3}, // launch group3
++/* patch2 end */
++{0x100, 0x01},
++};
+--
+2.7.4
+