[media] V4L: add an IMX074 sensor soc-camera / v4l2-subdev driver
authorGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Tue, 5 Oct 2010 15:33:25 +0000 (12:33 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Thu, 21 Oct 2010 09:55:41 +0000 (07:55 -0200)
This patch adds an initial driver for the IMXъ74 image sensor from Sony.
Lacking documentation, only very basic functionality in one specific image
format has been implemented and tested.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
drivers/media/video/Kconfig
drivers/media/video/Makefile
drivers/media/video/imx074.c [new file with mode: 0644]

index 31d8a2bc32036ef830b2ed80597898c05f140b22..8db8c6fd6fb1ab41f9b954d39bbec0fa94ef236c 100644 (file)
@@ -715,6 +715,12 @@ config SOC_CAMERA
          over a bus like PCI or USB. For example some i2c camera connected
          directly to the data bus of an SoC.
 
+config SOC_CAMERA_IMX074
+       tristate "imx074 support"
+       depends on SOC_CAMERA && I2C
+       help
+         This driver supports IMX074 cameras from Sony
+
 config SOC_CAMERA_MT9M001
        tristate "mt9m001 support"
        depends on SOC_CAMERA && I2C
index 134f616f7a384579fc47dcacd7878ec522de1393..4f0952a758da6e644d95b436b79425babd383dca 100644 (file)
@@ -72,6 +72,7 @@ obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o
 obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o
 obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o
 
+obj-$(CONFIG_SOC_CAMERA_IMX074)                += imx074.o
 obj-$(CONFIG_SOC_CAMERA_MT9M001)       += mt9m001.o
 obj-$(CONFIG_SOC_CAMERA_MT9M111)       += mt9m111.o
 obj-$(CONFIG_SOC_CAMERA_MT9T031)       += mt9t031.o
diff --git a/drivers/media/video/imx074.c b/drivers/media/video/imx074.c
new file mode 100644 (file)
index 0000000..380e459
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * Driver for IMX074 CMOS Image Sensor from Sony
+ *
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * Partially inspired by the IMX074 driver from the Android / MSM tree
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/soc_camera.h>
+#include <media/soc_mediabus.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-chip-ident.h>
+
+/* IMX074 registers */
+
+#define MODE_SELECT                    0x0100
+#define IMAGE_ORIENTATION              0x0101
+#define GROUPED_PARAMETER_HOLD         0x0104
+
+/* Integration Time */
+#define COARSE_INTEGRATION_TIME_HI     0x0202
+#define COARSE_INTEGRATION_TIME_LO     0x0203
+/* Gain */
+#define ANALOGUE_GAIN_CODE_GLOBAL_HI   0x0204
+#define ANALOGUE_GAIN_CODE_GLOBAL_LO   0x0205
+
+/* PLL registers */
+#define PRE_PLL_CLK_DIV                        0x0305
+#define PLL_MULTIPLIER                 0x0307
+#define PLSTATIM                       0x302b
+#define VNDMY_ABLMGSHLMT               0x300a
+#define Y_OPBADDR_START_DI             0x3014
+/* mode setting */
+#define FRAME_LENGTH_LINES_HI          0x0340
+#define FRAME_LENGTH_LINES_LO          0x0341
+#define LINE_LENGTH_PCK_HI             0x0342
+#define LINE_LENGTH_PCK_LO             0x0343
+#define YADDR_START                    0x0347
+#define YADDR_END                      0x034b
+#define X_OUTPUT_SIZE_MSB              0x034c
+#define X_OUTPUT_SIZE_LSB              0x034d
+#define Y_OUTPUT_SIZE_MSB              0x034e
+#define Y_OUTPUT_SIZE_LSB              0x034f
+#define X_EVEN_INC                     0x0381
+#define X_ODD_INC                      0x0383
+#define Y_EVEN_INC                     0x0385
+#define Y_ODD_INC                      0x0387
+
+#define HMODEADD                       0x3001
+#define VMODEADD                       0x3016
+#define VAPPLINE_START                 0x3069
+#define VAPPLINE_END                   0x306b
+#define SHUTTER                                0x3086
+#define HADDAVE                                0x30e8
+#define LANESEL                                0x3301
+
+/* IMX074 supported geometry */
+#define IMX074_WIDTH                   1052
+#define IMX074_HEIGHT                  780
+
+/* IMX074 has only one fixed colorspace per pixelcode */
+struct imx074_datafmt {
+       enum v4l2_mbus_pixelcode        code;
+       enum v4l2_colorspace            colorspace;
+};
+
+struct imx074 {
+       struct v4l2_subdev              subdev;
+       const struct imx074_datafmt     *fmt;
+};
+
+static const struct imx074_datafmt imx074_colour_fmts[] = {
+       {V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB},
+};
+
+static struct imx074 *to_imx074(const struct i2c_client *client)
+{
+       return container_of(i2c_get_clientdata(client), struct imx074, subdev);
+}
+
+/* Find a data format by a pixel code in an array */
+static const struct imx074_datafmt *imx074_find_datafmt(enum v4l2_mbus_pixelcode code)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(imx074_colour_fmts); i++)
+               if (imx074_colour_fmts[i].code == code)
+                       return imx074_colour_fmts + i;
+
+       return NULL;
+}
+
+static int reg_write(struct i2c_client *client, const u16 addr, const u8 data)
+{
+       struct i2c_adapter *adap = client->adapter;
+       struct i2c_msg msg;
+       unsigned char tx[3];
+       int ret;
+
+       msg.addr = client->addr;
+       msg.buf = tx;
+       msg.len = 3;
+       msg.flags = 0;
+
+       tx[0] = addr >> 8;
+       tx[1] = addr & 0xff;
+       tx[2] = data;
+
+       ret = i2c_transfer(adap, &msg, 1);
+
+       mdelay(2);
+
+       return ret == 1 ? 0 : -EIO;
+}
+
+static int reg_read(struct i2c_client *client, const u16 addr)
+{
+       u8 buf[2] = {addr >> 8, addr & 0xff};
+       int ret;
+       struct i2c_msg msgs[] = {
+               {
+                       .addr  = client->addr,
+                       .flags = 0,
+                       .len   = 2,
+                       .buf   = buf,
+               }, {
+                       .addr  = client->addr,
+                       .flags = I2C_M_RD,
+                       .len   = 2,
+                       .buf   = buf,
+               },
+       };
+
+       ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+       if (ret < 0) {
+               dev_warn(&client->dev, "Reading register %x from %x failed\n",
+                        addr, client->addr);
+               return ret;
+       }
+
+       return buf[0] & 0xff; /* no sign-extension */
+}
+
+static int imx074_try_fmt(struct v4l2_subdev *sd,
+                         struct v4l2_mbus_framefmt *mf)
+{
+       const struct imx074_datafmt *fmt = imx074_find_datafmt(mf->code);
+
+       dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code);
+
+       if (!fmt) {
+               mf->code        = imx074_colour_fmts[0].code;
+               mf->colorspace  = imx074_colour_fmts[0].colorspace;
+       }
+
+       mf->width       = IMX074_WIDTH;
+       mf->height      = IMX074_HEIGHT;
+       mf->field       = V4L2_FIELD_NONE;
+
+       return 0;
+}
+
+static int imx074_s_fmt(struct v4l2_subdev *sd,
+                       struct v4l2_mbus_framefmt *mf)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct imx074 *priv = to_imx074(client);
+
+       dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code);
+
+       /* MIPI CSI could have changed the format, double-check */
+       if (!imx074_find_datafmt(mf->code))
+               return -EINVAL;
+
+       imx074_try_fmt(sd, mf);
+
+       priv->fmt = imx074_find_datafmt(mf->code);
+
+       return 0;
+}
+
+static int imx074_g_fmt(struct v4l2_subdev *sd,
+                       struct v4l2_mbus_framefmt *mf)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct imx074 *priv = to_imx074(client);
+
+       const struct imx074_datafmt *fmt = priv->fmt;
+
+       mf->code        = fmt->code;
+       mf->colorspace  = fmt->colorspace;
+       mf->width       = IMX074_WIDTH;
+       mf->height      = IMX074_HEIGHT;
+       mf->field       = V4L2_FIELD_NONE;
+
+       return 0;
+}
+
+static int imx074_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
+{
+       struct v4l2_rect *rect = &a->c;
+
+       a->type         = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       rect->top       = 0;
+       rect->left      = 0;
+       rect->width     = IMX074_WIDTH;
+       rect->height    = IMX074_HEIGHT;
+
+       return 0;
+}
+
+static int imx074_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
+{
+       a->bounds.left                  = 0;
+       a->bounds.top                   = 0;
+       a->bounds.width                 = IMX074_WIDTH;
+       a->bounds.height                = IMX074_HEIGHT;
+       a->defrect                      = a->bounds;
+       a->type                         = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       a->pixelaspect.numerator        = 1;
+       a->pixelaspect.denominator      = 1;
+
+       return 0;
+}
+
+static int imx074_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
+                          enum v4l2_mbus_pixelcode *code)
+{
+       if ((unsigned int)index >= ARRAY_SIZE(imx074_colour_fmts))
+               return -EINVAL;
+
+       *code = imx074_colour_fmts[index].code;
+       return 0;
+}
+
+static int imx074_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+       /* MODE_SELECT: stream or standby */
+       return reg_write(client, MODE_SELECT, !!enable);
+}
+
+static int imx074_g_chip_ident(struct v4l2_subdev *sd,
+                              struct v4l2_dbg_chip_ident *id)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+       if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR)
+               return -EINVAL;
+
+       if (id->match.addr != client->addr)
+               return -ENODEV;
+
+       id->ident       = V4L2_IDENT_IMX074;
+       id->revision    = 0;
+
+       return 0;
+}
+
+static struct v4l2_subdev_video_ops imx074_subdev_video_ops = {
+       .s_stream       = imx074_s_stream,
+       .s_mbus_fmt     = imx074_s_fmt,
+       .g_mbus_fmt     = imx074_g_fmt,
+       .try_mbus_fmt   = imx074_try_fmt,
+       .enum_mbus_fmt  = imx074_enum_fmt,
+       .g_crop         = imx074_g_crop,
+       .cropcap        = imx074_cropcap,
+};
+
+static struct v4l2_subdev_core_ops imx074_subdev_core_ops = {
+       .g_chip_ident   = imx074_g_chip_ident,
+};
+
+static struct v4l2_subdev_ops imx074_subdev_ops = {
+       .core   = &imx074_subdev_core_ops,
+       .video  = &imx074_subdev_video_ops,
+};
+
+/*
+ * We have to provide soc-camera operations, but we don't have anything to say
+ * there. The MIPI CSI2 driver will provide .query_bus_param and .set_bus_param
+ */
+static unsigned long imx074_query_bus_param(struct soc_camera_device *icd)
+{
+       return 0;
+}
+
+static int imx074_set_bus_param(struct soc_camera_device *icd,
+                                unsigned long flags)
+{
+       return -1;
+}
+
+static struct soc_camera_ops imx074_ops = {
+       .query_bus_param        = imx074_query_bus_param,
+       .set_bus_param          = imx074_set_bus_param,
+};
+
+static int imx074_video_probe(struct soc_camera_device *icd,
+                             struct i2c_client *client)
+{
+       int ret;
+       u16 id;
+
+       /* Read sensor Model ID */
+       ret = reg_read(client, 0);
+       if (ret < 0)
+               return ret;
+
+       id = ret << 8;
+
+       ret = reg_read(client, 1);
+       if (ret < 0)
+               return ret;
+
+       id |= ret;
+
+       dev_info(&client->dev, "Chip ID 0x%04x detected\n", id);
+
+       if (id != 0x74)
+               return -ENODEV;
+
+       /* PLL Setting EXTCLK=24MHz, 22.5times */
+       reg_write(client, PLL_MULTIPLIER, 0x2D);
+       reg_write(client, PRE_PLL_CLK_DIV, 0x02);
+       reg_write(client, PLSTATIM, 0x4B);
+
+       /* 2-lane mode */
+       reg_write(client, 0x3024, 0x00);
+
+       reg_write(client, IMAGE_ORIENTATION, 0x00);
+
+       /* select RAW mode:
+        * 0x08+0x08 = top 8 bits
+        * 0x0a+0x08 = compressed 8-bits
+        * 0x0a+0x0a = 10 bits
+        */
+       reg_write(client, 0x0112, 0x08);
+       reg_write(client, 0x0113, 0x08);
+
+       /* Base setting for High frame mode */
+       reg_write(client, VNDMY_ABLMGSHLMT, 0x80);
+       reg_write(client, Y_OPBADDR_START_DI, 0x08);
+       reg_write(client, 0x3015, 0x37);
+       reg_write(client, 0x301C, 0x01);
+       reg_write(client, 0x302C, 0x05);
+       reg_write(client, 0x3031, 0x26);
+       reg_write(client, 0x3041, 0x60);
+       reg_write(client, 0x3051, 0x24);
+       reg_write(client, 0x3053, 0x34);
+       reg_write(client, 0x3057, 0xC0);
+       reg_write(client, 0x305C, 0x09);
+       reg_write(client, 0x305D, 0x07);
+       reg_write(client, 0x3060, 0x30);
+       reg_write(client, 0x3065, 0x00);
+       reg_write(client, 0x30AA, 0x08);
+       reg_write(client, 0x30AB, 0x1C);
+       reg_write(client, 0x30B0, 0x32);
+       reg_write(client, 0x30B2, 0x83);
+       reg_write(client, 0x30D3, 0x04);
+       reg_write(client, 0x3106, 0x78);
+       reg_write(client, 0x310C, 0x82);
+       reg_write(client, 0x3304, 0x05);
+       reg_write(client, 0x3305, 0x04);
+       reg_write(client, 0x3306, 0x11);
+       reg_write(client, 0x3307, 0x02);
+       reg_write(client, 0x3308, 0x0C);
+       reg_write(client, 0x3309, 0x06);
+       reg_write(client, 0x330A, 0x08);
+       reg_write(client, 0x330B, 0x04);
+       reg_write(client, 0x330C, 0x08);
+       reg_write(client, 0x330D, 0x06);
+       reg_write(client, 0x330E, 0x01);
+       reg_write(client, 0x3381, 0x00);
+
+       /* V : 1/2V-addition (1,3), H : 1/2H-averaging (1,3) -> Full HD */
+       /* 1608 = 1560 + 48 (black lines) */
+       reg_write(client, FRAME_LENGTH_LINES_HI, 0x06);
+       reg_write(client, FRAME_LENGTH_LINES_LO, 0x48);
+       reg_write(client, YADDR_START, 0x00);
+       reg_write(client, YADDR_END, 0x2F);
+       /* 0x838 == 2104 */
+       reg_write(client, X_OUTPUT_SIZE_MSB, 0x08);
+       reg_write(client, X_OUTPUT_SIZE_LSB, 0x38);
+       /* 0x618 == 1560 */
+       reg_write(client, Y_OUTPUT_SIZE_MSB, 0x06);
+       reg_write(client, Y_OUTPUT_SIZE_LSB, 0x18);
+       reg_write(client, X_EVEN_INC, 0x01);
+       reg_write(client, X_ODD_INC, 0x03);
+       reg_write(client, Y_EVEN_INC, 0x01);
+       reg_write(client, Y_ODD_INC, 0x03);
+       reg_write(client, HMODEADD, 0x00);
+       reg_write(client, VMODEADD, 0x16);
+       reg_write(client, VAPPLINE_START, 0x24);
+       reg_write(client, VAPPLINE_END, 0x53);
+       reg_write(client, SHUTTER, 0x00);
+       reg_write(client, HADDAVE, 0x80);
+
+       reg_write(client, LANESEL, 0x00);
+
+       reg_write(client, GROUPED_PARAMETER_HOLD, 0x00);        /* off */
+
+       return 0;
+}
+
+static int imx074_probe(struct i2c_client *client,
+                       const struct i2c_device_id *did)
+{
+       struct imx074 *priv;
+       struct soc_camera_device *icd = client->dev.platform_data;
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       struct soc_camera_link *icl;
+       int ret;
+
+       if (!icd) {
+               dev_err(&client->dev, "IMX074: missing soc-camera data!\n");
+               return -EINVAL;
+       }
+
+       icl = to_soc_camera_link(icd);
+       if (!icl) {
+               dev_err(&client->dev, "IMX074: missing platform data!\n");
+               return -EINVAL;
+       }
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+               dev_warn(&adapter->dev,
+                        "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
+               return -EIO;
+       }
+
+       priv = kzalloc(sizeof(struct imx074), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       v4l2_i2c_subdev_init(&priv->subdev, client, &imx074_subdev_ops);
+
+       icd->ops        = &imx074_ops;
+       priv->fmt       = &imx074_colour_fmts[0];
+
+       ret = imx074_video_probe(icd, client);
+       if (ret < 0) {
+               icd->ops = NULL;
+               i2c_set_clientdata(client, NULL);
+               kfree(priv);
+               return ret;
+       }
+
+       return ret;
+}
+
+static int imx074_remove(struct i2c_client *client)
+{
+       struct imx074 *priv = to_imx074(client);
+       struct soc_camera_device *icd = client->dev.platform_data;
+       struct soc_camera_link *icl = to_soc_camera_link(icd);
+
+       icd->ops = NULL;
+       if (icl->free_bus)
+               icl->free_bus(icl);
+       i2c_set_clientdata(client, NULL);
+       client->driver = NULL;
+       kfree(priv);
+
+       return 0;
+}
+
+static const struct i2c_device_id imx074_id[] = {
+       { "imx074", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, imx074_id);
+
+static struct i2c_driver imx074_i2c_driver = {
+       .driver = {
+               .name = "imx074",
+       },
+       .probe          = imx074_probe,
+       .remove         = imx074_remove,
+       .id_table       = imx074_id,
+};
+
+static int __init imx074_mod_init(void)
+{
+       return i2c_add_driver(&imx074_i2c_driver);
+}
+
+static void __exit imx074_mod_exit(void)
+{
+       i2c_del_driver(&imx074_i2c_driver);
+}
+
+module_init(imx074_mod_init);
+module_exit(imx074_mod_exit);
+
+MODULE_DESCRIPTION("Sony IMX074 Camera driver");
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_LICENSE("GPL v2");