| /* |
| * 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/v4l2-mediabus.h> |
| #include <linux/slab.h> |
| #include <linux/videodev2.h> |
| #include <linux/module.h> |
| |
| #include <media/soc_camera.h> |
| #include <media/v4l2-async.h> |
| #include <media/v4l2-clk.h> |
| #include <media/v4l2-subdev.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 { |
| u32 code; |
| enum v4l2_colorspace colorspace; |
| }; |
| |
| struct imx074 { |
| struct v4l2_subdev subdev; |
| const struct imx074_datafmt *fmt; |
| struct v4l2_clk *clk; |
| }; |
| |
| static const struct imx074_datafmt imx074_colour_fmts[] = { |
| {MEDIA_BUS_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(u32 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_set_fmt(struct v4l2_subdev *sd, |
| struct v4l2_subdev_pad_config *cfg, |
| struct v4l2_subdev_format *format) |
| { |
| struct v4l2_mbus_framefmt *mf = &format->format; |
| const struct imx074_datafmt *fmt = imx074_find_datafmt(mf->code); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct imx074 *priv = to_imx074(client); |
| |
| if (format->pad) |
| return -EINVAL; |
| |
| dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code); |
| |
| if (!fmt) { |
| /* MIPI CSI could have changed the format, double-check */ |
| if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) |
| return -EINVAL; |
| 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; |
| |
| if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) |
| priv->fmt = imx074_find_datafmt(mf->code); |
| else |
| cfg->try_fmt = *mf; |
| |
| return 0; |
| } |
| |
| static int imx074_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 imx074 *priv = to_imx074(client); |
| |
| const struct imx074_datafmt *fmt = priv->fmt; |
| |
| if (format->pad) |
| return -EINVAL; |
| |
| 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_get_selection(struct v4l2_subdev *sd, |
| struct v4l2_subdev_pad_config *cfg, |
| struct v4l2_subdev_selection *sel) |
| { |
| if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) |
| return -EINVAL; |
| |
| sel->r.left = 0; |
| sel->r.top = 0; |
| sel->r.width = IMX074_WIDTH; |
| sel->r.height = IMX074_HEIGHT; |
| |
| switch (sel->target) { |
| case V4L2_SEL_TGT_CROP_BOUNDS: |
| case V4L2_SEL_TGT_CROP_DEFAULT: |
| case V4L2_SEL_TGT_CROP: |
| return 0; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int imx074_enum_mbus_code(struct v4l2_subdev *sd, |
| struct v4l2_subdev_pad_config *cfg, |
| struct v4l2_subdev_mbus_code_enum *code) |
| { |
| if (code->pad || |
| (unsigned int)code->index >= ARRAY_SIZE(imx074_colour_fmts)) |
| return -EINVAL; |
| |
| code->code = imx074_colour_fmts[code->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_s_power(struct v4l2_subdev *sd, int on) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); |
| struct imx074 *priv = to_imx074(client); |
| |
| return soc_camera_set_power(&client->dev, ssdd, priv->clk, on); |
| } |
| |
| static int imx074_g_mbus_config(struct v4l2_subdev *sd, |
| struct v4l2_mbus_config *cfg) |
| { |
| cfg->type = V4L2_MBUS_CSI2; |
| cfg->flags = V4L2_MBUS_CSI2_2_LANE | |
| V4L2_MBUS_CSI2_CHANNEL_0 | |
| V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; |
| |
| return 0; |
| } |
| |
| static struct v4l2_subdev_video_ops imx074_subdev_video_ops = { |
| .s_stream = imx074_s_stream, |
| .g_mbus_config = imx074_g_mbus_config, |
| }; |
| |
| static struct v4l2_subdev_core_ops imx074_subdev_core_ops = { |
| .s_power = imx074_s_power, |
| }; |
| |
| static const struct v4l2_subdev_pad_ops imx074_subdev_pad_ops = { |
| .enum_mbus_code = imx074_enum_mbus_code, |
| .get_selection = imx074_get_selection, |
| .get_fmt = imx074_get_fmt, |
| .set_fmt = imx074_set_fmt, |
| }; |
| |
| static struct v4l2_subdev_ops imx074_subdev_ops = { |
| .core = &imx074_subdev_core_ops, |
| .video = &imx074_subdev_video_ops, |
| .pad = &imx074_subdev_pad_ops, |
| }; |
| |
| static int imx074_video_probe(struct i2c_client *client) |
| { |
| struct v4l2_subdev *subdev = i2c_get_clientdata(client); |
| int ret; |
| u16 id; |
| |
| ret = imx074_s_power(subdev, 1); |
| if (ret < 0) |
| return ret; |
| |
| /* Read sensor Model ID */ |
| ret = reg_read(client, 0); |
| if (ret < 0) |
| goto done; |
| |
| id = ret << 8; |
| |
| ret = reg_read(client, 1); |
| if (ret < 0) |
| goto done; |
| |
| id |= ret; |
| |
| dev_info(&client->dev, "Chip ID 0x%04x detected\n", id); |
| |
| if (id != 0x74) { |
| ret = -ENODEV; |
| goto done; |
| } |
| |
| /* 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 */ |
| |
| ret = 0; |
| |
| done: |
| imx074_s_power(subdev, 0); |
| return ret; |
| } |
| |
| static int imx074_probe(struct i2c_client *client, |
| const struct i2c_device_id *did) |
| { |
| struct imx074 *priv; |
| struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); |
| struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); |
| int ret; |
| |
| if (!ssdd) { |
| 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 = devm_kzalloc(&client->dev, sizeof(struct imx074), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| v4l2_i2c_subdev_init(&priv->subdev, client, &imx074_subdev_ops); |
| |
| priv->fmt = &imx074_colour_fmts[0]; |
| |
| priv->clk = v4l2_clk_get(&client->dev, "mclk"); |
| if (IS_ERR(priv->clk)) { |
| dev_info(&client->dev, "Error %ld getting clock\n", PTR_ERR(priv->clk)); |
| return -EPROBE_DEFER; |
| } |
| |
| ret = soc_camera_power_init(&client->dev, ssdd); |
| if (ret < 0) |
| goto epwrinit; |
| |
| ret = imx074_video_probe(client); |
| if (ret < 0) |
| goto eprobe; |
| |
| ret = v4l2_async_register_subdev(&priv->subdev); |
| if (!ret) |
| return 0; |
| |
| epwrinit: |
| eprobe: |
| v4l2_clk_put(priv->clk); |
| return ret; |
| } |
| |
| static int imx074_remove(struct i2c_client *client) |
| { |
| struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); |
| struct imx074 *priv = to_imx074(client); |
| |
| v4l2_async_unregister_subdev(&priv->subdev); |
| v4l2_clk_put(priv->clk); |
| |
| if (ssdd->free_bus) |
| ssdd->free_bus(ssdd); |
| |
| 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, |
| }; |
| |
| module_i2c_driver(imx074_i2c_driver); |
| |
| MODULE_DESCRIPTION("Sony IMX074 Camera driver"); |
| MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); |
| MODULE_LICENSE("GPL v2"); |