mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 23:28:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
87
drivers/media/i2c/soc_camera/Kconfig
Normal file
87
drivers/media/i2c/soc_camera/Kconfig
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
comment "soc_camera sensor drivers"
|
||||
|
||||
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
|
||||
help
|
||||
This driver supports MT9M001 cameras from Micron, monochrome
|
||||
and colour models.
|
||||
|
||||
config SOC_CAMERA_MT9M111
|
||||
tristate "mt9m111, mt9m112 and mt9m131 support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This driver supports MT9M111, MT9M112 and MT9M131 cameras from
|
||||
Micron/Aptina
|
||||
|
||||
config SOC_CAMERA_MT9T031
|
||||
tristate "mt9t031 support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This driver supports MT9T031 cameras from Micron.
|
||||
|
||||
config SOC_CAMERA_MT9T112
|
||||
tristate "mt9t112 support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This driver supports MT9T112 cameras from Aptina.
|
||||
|
||||
config SOC_CAMERA_MT9V022
|
||||
tristate "mt9v022 and mt9v024 support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This driver supports MT9V022 cameras from Micron
|
||||
|
||||
config SOC_CAMERA_OV2640
|
||||
tristate "ov2640 camera support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a ov2640 camera driver
|
||||
|
||||
config SOC_CAMERA_OV5642
|
||||
tristate "ov5642 camera support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a V4L2 camera driver for the OmniVision OV5642 sensor
|
||||
|
||||
config SOC_CAMERA_OV6650
|
||||
tristate "ov6650 sensor support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
---help---
|
||||
This is a V4L2 SoC camera driver for the OmniVision OV6650 sensor
|
||||
|
||||
config SOC_CAMERA_OV772X
|
||||
tristate "ov772x camera support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a ov772x camera driver
|
||||
|
||||
config SOC_CAMERA_OV9640
|
||||
tristate "ov9640 camera support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a ov9640 camera driver
|
||||
|
||||
config SOC_CAMERA_OV9740
|
||||
tristate "ov9740 camera support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a ov9740 camera driver
|
||||
|
||||
config SOC_CAMERA_RJ54N1
|
||||
tristate "rj54n1cb0c support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a rj54n1cb0c video driver
|
||||
|
||||
config SOC_CAMERA_TW9910
|
||||
tristate "tw9910 support"
|
||||
depends on SOC_CAMERA && I2C
|
||||
help
|
||||
This is a tw9910 video driver
|
||||
14
drivers/media/i2c/soc_camera/Makefile
Normal file
14
drivers/media/i2c/soc_camera/Makefile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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
|
||||
obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o
|
||||
obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o
|
||||
obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o
|
||||
obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o
|
||||
obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o
|
||||
obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o
|
||||
obj-$(CONFIG_SOC_CAMERA_OV9640) += ov9640.o
|
||||
obj-$(CONFIG_SOC_CAMERA_OV9740) += ov9740.o
|
||||
obj-$(CONFIG_SOC_CAMERA_RJ54N1) += rj54n1cb0c.o
|
||||
obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o
|
||||
497
drivers/media/i2c/soc_camera/imx074.c
Normal file
497
drivers/media/i2c/soc_camera/imx074.c
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
* 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 {
|
||||
enum v4l2_mbus_pixelcode 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[] = {
|
||||
{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_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,
|
||||
.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,
|
||||
.g_mbus_config = imx074_g_mbus_config,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_core_ops imx074_subdev_core_ops = {
|
||||
.s_power = imx074_s_power,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_ops imx074_subdev_ops = {
|
||||
.core = &imx074_subdev_core_ops,
|
||||
.video = &imx074_subdev_video_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");
|
||||
735
drivers/media/i2c/soc_camera/mt9m001.c
Normal file
735
drivers/media/i2c/soc_camera/mt9m001.c
Normal file
|
|
@ -0,0 +1,735 @@
|
|||
/*
|
||||
* Driver for MT9M001 CMOS Image Sensor from Micron
|
||||
*
|
||||
* Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
|
||||
*
|
||||
* 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/videodev2.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <media/soc_camera.h>
|
||||
#include <media/soc_mediabus.h>
|
||||
#include <media/v4l2-clk.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
|
||||
/*
|
||||
* mt9m001 i2c address 0x5d
|
||||
* The platform has to define struct i2c_board_info objects and link to them
|
||||
* from struct soc_camera_host_desc
|
||||
*/
|
||||
|
||||
/* mt9m001 selected register addresses */
|
||||
#define MT9M001_CHIP_VERSION 0x00
|
||||
#define MT9M001_ROW_START 0x01
|
||||
#define MT9M001_COLUMN_START 0x02
|
||||
#define MT9M001_WINDOW_HEIGHT 0x03
|
||||
#define MT9M001_WINDOW_WIDTH 0x04
|
||||
#define MT9M001_HORIZONTAL_BLANKING 0x05
|
||||
#define MT9M001_VERTICAL_BLANKING 0x06
|
||||
#define MT9M001_OUTPUT_CONTROL 0x07
|
||||
#define MT9M001_SHUTTER_WIDTH 0x09
|
||||
#define MT9M001_FRAME_RESTART 0x0b
|
||||
#define MT9M001_SHUTTER_DELAY 0x0c
|
||||
#define MT9M001_RESET 0x0d
|
||||
#define MT9M001_READ_OPTIONS1 0x1e
|
||||
#define MT9M001_READ_OPTIONS2 0x20
|
||||
#define MT9M001_GLOBAL_GAIN 0x35
|
||||
#define MT9M001_CHIP_ENABLE 0xF1
|
||||
|
||||
#define MT9M001_MAX_WIDTH 1280
|
||||
#define MT9M001_MAX_HEIGHT 1024
|
||||
#define MT9M001_MIN_WIDTH 48
|
||||
#define MT9M001_MIN_HEIGHT 32
|
||||
#define MT9M001_COLUMN_SKIP 20
|
||||
#define MT9M001_ROW_SKIP 12
|
||||
|
||||
/* MT9M001 has only one fixed colorspace per pixelcode */
|
||||
struct mt9m001_datafmt {
|
||||
enum v4l2_mbus_pixelcode code;
|
||||
enum v4l2_colorspace colorspace;
|
||||
};
|
||||
|
||||
/* Find a data format by a pixel code in an array */
|
||||
static const struct mt9m001_datafmt *mt9m001_find_datafmt(
|
||||
enum v4l2_mbus_pixelcode code, const struct mt9m001_datafmt *fmt,
|
||||
int n)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
if (fmt[i].code == code)
|
||||
return fmt + i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct mt9m001_datafmt mt9m001_colour_fmts[] = {
|
||||
/*
|
||||
* Order important: first natively supported,
|
||||
* second supported with a GPIO extender
|
||||
*/
|
||||
{V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB},
|
||||
{V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB},
|
||||
};
|
||||
|
||||
static const struct mt9m001_datafmt mt9m001_monochrome_fmts[] = {
|
||||
/* Order important - see above */
|
||||
{V4L2_MBUS_FMT_Y10_1X10, V4L2_COLORSPACE_JPEG},
|
||||
{V4L2_MBUS_FMT_Y8_1X8, V4L2_COLORSPACE_JPEG},
|
||||
};
|
||||
|
||||
struct mt9m001 {
|
||||
struct v4l2_subdev subdev;
|
||||
struct v4l2_ctrl_handler hdl;
|
||||
struct {
|
||||
/* exposure/auto-exposure cluster */
|
||||
struct v4l2_ctrl *autoexposure;
|
||||
struct v4l2_ctrl *exposure;
|
||||
};
|
||||
struct v4l2_rect rect; /* Sensor window */
|
||||
struct v4l2_clk *clk;
|
||||
const struct mt9m001_datafmt *fmt;
|
||||
const struct mt9m001_datafmt *fmts;
|
||||
int num_fmts;
|
||||
unsigned int total_h;
|
||||
unsigned short y_skip_top; /* Lines to skip at the top */
|
||||
};
|
||||
|
||||
static struct mt9m001 *to_mt9m001(const struct i2c_client *client)
|
||||
{
|
||||
return container_of(i2c_get_clientdata(client), struct mt9m001, subdev);
|
||||
}
|
||||
|
||||
static int reg_read(struct i2c_client *client, const u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_word_swapped(client, reg);
|
||||
}
|
||||
|
||||
static int reg_write(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
return i2c_smbus_write_word_swapped(client, reg, data);
|
||||
}
|
||||
|
||||
static int reg_set(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return reg_write(client, reg, ret | data);
|
||||
}
|
||||
|
||||
static int reg_clear(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return reg_write(client, reg, ret & ~data);
|
||||
}
|
||||
|
||||
static int mt9m001_init(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dev_dbg(&client->dev, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* We don't know, whether platform provides reset, issue a soft reset
|
||||
* too. This returns all registers to their default values.
|
||||
*/
|
||||
ret = reg_write(client, MT9M001_RESET, 1);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_RESET, 0);
|
||||
|
||||
/* Disable chip, synchronous option update */
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_OUTPUT_CONTROL, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9m001_s_stream(struct v4l2_subdev *sd, int enable)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
/* Switch to master "normal" mode or stop sensor readout */
|
||||
if (reg_write(client, MT9M001_OUTPUT_CONTROL, enable ? 2 : 0) < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
struct v4l2_rect rect = a->c;
|
||||
int ret;
|
||||
const u16 hblank = 9, vblank = 25;
|
||||
|
||||
if (mt9m001->fmts == mt9m001_colour_fmts)
|
||||
/*
|
||||
* Bayer format - even number of rows for simplicity,
|
||||
* but let the user play with the top row.
|
||||
*/
|
||||
rect.height = ALIGN(rect.height, 2);
|
||||
|
||||
/* Datasheet requirement: see register description */
|
||||
rect.width = ALIGN(rect.width, 2);
|
||||
rect.left = ALIGN(rect.left, 2);
|
||||
|
||||
soc_camera_limit_side(&rect.left, &rect.width,
|
||||
MT9M001_COLUMN_SKIP, MT9M001_MIN_WIDTH, MT9M001_MAX_WIDTH);
|
||||
|
||||
soc_camera_limit_side(&rect.top, &rect.height,
|
||||
MT9M001_ROW_SKIP, MT9M001_MIN_HEIGHT, MT9M001_MAX_HEIGHT);
|
||||
|
||||
mt9m001->total_h = rect.height + mt9m001->y_skip_top + vblank;
|
||||
|
||||
/* Blanking and start values - default... */
|
||||
ret = reg_write(client, MT9M001_HORIZONTAL_BLANKING, hblank);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_VERTICAL_BLANKING, vblank);
|
||||
|
||||
/*
|
||||
* The caller provides a supported format, as verified per
|
||||
* call to .try_mbus_fmt()
|
||||
*/
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_COLUMN_START, rect.left);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_ROW_START, rect.top);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_WINDOW_WIDTH, rect.width - 1);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9M001_WINDOW_HEIGHT,
|
||||
rect.height + mt9m001->y_skip_top - 1);
|
||||
if (!ret && v4l2_ctrl_g_ctrl(mt9m001->autoexposure) == V4L2_EXPOSURE_AUTO)
|
||||
ret = reg_write(client, MT9M001_SHUTTER_WIDTH, mt9m001->total_h);
|
||||
|
||||
if (!ret)
|
||||
mt9m001->rect = rect;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9m001_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
|
||||
a->c = mt9m001->rect;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
|
||||
{
|
||||
a->bounds.left = MT9M001_COLUMN_SKIP;
|
||||
a->bounds.top = MT9M001_ROW_SKIP;
|
||||
a->bounds.width = MT9M001_MAX_WIDTH;
|
||||
a->bounds.height = MT9M001_MAX_HEIGHT;
|
||||
a->defrect = a->bounds;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
a->pixelaspect.numerator = 1;
|
||||
a->pixelaspect.denominator = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_g_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
|
||||
mf->width = mt9m001->rect.width;
|
||||
mf->height = mt9m001->rect.height;
|
||||
mf->code = mt9m001->fmt->code;
|
||||
mf->colorspace = mt9m001->fmt->colorspace;
|
||||
mf->field = V4L2_FIELD_NONE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_s_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
struct v4l2_crop a = {
|
||||
.c = {
|
||||
.left = mt9m001->rect.left,
|
||||
.top = mt9m001->rect.top,
|
||||
.width = mf->width,
|
||||
.height = mf->height,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
/* No support for scaling so far, just crop. TODO: use skipping */
|
||||
ret = mt9m001_s_crop(sd, &a);
|
||||
if (!ret) {
|
||||
mf->width = mt9m001->rect.width;
|
||||
mf->height = mt9m001->rect.height;
|
||||
mt9m001->fmt = mt9m001_find_datafmt(mf->code,
|
||||
mt9m001->fmts, mt9m001->num_fmts);
|
||||
mf->colorspace = mt9m001->fmt->colorspace;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9m001_try_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
const struct mt9m001_datafmt *fmt;
|
||||
|
||||
v4l_bound_align_image(&mf->width, MT9M001_MIN_WIDTH,
|
||||
MT9M001_MAX_WIDTH, 1,
|
||||
&mf->height, MT9M001_MIN_HEIGHT + mt9m001->y_skip_top,
|
||||
MT9M001_MAX_HEIGHT + mt9m001->y_skip_top, 0, 0);
|
||||
|
||||
if (mt9m001->fmts == mt9m001_colour_fmts)
|
||||
mf->height = ALIGN(mf->height - 1, 2);
|
||||
|
||||
fmt = mt9m001_find_datafmt(mf->code, mt9m001->fmts,
|
||||
mt9m001->num_fmts);
|
||||
if (!fmt) {
|
||||
fmt = mt9m001->fmt;
|
||||
mf->code = fmt->code;
|
||||
}
|
||||
|
||||
mf->colorspace = fmt->colorspace;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
static int mt9m001_g_register(struct v4l2_subdev *sd,
|
||||
struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
reg->size = 2;
|
||||
reg->val = reg_read(client, reg->reg);
|
||||
|
||||
if (reg->val > 0xffff)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_s_register(struct v4l2_subdev *sd,
|
||||
const struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
if (reg_write(client, reg->reg, reg->val) < 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int mt9m001_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 mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
|
||||
return soc_camera_set_power(&client->dev, ssdd, mt9m001->clk, on);
|
||||
}
|
||||
|
||||
static int mt9m001_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mt9m001 *mt9m001 = container_of(ctrl->handler,
|
||||
struct mt9m001, hdl);
|
||||
s32 min, max;
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_EXPOSURE_AUTO:
|
||||
min = mt9m001->exposure->minimum;
|
||||
max = mt9m001->exposure->maximum;
|
||||
mt9m001->exposure->val =
|
||||
(524 + (mt9m001->total_h - 1) * (max - min)) / 1048 + min;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_s_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mt9m001 *mt9m001 = container_of(ctrl->handler,
|
||||
struct mt9m001, hdl);
|
||||
struct v4l2_subdev *sd = &mt9m001->subdev;
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct v4l2_ctrl *exp = mt9m001->exposure;
|
||||
int data;
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_VFLIP:
|
||||
if (ctrl->val)
|
||||
data = reg_set(client, MT9M001_READ_OPTIONS2, 0x8000);
|
||||
else
|
||||
data = reg_clear(client, MT9M001_READ_OPTIONS2, 0x8000);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
|
||||
case V4L2_CID_GAIN:
|
||||
/* See Datasheet Table 7, Gain settings. */
|
||||
if (ctrl->val <= ctrl->default_value) {
|
||||
/* Pack it into 0..1 step 0.125, register values 0..8 */
|
||||
unsigned long range = ctrl->default_value - ctrl->minimum;
|
||||
data = ((ctrl->val - (s32)ctrl->minimum) * 8 + range / 2) / range;
|
||||
|
||||
dev_dbg(&client->dev, "Setting gain %d\n", data);
|
||||
data = reg_write(client, MT9M001_GLOBAL_GAIN, data);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
} else {
|
||||
/* Pack it into 1.125..15 variable step, register values 9..67 */
|
||||
/* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */
|
||||
unsigned long range = ctrl->maximum - ctrl->default_value - 1;
|
||||
unsigned long gain = ((ctrl->val - (s32)ctrl->default_value - 1) *
|
||||
111 + range / 2) / range + 9;
|
||||
|
||||
if (gain <= 32)
|
||||
data = gain;
|
||||
else if (gain <= 64)
|
||||
data = ((gain - 32) * 16 + 16) / 32 + 80;
|
||||
else
|
||||
data = ((gain - 64) * 7 + 28) / 56 + 96;
|
||||
|
||||
dev_dbg(&client->dev, "Setting gain from %d to %d\n",
|
||||
reg_read(client, MT9M001_GLOBAL_GAIN), data);
|
||||
data = reg_write(client, MT9M001_GLOBAL_GAIN, data);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case V4L2_CID_EXPOSURE_AUTO:
|
||||
if (ctrl->val == V4L2_EXPOSURE_MANUAL) {
|
||||
unsigned long range = exp->maximum - exp->minimum;
|
||||
unsigned long shutter = ((exp->val - (s32)exp->minimum) * 1048 +
|
||||
range / 2) / range + 1;
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"Setting shutter width from %d to %lu\n",
|
||||
reg_read(client, MT9M001_SHUTTER_WIDTH), shutter);
|
||||
if (reg_write(client, MT9M001_SHUTTER_WIDTH, shutter) < 0)
|
||||
return -EIO;
|
||||
} else {
|
||||
const u16 vblank = 25;
|
||||
|
||||
mt9m001->total_h = mt9m001->rect.height +
|
||||
mt9m001->y_skip_top + vblank;
|
||||
if (reg_write(client, MT9M001_SHUTTER_WIDTH, mt9m001->total_h) < 0)
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interface active, can use i2c. If it fails, it can indeed mean, that
|
||||
* this wasn't our capture interface, so, we wait for the right one
|
||||
*/
|
||||
static int mt9m001_video_probe(struct soc_camera_subdev_desc *ssdd,
|
||||
struct i2c_client *client)
|
||||
{
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
s32 data;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
ret = mt9m001_s_power(&mt9m001->subdev, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Enable the chip */
|
||||
data = reg_write(client, MT9M001_CHIP_ENABLE, 1);
|
||||
dev_dbg(&client->dev, "write: %d\n", data);
|
||||
|
||||
/* Read out the chip version register */
|
||||
data = reg_read(client, MT9M001_CHIP_VERSION);
|
||||
|
||||
/* must be 0x8411 or 0x8421 for colour sensor and 8431 for bw */
|
||||
switch (data) {
|
||||
case 0x8411:
|
||||
case 0x8421:
|
||||
mt9m001->fmts = mt9m001_colour_fmts;
|
||||
break;
|
||||
case 0x8431:
|
||||
mt9m001->fmts = mt9m001_monochrome_fmts;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"No MT9M001 chip detected, register read %x\n", data);
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
mt9m001->num_fmts = 0;
|
||||
|
||||
/*
|
||||
* This is a 10bit sensor, so by default we only allow 10bit.
|
||||
* The platform may support different bus widths due to
|
||||
* different routing of the data lines.
|
||||
*/
|
||||
if (ssdd->query_bus_param)
|
||||
flags = ssdd->query_bus_param(ssdd);
|
||||
else
|
||||
flags = SOCAM_DATAWIDTH_10;
|
||||
|
||||
if (flags & SOCAM_DATAWIDTH_10)
|
||||
mt9m001->num_fmts++;
|
||||
else
|
||||
mt9m001->fmts++;
|
||||
|
||||
if (flags & SOCAM_DATAWIDTH_8)
|
||||
mt9m001->num_fmts++;
|
||||
|
||||
mt9m001->fmt = &mt9m001->fmts[0];
|
||||
|
||||
dev_info(&client->dev, "Detected a MT9M001 chip ID %x (%s)\n", data,
|
||||
data == 0x8431 ? "C12STM" : "C12ST");
|
||||
|
||||
ret = mt9m001_init(client);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to initialise the camera\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* mt9m001_init() has reset the chip, returning registers to defaults */
|
||||
ret = v4l2_ctrl_handler_setup(&mt9m001->hdl);
|
||||
|
||||
done:
|
||||
mt9m001_s_power(&mt9m001->subdev, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mt9m001_video_remove(struct soc_camera_subdev_desc *ssdd)
|
||||
{
|
||||
if (ssdd->free_bus)
|
||||
ssdd->free_bus(ssdd);
|
||||
}
|
||||
|
||||
static int mt9m001_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
|
||||
*lines = mt9m001->y_skip_top;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ctrl_ops mt9m001_ctrl_ops = {
|
||||
.g_volatile_ctrl = mt9m001_g_volatile_ctrl,
|
||||
.s_ctrl = mt9m001_s_ctrl,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_core_ops mt9m001_subdev_core_ops = {
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
.g_register = mt9m001_g_register,
|
||||
.s_register = mt9m001_s_register,
|
||||
#endif
|
||||
.s_power = mt9m001_s_power,
|
||||
};
|
||||
|
||||
static int mt9m001_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
|
||||
enum v4l2_mbus_pixelcode *code)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
|
||||
if (index >= mt9m001->num_fmts)
|
||||
return -EINVAL;
|
||||
|
||||
*code = mt9m001->fmts[index].code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_g_mbus_config(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
/* MT9M001 has all capture_format parameters fixed */
|
||||
cfg->flags = V4L2_MBUS_PCLK_SAMPLE_FALLING |
|
||||
V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH |
|
||||
V4L2_MBUS_DATA_ACTIVE_HIGH | V4L2_MBUS_MASTER;
|
||||
cfg->type = V4L2_MBUS_PARALLEL;
|
||||
cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9m001_s_mbus_config(struct v4l2_subdev *sd,
|
||||
const struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
const struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
unsigned int bps = soc_mbus_get_fmtdesc(mt9m001->fmt->code)->bits_per_sample;
|
||||
|
||||
if (ssdd->set_bus_param)
|
||||
return ssdd->set_bus_param(ssdd, 1 << (bps - 1));
|
||||
|
||||
/*
|
||||
* Without board specific bus width settings we only support the
|
||||
* sensors native bus width
|
||||
*/
|
||||
return bps == 10 ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static struct v4l2_subdev_video_ops mt9m001_subdev_video_ops = {
|
||||
.s_stream = mt9m001_s_stream,
|
||||
.s_mbus_fmt = mt9m001_s_fmt,
|
||||
.g_mbus_fmt = mt9m001_g_fmt,
|
||||
.try_mbus_fmt = mt9m001_try_fmt,
|
||||
.s_crop = mt9m001_s_crop,
|
||||
.g_crop = mt9m001_g_crop,
|
||||
.cropcap = mt9m001_cropcap,
|
||||
.enum_mbus_fmt = mt9m001_enum_fmt,
|
||||
.g_mbus_config = mt9m001_g_mbus_config,
|
||||
.s_mbus_config = mt9m001_s_mbus_config,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_sensor_ops mt9m001_subdev_sensor_ops = {
|
||||
.g_skip_top_lines = mt9m001_g_skip_top_lines,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_ops mt9m001_subdev_ops = {
|
||||
.core = &mt9m001_subdev_core_ops,
|
||||
.video = &mt9m001_subdev_video_ops,
|
||||
.sensor = &mt9m001_subdev_sensor_ops,
|
||||
};
|
||||
|
||||
static int mt9m001_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *did)
|
||||
{
|
||||
struct mt9m001 *mt9m001;
|
||||
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, "MT9M001 driver needs platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
|
||||
dev_warn(&adapter->dev,
|
||||
"I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mt9m001 = devm_kzalloc(&client->dev, sizeof(struct mt9m001), GFP_KERNEL);
|
||||
if (!mt9m001)
|
||||
return -ENOMEM;
|
||||
|
||||
v4l2_i2c_subdev_init(&mt9m001->subdev, client, &mt9m001_subdev_ops);
|
||||
v4l2_ctrl_handler_init(&mt9m001->hdl, 4);
|
||||
v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops,
|
||||
V4L2_CID_VFLIP, 0, 1, 1, 0);
|
||||
v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops,
|
||||
V4L2_CID_GAIN, 0, 127, 1, 64);
|
||||
mt9m001->exposure = v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops,
|
||||
V4L2_CID_EXPOSURE, 1, 255, 1, 255);
|
||||
/*
|
||||
* Simulated autoexposure. If enabled, we calculate shutter width
|
||||
* ourselves in the driver based on vertical blanking and frame width
|
||||
*/
|
||||
mt9m001->autoexposure = v4l2_ctrl_new_std_menu(&mt9m001->hdl,
|
||||
&mt9m001_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0,
|
||||
V4L2_EXPOSURE_AUTO);
|
||||
mt9m001->subdev.ctrl_handler = &mt9m001->hdl;
|
||||
if (mt9m001->hdl.error)
|
||||
return mt9m001->hdl.error;
|
||||
|
||||
v4l2_ctrl_auto_cluster(2, &mt9m001->autoexposure,
|
||||
V4L2_EXPOSURE_MANUAL, true);
|
||||
|
||||
/* Second stage probe - when a capture adapter is there */
|
||||
mt9m001->y_skip_top = 0;
|
||||
mt9m001->rect.left = MT9M001_COLUMN_SKIP;
|
||||
mt9m001->rect.top = MT9M001_ROW_SKIP;
|
||||
mt9m001->rect.width = MT9M001_MAX_WIDTH;
|
||||
mt9m001->rect.height = MT9M001_MAX_HEIGHT;
|
||||
|
||||
mt9m001->clk = v4l2_clk_get(&client->dev, "mclk");
|
||||
if (IS_ERR(mt9m001->clk)) {
|
||||
ret = PTR_ERR(mt9m001->clk);
|
||||
goto eclkget;
|
||||
}
|
||||
|
||||
ret = mt9m001_video_probe(ssdd, client);
|
||||
if (ret) {
|
||||
v4l2_clk_put(mt9m001->clk);
|
||||
eclkget:
|
||||
v4l2_ctrl_handler_free(&mt9m001->hdl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9m001_remove(struct i2c_client *client)
|
||||
{
|
||||
struct mt9m001 *mt9m001 = to_mt9m001(client);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
v4l2_clk_put(mt9m001->clk);
|
||||
v4l2_device_unregister_subdev(&mt9m001->subdev);
|
||||
v4l2_ctrl_handler_free(&mt9m001->hdl);
|
||||
mt9m001_video_remove(ssdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mt9m001_id[] = {
|
||||
{ "mt9m001", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mt9m001_id);
|
||||
|
||||
static struct i2c_driver mt9m001_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "mt9m001",
|
||||
},
|
||||
.probe = mt9m001_probe,
|
||||
.remove = mt9m001_remove,
|
||||
.id_table = mt9m001_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mt9m001_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Micron MT9M001 Camera driver");
|
||||
MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>");
|
||||
MODULE_LICENSE("GPL");
|
||||
1050
drivers/media/i2c/soc_camera/mt9m111.c
Normal file
1050
drivers/media/i2c/soc_camera/mt9m111.c
Normal file
File diff suppressed because it is too large
Load diff
840
drivers/media/i2c/soc_camera/mt9t031.c
Normal file
840
drivers/media/i2c/soc_camera/mt9t031.c
Normal file
|
|
@ -0,0 +1,840 @@
|
|||
/*
|
||||
* Driver for MT9T031 CMOS Image Sensor from Micron
|
||||
*
|
||||
* Copyright (C) 2008, Guennadi Liakhovetski, DENX Software Engineering <lg@denx.de>
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/v4l2-mediabus.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <media/soc_camera.h>
|
||||
#include <media/v4l2-clk.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
|
||||
/*
|
||||
* ATTENTION: this driver still cannot be used outside of the soc-camera
|
||||
* framework because of its PM implementation, using the video_device node.
|
||||
* If hardware becomes available for testing, alternative PM approaches shall
|
||||
* be considered and tested.
|
||||
*/
|
||||
|
||||
/*
|
||||
* mt9t031 i2c address 0x5d
|
||||
* The platform has to define struct i2c_board_info objects and link to them
|
||||
* from struct soc_camera_host_desc
|
||||
*/
|
||||
|
||||
/* mt9t031 selected register addresses */
|
||||
#define MT9T031_CHIP_VERSION 0x00
|
||||
#define MT9T031_ROW_START 0x01
|
||||
#define MT9T031_COLUMN_START 0x02
|
||||
#define MT9T031_WINDOW_HEIGHT 0x03
|
||||
#define MT9T031_WINDOW_WIDTH 0x04
|
||||
#define MT9T031_HORIZONTAL_BLANKING 0x05
|
||||
#define MT9T031_VERTICAL_BLANKING 0x06
|
||||
#define MT9T031_OUTPUT_CONTROL 0x07
|
||||
#define MT9T031_SHUTTER_WIDTH_UPPER 0x08
|
||||
#define MT9T031_SHUTTER_WIDTH 0x09
|
||||
#define MT9T031_PIXEL_CLOCK_CONTROL 0x0a
|
||||
#define MT9T031_FRAME_RESTART 0x0b
|
||||
#define MT9T031_SHUTTER_DELAY 0x0c
|
||||
#define MT9T031_RESET 0x0d
|
||||
#define MT9T031_READ_MODE_1 0x1e
|
||||
#define MT9T031_READ_MODE_2 0x20
|
||||
#define MT9T031_READ_MODE_3 0x21
|
||||
#define MT9T031_ROW_ADDRESS_MODE 0x22
|
||||
#define MT9T031_COLUMN_ADDRESS_MODE 0x23
|
||||
#define MT9T031_GLOBAL_GAIN 0x35
|
||||
#define MT9T031_CHIP_ENABLE 0xF8
|
||||
|
||||
#define MT9T031_MAX_HEIGHT 1536
|
||||
#define MT9T031_MAX_WIDTH 2048
|
||||
#define MT9T031_MIN_HEIGHT 2
|
||||
#define MT9T031_MIN_WIDTH 18
|
||||
#define MT9T031_HORIZONTAL_BLANK 142
|
||||
#define MT9T031_VERTICAL_BLANK 25
|
||||
#define MT9T031_COLUMN_SKIP 32
|
||||
#define MT9T031_ROW_SKIP 20
|
||||
|
||||
struct mt9t031 {
|
||||
struct v4l2_subdev subdev;
|
||||
struct v4l2_ctrl_handler hdl;
|
||||
struct {
|
||||
/* exposure/auto-exposure cluster */
|
||||
struct v4l2_ctrl *autoexposure;
|
||||
struct v4l2_ctrl *exposure;
|
||||
};
|
||||
struct v4l2_rect rect; /* Sensor window */
|
||||
struct v4l2_clk *clk;
|
||||
u16 xskip;
|
||||
u16 yskip;
|
||||
unsigned int total_h;
|
||||
unsigned short y_skip_top; /* Lines to skip at the top */
|
||||
};
|
||||
|
||||
static struct mt9t031 *to_mt9t031(const struct i2c_client *client)
|
||||
{
|
||||
return container_of(i2c_get_clientdata(client), struct mt9t031, subdev);
|
||||
}
|
||||
|
||||
static int reg_read(struct i2c_client *client, const u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_word_swapped(client, reg);
|
||||
}
|
||||
|
||||
static int reg_write(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
return i2c_smbus_write_word_swapped(client, reg, data);
|
||||
}
|
||||
|
||||
static int reg_set(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return reg_write(client, reg, ret | data);
|
||||
}
|
||||
|
||||
static int reg_clear(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return reg_write(client, reg, ret & ~data);
|
||||
}
|
||||
|
||||
static int set_shutter(struct i2c_client *client, const u32 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_write(client, MT9T031_SHUTTER_WIDTH_UPPER, data >> 16);
|
||||
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_SHUTTER_WIDTH, data & 0xffff);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_shutter(struct i2c_client *client, u32 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, MT9T031_SHUTTER_WIDTH_UPPER);
|
||||
*data = ret << 16;
|
||||
|
||||
if (ret >= 0)
|
||||
ret = reg_read(client, MT9T031_SHUTTER_WIDTH);
|
||||
*data |= ret & 0xffff;
|
||||
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
static int mt9t031_idle(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Disable chip output, synchronous option update */
|
||||
ret = reg_write(client, MT9T031_RESET, 1);
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_RESET, 0);
|
||||
if (ret >= 0)
|
||||
ret = reg_clear(client, MT9T031_OUTPUT_CONTROL, 2);
|
||||
|
||||
return ret >= 0 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int mt9t031_s_stream(struct v4l2_subdev *sd, int enable)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
int ret;
|
||||
|
||||
if (enable)
|
||||
/* Switch to master "normal" mode */
|
||||
ret = reg_set(client, MT9T031_OUTPUT_CONTROL, 2);
|
||||
else
|
||||
/* Stop sensor readout */
|
||||
ret = reg_clear(client, MT9T031_OUTPUT_CONTROL, 2);
|
||||
|
||||
if (ret < 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* target must be _even_ */
|
||||
static u16 mt9t031_skip(s32 *source, s32 target, s32 max)
|
||||
{
|
||||
unsigned int skip;
|
||||
|
||||
if (*source < target + target / 2) {
|
||||
*source = target;
|
||||
return 1;
|
||||
}
|
||||
|
||||
skip = min(max, *source + target / 2) / target;
|
||||
if (skip > 8)
|
||||
skip = 8;
|
||||
*source = target * skip;
|
||||
|
||||
return skip;
|
||||
}
|
||||
|
||||
/* rect is the sensor rectangle, the caller guarantees parameter validity */
|
||||
static int mt9t031_set_params(struct i2c_client *client,
|
||||
struct v4l2_rect *rect, u16 xskip, u16 yskip)
|
||||
{
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
int ret;
|
||||
u16 xbin, ybin;
|
||||
const u16 hblank = MT9T031_HORIZONTAL_BLANK,
|
||||
vblank = MT9T031_VERTICAL_BLANK;
|
||||
|
||||
xbin = min(xskip, (u16)3);
|
||||
ybin = min(yskip, (u16)3);
|
||||
|
||||
/*
|
||||
* Could just do roundup(rect->left, [xy]bin * 2); but this is cheaper.
|
||||
* There is always a valid suitably aligned value. The worst case is
|
||||
* xbin = 3, width = 2048. Then we will start at 36, the last read out
|
||||
* pixel will be 2083, which is < 2085 - first black pixel.
|
||||
*
|
||||
* MT9T031 datasheet imposes window left border alignment, depending on
|
||||
* the selected xskip. Failing to conform to this requirement produces
|
||||
* dark horizontal stripes in the image. However, even obeying to this
|
||||
* requirement doesn't eliminate the stripes in all configurations. They
|
||||
* appear "locally reproducibly," but can differ between tests under
|
||||
* different lighting conditions.
|
||||
*/
|
||||
switch (xbin) {
|
||||
case 1:
|
||||
rect->left &= ~1;
|
||||
break;
|
||||
case 2:
|
||||
rect->left &= ~3;
|
||||
break;
|
||||
case 3:
|
||||
rect->left = rect->left > roundup(MT9T031_COLUMN_SKIP, 6) ?
|
||||
(rect->left / 6) * 6 : roundup(MT9T031_COLUMN_SKIP, 6);
|
||||
}
|
||||
|
||||
rect->top &= ~1;
|
||||
|
||||
dev_dbg(&client->dev, "skip %u:%u, rect %ux%u@%u:%u\n",
|
||||
xskip, yskip, rect->width, rect->height, rect->left, rect->top);
|
||||
|
||||
/* Disable register update, reconfigure atomically */
|
||||
ret = reg_set(client, MT9T031_OUTPUT_CONTROL, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Blanking and start values - default... */
|
||||
ret = reg_write(client, MT9T031_HORIZONTAL_BLANKING, hblank);
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_VERTICAL_BLANKING, vblank);
|
||||
|
||||
if (yskip != mt9t031->yskip || xskip != mt9t031->xskip) {
|
||||
/* Binning, skipping */
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_COLUMN_ADDRESS_MODE,
|
||||
((xbin - 1) << 4) | (xskip - 1));
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_ROW_ADDRESS_MODE,
|
||||
((ybin - 1) << 4) | (yskip - 1));
|
||||
}
|
||||
dev_dbg(&client->dev, "new physical left %u, top %u\n",
|
||||
rect->left, rect->top);
|
||||
|
||||
/*
|
||||
* The caller provides a supported format, as guaranteed by
|
||||
* .try_mbus_fmt(), soc_camera_s_crop() and soc_camera_cropcap()
|
||||
*/
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_COLUMN_START, rect->left);
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_ROW_START, rect->top);
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_WINDOW_WIDTH, rect->width - 1);
|
||||
if (ret >= 0)
|
||||
ret = reg_write(client, MT9T031_WINDOW_HEIGHT,
|
||||
rect->height + mt9t031->y_skip_top - 1);
|
||||
if (ret >= 0 && v4l2_ctrl_g_ctrl(mt9t031->autoexposure) == V4L2_EXPOSURE_AUTO) {
|
||||
mt9t031->total_h = rect->height + mt9t031->y_skip_top + vblank;
|
||||
|
||||
ret = set_shutter(client, mt9t031->total_h);
|
||||
}
|
||||
|
||||
/* Re-enable register update, commit all changes */
|
||||
if (ret >= 0)
|
||||
ret = reg_clear(client, MT9T031_OUTPUT_CONTROL, 1);
|
||||
|
||||
if (ret >= 0) {
|
||||
mt9t031->rect = *rect;
|
||||
mt9t031->xskip = xskip;
|
||||
mt9t031->yskip = yskip;
|
||||
}
|
||||
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
static int mt9t031_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a)
|
||||
{
|
||||
struct v4l2_rect rect = a->c;
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
|
||||
rect.width = ALIGN(rect.width, 2);
|
||||
rect.height = ALIGN(rect.height, 2);
|
||||
|
||||
soc_camera_limit_side(&rect.left, &rect.width,
|
||||
MT9T031_COLUMN_SKIP, MT9T031_MIN_WIDTH, MT9T031_MAX_WIDTH);
|
||||
|
||||
soc_camera_limit_side(&rect.top, &rect.height,
|
||||
MT9T031_ROW_SKIP, MT9T031_MIN_HEIGHT, MT9T031_MAX_HEIGHT);
|
||||
|
||||
return mt9t031_set_params(client, &rect, mt9t031->xskip, mt9t031->yskip);
|
||||
}
|
||||
|
||||
static int mt9t031_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
|
||||
a->c = mt9t031->rect;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
|
||||
{
|
||||
a->bounds.left = MT9T031_COLUMN_SKIP;
|
||||
a->bounds.top = MT9T031_ROW_SKIP;
|
||||
a->bounds.width = MT9T031_MAX_WIDTH;
|
||||
a->bounds.height = MT9T031_MAX_HEIGHT;
|
||||
a->defrect = a->bounds;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
a->pixelaspect.numerator = 1;
|
||||
a->pixelaspect.denominator = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_g_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
|
||||
mf->width = mt9t031->rect.width / mt9t031->xskip;
|
||||
mf->height = mt9t031->rect.height / mt9t031->yskip;
|
||||
mf->code = V4L2_MBUS_FMT_SBGGR10_1X10;
|
||||
mf->colorspace = V4L2_COLORSPACE_SRGB;
|
||||
mf->field = V4L2_FIELD_NONE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_s_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
u16 xskip, yskip;
|
||||
struct v4l2_rect rect = mt9t031->rect;
|
||||
|
||||
/*
|
||||
* try_fmt has put width and height within limits.
|
||||
* S_FMT: use binning and skipping for scaling
|
||||
*/
|
||||
xskip = mt9t031_skip(&rect.width, mf->width, MT9T031_MAX_WIDTH);
|
||||
yskip = mt9t031_skip(&rect.height, mf->height, MT9T031_MAX_HEIGHT);
|
||||
|
||||
mf->code = V4L2_MBUS_FMT_SBGGR10_1X10;
|
||||
mf->colorspace = V4L2_COLORSPACE_SRGB;
|
||||
|
||||
/* mt9t031_set_params() doesn't change width and height */
|
||||
return mt9t031_set_params(client, &rect, xskip, yskip);
|
||||
}
|
||||
|
||||
/*
|
||||
* If a user window larger than sensor window is requested, we'll increase the
|
||||
* sensor window.
|
||||
*/
|
||||
static int mt9t031_try_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
v4l_bound_align_image(
|
||||
&mf->width, MT9T031_MIN_WIDTH, MT9T031_MAX_WIDTH, 1,
|
||||
&mf->height, MT9T031_MIN_HEIGHT, MT9T031_MAX_HEIGHT, 1, 0);
|
||||
|
||||
mf->code = V4L2_MBUS_FMT_SBGGR10_1X10;
|
||||
mf->colorspace = V4L2_COLORSPACE_SRGB;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
static int mt9t031_g_register(struct v4l2_subdev *sd,
|
||||
struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
reg->size = 1;
|
||||
reg->val = reg_read(client, reg->reg);
|
||||
|
||||
if (reg->val > 0xffff)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_s_register(struct v4l2_subdev *sd,
|
||||
const struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
if (reg_write(client, reg->reg, reg->val) < 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int mt9t031_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mt9t031 *mt9t031 = container_of(ctrl->handler,
|
||||
struct mt9t031, hdl);
|
||||
const u32 shutter_max = MT9T031_MAX_HEIGHT + MT9T031_VERTICAL_BLANK;
|
||||
s32 min, max;
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_EXPOSURE_AUTO:
|
||||
min = mt9t031->exposure->minimum;
|
||||
max = mt9t031->exposure->maximum;
|
||||
mt9t031->exposure->val =
|
||||
(shutter_max / 2 + (mt9t031->total_h - 1) * (max - min))
|
||||
/ shutter_max + min;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_s_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mt9t031 *mt9t031 = container_of(ctrl->handler,
|
||||
struct mt9t031, hdl);
|
||||
struct v4l2_subdev *sd = &mt9t031->subdev;
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct v4l2_ctrl *exp = mt9t031->exposure;
|
||||
int data;
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_VFLIP:
|
||||
if (ctrl->val)
|
||||
data = reg_set(client, MT9T031_READ_MODE_2, 0x8000);
|
||||
else
|
||||
data = reg_clear(client, MT9T031_READ_MODE_2, 0x8000);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
case V4L2_CID_HFLIP:
|
||||
if (ctrl->val)
|
||||
data = reg_set(client, MT9T031_READ_MODE_2, 0x4000);
|
||||
else
|
||||
data = reg_clear(client, MT9T031_READ_MODE_2, 0x4000);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
case V4L2_CID_GAIN:
|
||||
/* See Datasheet Table 7, Gain settings. */
|
||||
if (ctrl->val <= ctrl->default_value) {
|
||||
/* Pack it into 0..1 step 0.125, register values 0..8 */
|
||||
unsigned long range = ctrl->default_value - ctrl->minimum;
|
||||
data = ((ctrl->val - (s32)ctrl->minimum) * 8 + range / 2) / range;
|
||||
|
||||
dev_dbg(&client->dev, "Setting gain %d\n", data);
|
||||
data = reg_write(client, MT9T031_GLOBAL_GAIN, data);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
} else {
|
||||
/* Pack it into 1.125..128 variable step, register values 9..0x7860 */
|
||||
/* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */
|
||||
unsigned long range = ctrl->maximum - ctrl->default_value - 1;
|
||||
/* calculated gain: map 65..127 to 9..1024 step 0.125 */
|
||||
unsigned long gain = ((ctrl->val - (s32)ctrl->default_value - 1) *
|
||||
1015 + range / 2) / range + 9;
|
||||
|
||||
if (gain <= 32) /* calculated gain 9..32 -> 9..32 */
|
||||
data = gain;
|
||||
else if (gain <= 64) /* calculated gain 33..64 -> 0x51..0x60 */
|
||||
data = ((gain - 32) * 16 + 16) / 32 + 80;
|
||||
else
|
||||
/* calculated gain 65..1024 -> (1..120) << 8 + 0x60 */
|
||||
data = (((gain - 64 + 7) * 32) & 0xff00) | 0x60;
|
||||
|
||||
dev_dbg(&client->dev, "Set gain from 0x%x to 0x%x\n",
|
||||
reg_read(client, MT9T031_GLOBAL_GAIN), data);
|
||||
data = reg_write(client, MT9T031_GLOBAL_GAIN, data);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case V4L2_CID_EXPOSURE_AUTO:
|
||||
if (ctrl->val == V4L2_EXPOSURE_MANUAL) {
|
||||
unsigned int range = exp->maximum - exp->minimum;
|
||||
unsigned int shutter = ((exp->val - (s32)exp->minimum) * 1048 +
|
||||
range / 2) / range + 1;
|
||||
u32 old;
|
||||
|
||||
get_shutter(client, &old);
|
||||
dev_dbg(&client->dev, "Set shutter from %u to %u\n",
|
||||
old, shutter);
|
||||
if (set_shutter(client, shutter) < 0)
|
||||
return -EIO;
|
||||
} else {
|
||||
const u16 vblank = MT9T031_VERTICAL_BLANK;
|
||||
mt9t031->total_h = mt9t031->rect.height +
|
||||
mt9t031->y_skip_top + vblank;
|
||||
|
||||
if (set_shutter(client, mt9t031->total_h) < 0)
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Management:
|
||||
* This function does nothing for now but must be present for pm to work
|
||||
*/
|
||||
static int mt9t031_runtime_suspend(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Management:
|
||||
* COLUMN_ADDRESS_MODE and ROW_ADDRESS_MODE are not rewritten if unchanged
|
||||
* they are however changed at reset if the platform hook is present
|
||||
* thus we rewrite them with the values stored by the driver
|
||||
*/
|
||||
static int mt9t031_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct video_device *vdev = to_video_device(dev);
|
||||
struct v4l2_subdev *sd = soc_camera_vdev_to_subdev(vdev);
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
|
||||
int ret;
|
||||
u16 xbin, ybin;
|
||||
|
||||
xbin = min(mt9t031->xskip, (u16)3);
|
||||
ybin = min(mt9t031->yskip, (u16)3);
|
||||
|
||||
ret = reg_write(client, MT9T031_COLUMN_ADDRESS_MODE,
|
||||
((xbin - 1) << 4) | (mt9t031->xskip - 1));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = reg_write(client, MT9T031_ROW_ADDRESS_MODE,
|
||||
((ybin - 1) << 4) | (mt9t031->yskip - 1));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops mt9t031_dev_pm_ops = {
|
||||
.runtime_suspend = mt9t031_runtime_suspend,
|
||||
.runtime_resume = mt9t031_runtime_resume,
|
||||
};
|
||||
|
||||
static struct device_type mt9t031_dev_type = {
|
||||
.name = "MT9T031",
|
||||
.pm = &mt9t031_dev_pm_ops,
|
||||
};
|
||||
|
||||
static int mt9t031_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 video_device *vdev = soc_camera_i2c_to_vdev(client);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
int ret;
|
||||
|
||||
if (on) {
|
||||
ret = soc_camera_power_on(&client->dev, ssdd, mt9t031->clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (vdev)
|
||||
/* Not needed during probing, when vdev isn't available yet */
|
||||
vdev->dev.type = &mt9t031_dev_type;
|
||||
} else {
|
||||
if (vdev)
|
||||
vdev->dev.type = NULL;
|
||||
soc_camera_power_off(&client->dev, ssdd, mt9t031->clk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interface active, can use i2c. If it fails, it can indeed mean, that
|
||||
* this wasn't our capture interface, so, we wait for the right one
|
||||
*/
|
||||
static int mt9t031_video_probe(struct i2c_client *client)
|
||||
{
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
s32 data;
|
||||
int ret;
|
||||
|
||||
ret = mt9t031_s_power(&mt9t031->subdev, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mt9t031_idle(client);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to initialise the camera\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Read out the chip version register */
|
||||
data = reg_read(client, MT9T031_CHIP_VERSION);
|
||||
|
||||
switch (data) {
|
||||
case 0x1621:
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"No MT9T031 chip detected, register read %x\n", data);
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "Detected a MT9T031 chip ID %x\n", data);
|
||||
|
||||
ret = v4l2_ctrl_handler_setup(&mt9t031->hdl);
|
||||
|
||||
done:
|
||||
mt9t031_s_power(&mt9t031->subdev, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9t031_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
|
||||
*lines = mt9t031->y_skip_top;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ctrl_ops mt9t031_ctrl_ops = {
|
||||
.g_volatile_ctrl = mt9t031_g_volatile_ctrl,
|
||||
.s_ctrl = mt9t031_s_ctrl,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_core_ops mt9t031_subdev_core_ops = {
|
||||
.s_power = mt9t031_s_power,
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
.g_register = mt9t031_g_register,
|
||||
.s_register = mt9t031_s_register,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int mt9t031_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
|
||||
enum v4l2_mbus_pixelcode *code)
|
||||
{
|
||||
if (index)
|
||||
return -EINVAL;
|
||||
|
||||
*code = V4L2_MBUS_FMT_SBGGR10_1X10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_g_mbus_config(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_PCLK_SAMPLE_RISING |
|
||||
V4L2_MBUS_PCLK_SAMPLE_FALLING | V4L2_MBUS_HSYNC_ACTIVE_HIGH |
|
||||
V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_DATA_ACTIVE_HIGH;
|
||||
cfg->type = V4L2_MBUS_PARALLEL;
|
||||
cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9t031_s_mbus_config(struct v4l2_subdev *sd,
|
||||
const struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
if (soc_camera_apply_board_flags(ssdd, cfg) &
|
||||
V4L2_MBUS_PCLK_SAMPLE_FALLING)
|
||||
return reg_clear(client, MT9T031_PIXEL_CLOCK_CONTROL, 0x8000);
|
||||
else
|
||||
return reg_set(client, MT9T031_PIXEL_CLOCK_CONTROL, 0x8000);
|
||||
}
|
||||
|
||||
static struct v4l2_subdev_video_ops mt9t031_subdev_video_ops = {
|
||||
.s_stream = mt9t031_s_stream,
|
||||
.s_mbus_fmt = mt9t031_s_fmt,
|
||||
.g_mbus_fmt = mt9t031_g_fmt,
|
||||
.try_mbus_fmt = mt9t031_try_fmt,
|
||||
.s_crop = mt9t031_s_crop,
|
||||
.g_crop = mt9t031_g_crop,
|
||||
.cropcap = mt9t031_cropcap,
|
||||
.enum_mbus_fmt = mt9t031_enum_fmt,
|
||||
.g_mbus_config = mt9t031_g_mbus_config,
|
||||
.s_mbus_config = mt9t031_s_mbus_config,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_sensor_ops mt9t031_subdev_sensor_ops = {
|
||||
.g_skip_top_lines = mt9t031_g_skip_top_lines,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_ops mt9t031_subdev_ops = {
|
||||
.core = &mt9t031_subdev_core_ops,
|
||||
.video = &mt9t031_subdev_video_ops,
|
||||
.sensor = &mt9t031_subdev_sensor_ops,
|
||||
};
|
||||
|
||||
static int mt9t031_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *did)
|
||||
{
|
||||
struct mt9t031 *mt9t031;
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
||||
int ret;
|
||||
|
||||
if (!ssdd) {
|
||||
dev_err(&client->dev, "MT9T031 driver needs platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
|
||||
dev_warn(&adapter->dev,
|
||||
"I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mt9t031 = devm_kzalloc(&client->dev, sizeof(struct mt9t031), GFP_KERNEL);
|
||||
if (!mt9t031)
|
||||
return -ENOMEM;
|
||||
|
||||
v4l2_i2c_subdev_init(&mt9t031->subdev, client, &mt9t031_subdev_ops);
|
||||
v4l2_ctrl_handler_init(&mt9t031->hdl, 5);
|
||||
v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops,
|
||||
V4L2_CID_VFLIP, 0, 1, 1, 0);
|
||||
v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops,
|
||||
V4L2_CID_HFLIP, 0, 1, 1, 0);
|
||||
v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops,
|
||||
V4L2_CID_GAIN, 0, 127, 1, 64);
|
||||
|
||||
/*
|
||||
* Simulated autoexposure. If enabled, we calculate shutter width
|
||||
* ourselves in the driver based on vertical blanking and frame width
|
||||
*/
|
||||
mt9t031->autoexposure = v4l2_ctrl_new_std_menu(&mt9t031->hdl,
|
||||
&mt9t031_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0,
|
||||
V4L2_EXPOSURE_AUTO);
|
||||
mt9t031->exposure = v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops,
|
||||
V4L2_CID_EXPOSURE, 1, 255, 1, 255);
|
||||
|
||||
mt9t031->subdev.ctrl_handler = &mt9t031->hdl;
|
||||
if (mt9t031->hdl.error)
|
||||
return mt9t031->hdl.error;
|
||||
|
||||
v4l2_ctrl_auto_cluster(2, &mt9t031->autoexposure,
|
||||
V4L2_EXPOSURE_MANUAL, true);
|
||||
|
||||
mt9t031->y_skip_top = 0;
|
||||
mt9t031->rect.left = MT9T031_COLUMN_SKIP;
|
||||
mt9t031->rect.top = MT9T031_ROW_SKIP;
|
||||
mt9t031->rect.width = MT9T031_MAX_WIDTH;
|
||||
mt9t031->rect.height = MT9T031_MAX_HEIGHT;
|
||||
|
||||
mt9t031->xskip = 1;
|
||||
mt9t031->yskip = 1;
|
||||
|
||||
mt9t031->clk = v4l2_clk_get(&client->dev, "mclk");
|
||||
if (IS_ERR(mt9t031->clk)) {
|
||||
ret = PTR_ERR(mt9t031->clk);
|
||||
goto eclkget;
|
||||
}
|
||||
|
||||
ret = mt9t031_video_probe(client);
|
||||
if (ret) {
|
||||
v4l2_clk_put(mt9t031->clk);
|
||||
eclkget:
|
||||
v4l2_ctrl_handler_free(&mt9t031->hdl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9t031_remove(struct i2c_client *client)
|
||||
{
|
||||
struct mt9t031 *mt9t031 = to_mt9t031(client);
|
||||
|
||||
v4l2_clk_put(mt9t031->clk);
|
||||
v4l2_device_unregister_subdev(&mt9t031->subdev);
|
||||
v4l2_ctrl_handler_free(&mt9t031->hdl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mt9t031_id[] = {
|
||||
{ "mt9t031", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mt9t031_id);
|
||||
|
||||
static struct i2c_driver mt9t031_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "mt9t031",
|
||||
},
|
||||
.probe = mt9t031_probe,
|
||||
.remove = mt9t031_remove,
|
||||
.id_table = mt9t031_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mt9t031_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Micron MT9T031 Camera driver");
|
||||
MODULE_AUTHOR("Guennadi Liakhovetski <lg@denx.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1138
drivers/media/i2c/soc_camera/mt9t112.c
Normal file
1138
drivers/media/i2c/soc_camera/mt9t112.c
Normal file
File diff suppressed because it is too large
Load diff
990
drivers/media/i2c/soc_camera/mt9v022.c
Normal file
990
drivers/media/i2c/soc_camera/mt9v022.c
Normal file
|
|
@ -0,0 +1,990 @@
|
|||
/*
|
||||
* Driver for MT9V022 CMOS Image Sensor from Micron
|
||||
*
|
||||
* Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
|
||||
*
|
||||
* 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/videodev2.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <media/mt9v022.h>
|
||||
#include <media/soc_camera.h>
|
||||
#include <media/soc_mediabus.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/v4l2-clk.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
|
||||
/*
|
||||
* mt9v022 i2c address 0x48, 0x4c, 0x58, 0x5c
|
||||
* The platform has to define struct i2c_board_info objects and link to them
|
||||
* from struct soc_camera_host_desc
|
||||
*/
|
||||
|
||||
static char *sensor_type;
|
||||
module_param(sensor_type, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(sensor_type, "Sensor type: \"colour\" or \"monochrome\"");
|
||||
|
||||
/* mt9v022 selected register addresses */
|
||||
#define MT9V022_CHIP_VERSION 0x00
|
||||
#define MT9V022_COLUMN_START 0x01
|
||||
#define MT9V022_ROW_START 0x02
|
||||
#define MT9V022_WINDOW_HEIGHT 0x03
|
||||
#define MT9V022_WINDOW_WIDTH 0x04
|
||||
#define MT9V022_HORIZONTAL_BLANKING 0x05
|
||||
#define MT9V022_VERTICAL_BLANKING 0x06
|
||||
#define MT9V022_CHIP_CONTROL 0x07
|
||||
#define MT9V022_SHUTTER_WIDTH1 0x08
|
||||
#define MT9V022_SHUTTER_WIDTH2 0x09
|
||||
#define MT9V022_SHUTTER_WIDTH_CTRL 0x0a
|
||||
#define MT9V022_TOTAL_SHUTTER_WIDTH 0x0b
|
||||
#define MT9V022_RESET 0x0c
|
||||
#define MT9V022_READ_MODE 0x0d
|
||||
#define MT9V022_MONITOR_MODE 0x0e
|
||||
#define MT9V022_PIXEL_OPERATION_MODE 0x0f
|
||||
#define MT9V022_LED_OUT_CONTROL 0x1b
|
||||
#define MT9V022_ADC_MODE_CONTROL 0x1c
|
||||
#define MT9V022_REG32 0x20
|
||||
#define MT9V022_ANALOG_GAIN 0x35
|
||||
#define MT9V022_BLACK_LEVEL_CALIB_CTRL 0x47
|
||||
#define MT9V022_PIXCLK_FV_LV 0x74
|
||||
#define MT9V022_DIGITAL_TEST_PATTERN 0x7f
|
||||
#define MT9V022_AEC_AGC_ENABLE 0xAF
|
||||
#define MT9V022_MAX_TOTAL_SHUTTER_WIDTH 0xBD
|
||||
|
||||
/* mt9v024 partial list register addresses changes with respect to mt9v022 */
|
||||
#define MT9V024_PIXCLK_FV_LV 0x72
|
||||
#define MT9V024_MAX_TOTAL_SHUTTER_WIDTH 0xAD
|
||||
|
||||
/* Progressive scan, master, defaults */
|
||||
#define MT9V022_CHIP_CONTROL_DEFAULT 0x188
|
||||
|
||||
#define MT9V022_MAX_WIDTH 752
|
||||
#define MT9V022_MAX_HEIGHT 480
|
||||
#define MT9V022_MIN_WIDTH 48
|
||||
#define MT9V022_MIN_HEIGHT 32
|
||||
#define MT9V022_COLUMN_SKIP 1
|
||||
#define MT9V022_ROW_SKIP 4
|
||||
|
||||
#define MT9V022_HORIZONTAL_BLANKING_MIN 43
|
||||
#define MT9V022_HORIZONTAL_BLANKING_MAX 1023
|
||||
#define MT9V022_HORIZONTAL_BLANKING_DEF 94
|
||||
#define MT9V022_VERTICAL_BLANKING_MIN 2
|
||||
#define MT9V022_VERTICAL_BLANKING_MAX 3000
|
||||
#define MT9V022_VERTICAL_BLANKING_DEF 45
|
||||
|
||||
#define is_mt9v022_rev3(id) (id == 0x1313)
|
||||
#define is_mt9v024(id) (id == 0x1324)
|
||||
|
||||
/* MT9V022 has only one fixed colorspace per pixelcode */
|
||||
struct mt9v022_datafmt {
|
||||
enum v4l2_mbus_pixelcode code;
|
||||
enum v4l2_colorspace colorspace;
|
||||
};
|
||||
|
||||
/* Find a data format by a pixel code in an array */
|
||||
static const struct mt9v022_datafmt *mt9v022_find_datafmt(
|
||||
enum v4l2_mbus_pixelcode code, const struct mt9v022_datafmt *fmt,
|
||||
int n)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
if (fmt[i].code == code)
|
||||
return fmt + i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct mt9v022_datafmt mt9v022_colour_fmts[] = {
|
||||
/*
|
||||
* Order important: first natively supported,
|
||||
* second supported with a GPIO extender
|
||||
*/
|
||||
{V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB},
|
||||
{V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB},
|
||||
};
|
||||
|
||||
static const struct mt9v022_datafmt mt9v022_monochrome_fmts[] = {
|
||||
/* Order important - see above */
|
||||
{V4L2_MBUS_FMT_Y10_1X10, V4L2_COLORSPACE_JPEG},
|
||||
{V4L2_MBUS_FMT_Y8_1X8, V4L2_COLORSPACE_JPEG},
|
||||
};
|
||||
|
||||
/* only registers with different addresses on different mt9v02x sensors */
|
||||
struct mt9v02x_register {
|
||||
u8 max_total_shutter_width;
|
||||
u8 pixclk_fv_lv;
|
||||
};
|
||||
|
||||
static const struct mt9v02x_register mt9v022_register = {
|
||||
.max_total_shutter_width = MT9V022_MAX_TOTAL_SHUTTER_WIDTH,
|
||||
.pixclk_fv_lv = MT9V022_PIXCLK_FV_LV,
|
||||
};
|
||||
|
||||
static const struct mt9v02x_register mt9v024_register = {
|
||||
.max_total_shutter_width = MT9V024_MAX_TOTAL_SHUTTER_WIDTH,
|
||||
.pixclk_fv_lv = MT9V024_PIXCLK_FV_LV,
|
||||
};
|
||||
|
||||
enum mt9v022_model {
|
||||
MT9V022IX7ATM,
|
||||
MT9V022IX7ATC,
|
||||
};
|
||||
|
||||
struct mt9v022 {
|
||||
struct v4l2_subdev subdev;
|
||||
struct v4l2_ctrl_handler hdl;
|
||||
struct {
|
||||
/* exposure/auto-exposure cluster */
|
||||
struct v4l2_ctrl *autoexposure;
|
||||
struct v4l2_ctrl *exposure;
|
||||
};
|
||||
struct {
|
||||
/* gain/auto-gain cluster */
|
||||
struct v4l2_ctrl *autogain;
|
||||
struct v4l2_ctrl *gain;
|
||||
};
|
||||
struct v4l2_ctrl *hblank;
|
||||
struct v4l2_ctrl *vblank;
|
||||
struct v4l2_rect rect; /* Sensor window */
|
||||
struct v4l2_clk *clk;
|
||||
const struct mt9v022_datafmt *fmt;
|
||||
const struct mt9v022_datafmt *fmts;
|
||||
const struct mt9v02x_register *reg;
|
||||
int num_fmts;
|
||||
enum mt9v022_model model;
|
||||
u16 chip_control;
|
||||
u16 chip_version;
|
||||
unsigned short y_skip_top; /* Lines to skip at the top */
|
||||
};
|
||||
|
||||
static struct mt9v022 *to_mt9v022(const struct i2c_client *client)
|
||||
{
|
||||
return container_of(i2c_get_clientdata(client), struct mt9v022, subdev);
|
||||
}
|
||||
|
||||
static int reg_read(struct i2c_client *client, const u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_word_swapped(client, reg);
|
||||
}
|
||||
|
||||
static int reg_write(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
return i2c_smbus_write_word_swapped(client, reg, data);
|
||||
}
|
||||
|
||||
static int reg_set(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return reg_write(client, reg, ret | data);
|
||||
}
|
||||
|
||||
static int reg_clear(struct i2c_client *client, const u8 reg,
|
||||
const u16 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = reg_read(client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return reg_write(client, reg, ret & ~data);
|
||||
}
|
||||
|
||||
static int mt9v022_init(struct i2c_client *client)
|
||||
{
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Almost the default mode: master, parallel, simultaneous, and an
|
||||
* undocumented bit 0x200, which is present in table 7, but not in 8,
|
||||
* plus snapshot mode to disable scan for now
|
||||
*/
|
||||
mt9v022->chip_control |= 0x10;
|
||||
ret = reg_write(client, MT9V022_CHIP_CONTROL, mt9v022->chip_control);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_READ_MODE, 0x300);
|
||||
|
||||
/* All defaults */
|
||||
if (!ret)
|
||||
/* AEC, AGC on */
|
||||
ret = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x3);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_ANALOG_GAIN, 16);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_TOTAL_SHUTTER_WIDTH, 480);
|
||||
if (!ret)
|
||||
ret = reg_write(client, mt9v022->reg->max_total_shutter_width, 480);
|
||||
if (!ret)
|
||||
/* default - auto */
|
||||
ret = reg_clear(client, MT9V022_BLACK_LEVEL_CALIB_CTRL, 1);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_DIGITAL_TEST_PATTERN, 0);
|
||||
if (!ret)
|
||||
return v4l2_ctrl_handler_setup(&mt9v022->hdl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9v022_s_stream(struct v4l2_subdev *sd, int enable)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
|
||||
if (enable) {
|
||||
/* Switch to master "normal" mode */
|
||||
mt9v022->chip_control &= ~0x10;
|
||||
if (is_mt9v022_rev3(mt9v022->chip_version) ||
|
||||
is_mt9v024(mt9v022->chip_version)) {
|
||||
/*
|
||||
* Unset snapshot mode specific settings: clear bit 9
|
||||
* and bit 2 in reg. 0x20 when in normal mode.
|
||||
*/
|
||||
if (reg_clear(client, MT9V022_REG32, 0x204))
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
/* Switch to snapshot mode */
|
||||
mt9v022->chip_control |= 0x10;
|
||||
if (is_mt9v022_rev3(mt9v022->chip_version) ||
|
||||
is_mt9v024(mt9v022->chip_version)) {
|
||||
/*
|
||||
* Required settings for snapshot mode: set bit 9
|
||||
* (RST enable) and bit 2 (CR enable) in reg. 0x20
|
||||
* See TechNote TN0960 or TN-09-225.
|
||||
*/
|
||||
if (reg_set(client, MT9V022_REG32, 0x204))
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
if (reg_write(client, MT9V022_CHIP_CONTROL, mt9v022->chip_control) < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
struct v4l2_rect rect = a->c;
|
||||
int min_row, min_blank;
|
||||
int ret;
|
||||
|
||||
/* Bayer format - even size lengths */
|
||||
if (mt9v022->fmts == mt9v022_colour_fmts) {
|
||||
rect.width = ALIGN(rect.width, 2);
|
||||
rect.height = ALIGN(rect.height, 2);
|
||||
/* Let the user play with the starting pixel */
|
||||
}
|
||||
|
||||
soc_camera_limit_side(&rect.left, &rect.width,
|
||||
MT9V022_COLUMN_SKIP, MT9V022_MIN_WIDTH, MT9V022_MAX_WIDTH);
|
||||
|
||||
soc_camera_limit_side(&rect.top, &rect.height,
|
||||
MT9V022_ROW_SKIP, MT9V022_MIN_HEIGHT, MT9V022_MAX_HEIGHT);
|
||||
|
||||
/* Like in example app. Contradicts the datasheet though */
|
||||
ret = reg_read(client, MT9V022_AEC_AGC_ENABLE);
|
||||
if (ret >= 0) {
|
||||
if (ret & 1) /* Autoexposure */
|
||||
ret = reg_write(client, mt9v022->reg->max_total_shutter_width,
|
||||
rect.height + mt9v022->y_skip_top + 43);
|
||||
/*
|
||||
* If autoexposure is off, there is no need to set
|
||||
* MT9V022_TOTAL_SHUTTER_WIDTH here. Autoexposure can be off
|
||||
* only if the user has set exposure manually, using the
|
||||
* V4L2_CID_EXPOSURE_AUTO with the value V4L2_EXPOSURE_MANUAL.
|
||||
* In this case the register MT9V022_TOTAL_SHUTTER_WIDTH
|
||||
* already contains the correct value.
|
||||
*/
|
||||
}
|
||||
/* Setup frame format: defaults apart from width and height */
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_COLUMN_START, rect.left);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_ROW_START, rect.top);
|
||||
/*
|
||||
* mt9v022: min total row time is 660 columns, min blanking is 43
|
||||
* mt9v024: min total row time is 690 columns, min blanking is 61
|
||||
*/
|
||||
if (is_mt9v024(mt9v022->chip_version)) {
|
||||
min_row = 690;
|
||||
min_blank = 61;
|
||||
} else {
|
||||
min_row = 660;
|
||||
min_blank = 43;
|
||||
}
|
||||
if (!ret)
|
||||
ret = v4l2_ctrl_s_ctrl(mt9v022->hblank,
|
||||
rect.width > min_row - min_blank ?
|
||||
min_blank : min_row - rect.width);
|
||||
if (!ret)
|
||||
ret = v4l2_ctrl_s_ctrl(mt9v022->vblank, 45);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_WINDOW_WIDTH, rect.width);
|
||||
if (!ret)
|
||||
ret = reg_write(client, MT9V022_WINDOW_HEIGHT,
|
||||
rect.height + mt9v022->y_skip_top);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_dbg(&client->dev, "Frame %dx%d pixel\n", rect.width, rect.height);
|
||||
|
||||
mt9v022->rect = rect;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
|
||||
a->c = mt9v022->rect;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
|
||||
{
|
||||
a->bounds.left = MT9V022_COLUMN_SKIP;
|
||||
a->bounds.top = MT9V022_ROW_SKIP;
|
||||
a->bounds.width = MT9V022_MAX_WIDTH;
|
||||
a->bounds.height = MT9V022_MAX_HEIGHT;
|
||||
a->defrect = a->bounds;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
a->pixelaspect.numerator = 1;
|
||||
a->pixelaspect.denominator = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_g_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
|
||||
mf->width = mt9v022->rect.width;
|
||||
mf->height = mt9v022->rect.height;
|
||||
mf->code = mt9v022->fmt->code;
|
||||
mf->colorspace = mt9v022->fmt->colorspace;
|
||||
mf->field = V4L2_FIELD_NONE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_s_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
struct v4l2_crop a = {
|
||||
.c = {
|
||||
.left = mt9v022->rect.left,
|
||||
.top = mt9v022->rect.top,
|
||||
.width = mf->width,
|
||||
.height = mf->height,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The caller provides a supported format, as verified per call to
|
||||
* .try_mbus_fmt(), datawidth is from our supported format list
|
||||
*/
|
||||
switch (mf->code) {
|
||||
case V4L2_MBUS_FMT_Y8_1X8:
|
||||
case V4L2_MBUS_FMT_Y10_1X10:
|
||||
if (mt9v022->model != MT9V022IX7ATM)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case V4L2_MBUS_FMT_SBGGR8_1X8:
|
||||
case V4L2_MBUS_FMT_SBGGR10_1X10:
|
||||
if (mt9v022->model != MT9V022IX7ATC)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* No support for scaling on this camera, just crop. */
|
||||
ret = mt9v022_s_crop(sd, &a);
|
||||
if (!ret) {
|
||||
mf->width = mt9v022->rect.width;
|
||||
mf->height = mt9v022->rect.height;
|
||||
mt9v022->fmt = mt9v022_find_datafmt(mf->code,
|
||||
mt9v022->fmts, mt9v022->num_fmts);
|
||||
mf->colorspace = mt9v022->fmt->colorspace;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9v022_try_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
const struct mt9v022_datafmt *fmt;
|
||||
int align = mf->code == V4L2_MBUS_FMT_SBGGR8_1X8 ||
|
||||
mf->code == V4L2_MBUS_FMT_SBGGR10_1X10;
|
||||
|
||||
v4l_bound_align_image(&mf->width, MT9V022_MIN_WIDTH,
|
||||
MT9V022_MAX_WIDTH, align,
|
||||
&mf->height, MT9V022_MIN_HEIGHT + mt9v022->y_skip_top,
|
||||
MT9V022_MAX_HEIGHT + mt9v022->y_skip_top, align, 0);
|
||||
|
||||
fmt = mt9v022_find_datafmt(mf->code, mt9v022->fmts,
|
||||
mt9v022->num_fmts);
|
||||
if (!fmt) {
|
||||
fmt = mt9v022->fmt;
|
||||
mf->code = fmt->code;
|
||||
}
|
||||
|
||||
mf->colorspace = fmt->colorspace;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
static int mt9v022_g_register(struct v4l2_subdev *sd,
|
||||
struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
reg->size = 2;
|
||||
reg->val = reg_read(client, reg->reg);
|
||||
|
||||
if (reg->val > 0xffff)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_s_register(struct v4l2_subdev *sd,
|
||||
const struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
if (reg_write(client, reg->reg, reg->val) < 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int mt9v022_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 mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
|
||||
return soc_camera_set_power(&client->dev, ssdd, mt9v022->clk, on);
|
||||
}
|
||||
|
||||
static int mt9v022_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mt9v022 *mt9v022 = container_of(ctrl->handler,
|
||||
struct mt9v022, hdl);
|
||||
struct v4l2_subdev *sd = &mt9v022->subdev;
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct v4l2_ctrl *gain = mt9v022->gain;
|
||||
struct v4l2_ctrl *exp = mt9v022->exposure;
|
||||
unsigned long range;
|
||||
int data;
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_AUTOGAIN:
|
||||
data = reg_read(client, MT9V022_ANALOG_GAIN);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
|
||||
range = gain->maximum - gain->minimum;
|
||||
gain->val = ((data - 16) * range + 24) / 48 + gain->minimum;
|
||||
return 0;
|
||||
case V4L2_CID_EXPOSURE_AUTO:
|
||||
data = reg_read(client, MT9V022_TOTAL_SHUTTER_WIDTH);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
|
||||
range = exp->maximum - exp->minimum;
|
||||
exp->val = ((data - 1) * range + 239) / 479 + exp->minimum;
|
||||
return 0;
|
||||
case V4L2_CID_HBLANK:
|
||||
data = reg_read(client, MT9V022_HORIZONTAL_BLANKING);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
ctrl->val = data;
|
||||
return 0;
|
||||
case V4L2_CID_VBLANK:
|
||||
data = reg_read(client, MT9V022_VERTICAL_BLANKING);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
ctrl->val = data;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mt9v022_s_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mt9v022 *mt9v022 = container_of(ctrl->handler,
|
||||
struct mt9v022, hdl);
|
||||
struct v4l2_subdev *sd = &mt9v022->subdev;
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
int data;
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_VFLIP:
|
||||
if (ctrl->val)
|
||||
data = reg_set(client, MT9V022_READ_MODE, 0x10);
|
||||
else
|
||||
data = reg_clear(client, MT9V022_READ_MODE, 0x10);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
case V4L2_CID_HFLIP:
|
||||
if (ctrl->val)
|
||||
data = reg_set(client, MT9V022_READ_MODE, 0x20);
|
||||
else
|
||||
data = reg_clear(client, MT9V022_READ_MODE, 0x20);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
case V4L2_CID_AUTOGAIN:
|
||||
if (ctrl->val) {
|
||||
if (reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x2) < 0)
|
||||
return -EIO;
|
||||
} else {
|
||||
struct v4l2_ctrl *gain = mt9v022->gain;
|
||||
/* mt9v022 has minimum == default */
|
||||
unsigned long range = gain->maximum - gain->minimum;
|
||||
/* Valid values 16 to 64, 32 to 64 must be even. */
|
||||
unsigned long gain_val = ((gain->val - (s32)gain->minimum) *
|
||||
48 + range / 2) / range + 16;
|
||||
|
||||
if (gain_val >= 32)
|
||||
gain_val &= ~1;
|
||||
|
||||
/*
|
||||
* The user wants to set gain manually, hope, she
|
||||
* knows, what she's doing... Switch AGC off.
|
||||
*/
|
||||
if (reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x2) < 0)
|
||||
return -EIO;
|
||||
|
||||
dev_dbg(&client->dev, "Setting gain from %d to %lu\n",
|
||||
reg_read(client, MT9V022_ANALOG_GAIN), gain_val);
|
||||
if (reg_write(client, MT9V022_ANALOG_GAIN, gain_val) < 0)
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
case V4L2_CID_EXPOSURE_AUTO:
|
||||
if (ctrl->val == V4L2_EXPOSURE_AUTO) {
|
||||
data = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x1);
|
||||
} else {
|
||||
struct v4l2_ctrl *exp = mt9v022->exposure;
|
||||
unsigned long range = exp->maximum - exp->minimum;
|
||||
unsigned long shutter = ((exp->val - (s32)exp->minimum) *
|
||||
479 + range / 2) / range + 1;
|
||||
|
||||
/*
|
||||
* The user wants to set shutter width manually, hope,
|
||||
* she knows, what she's doing... Switch AEC off.
|
||||
*/
|
||||
data = reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x1);
|
||||
if (data < 0)
|
||||
return -EIO;
|
||||
dev_dbg(&client->dev, "Shutter width from %d to %lu\n",
|
||||
reg_read(client, MT9V022_TOTAL_SHUTTER_WIDTH),
|
||||
shutter);
|
||||
if (reg_write(client, MT9V022_TOTAL_SHUTTER_WIDTH,
|
||||
shutter) < 0)
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
case V4L2_CID_HBLANK:
|
||||
if (reg_write(client, MT9V022_HORIZONTAL_BLANKING,
|
||||
ctrl->val) < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
case V4L2_CID_VBLANK:
|
||||
if (reg_write(client, MT9V022_VERTICAL_BLANKING,
|
||||
ctrl->val) < 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interface active, can use i2c. If it fails, it can indeed mean, that
|
||||
* this wasn't our capture interface, so, we wait for the right one
|
||||
*/
|
||||
static int mt9v022_video_probe(struct i2c_client *client)
|
||||
{
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
s32 data;
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
ret = mt9v022_s_power(&mt9v022->subdev, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Read out the chip version register */
|
||||
data = reg_read(client, MT9V022_CHIP_VERSION);
|
||||
|
||||
/* must be 0x1311, 0x1313 or 0x1324 */
|
||||
if (data != 0x1311 && data != 0x1313 && data != 0x1324) {
|
||||
ret = -ENODEV;
|
||||
dev_info(&client->dev, "No MT9V022 found, ID register 0x%x\n",
|
||||
data);
|
||||
goto ei2c;
|
||||
}
|
||||
|
||||
mt9v022->chip_version = data;
|
||||
|
||||
mt9v022->reg = is_mt9v024(data) ? &mt9v024_register :
|
||||
&mt9v022_register;
|
||||
|
||||
/* Soft reset */
|
||||
ret = reg_write(client, MT9V022_RESET, 1);
|
||||
if (ret < 0)
|
||||
goto ei2c;
|
||||
/* 15 clock cycles */
|
||||
udelay(200);
|
||||
if (reg_read(client, MT9V022_RESET)) {
|
||||
dev_err(&client->dev, "Resetting MT9V022 failed!\n");
|
||||
if (ret > 0)
|
||||
ret = -EIO;
|
||||
goto ei2c;
|
||||
}
|
||||
|
||||
/* Set monochrome or colour sensor type */
|
||||
if (sensor_type && (!strcmp("colour", sensor_type) ||
|
||||
!strcmp("color", sensor_type))) {
|
||||
ret = reg_write(client, MT9V022_PIXEL_OPERATION_MODE, 4 | 0x11);
|
||||
mt9v022->model = MT9V022IX7ATC;
|
||||
mt9v022->fmts = mt9v022_colour_fmts;
|
||||
} else {
|
||||
ret = reg_write(client, MT9V022_PIXEL_OPERATION_MODE, 0x11);
|
||||
mt9v022->model = MT9V022IX7ATM;
|
||||
mt9v022->fmts = mt9v022_monochrome_fmts;
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
goto ei2c;
|
||||
|
||||
mt9v022->num_fmts = 0;
|
||||
|
||||
/*
|
||||
* This is a 10bit sensor, so by default we only allow 10bit.
|
||||
* The platform may support different bus widths due to
|
||||
* different routing of the data lines.
|
||||
*/
|
||||
if (ssdd->query_bus_param)
|
||||
flags = ssdd->query_bus_param(ssdd);
|
||||
else
|
||||
flags = SOCAM_DATAWIDTH_10;
|
||||
|
||||
if (flags & SOCAM_DATAWIDTH_10)
|
||||
mt9v022->num_fmts++;
|
||||
else
|
||||
mt9v022->fmts++;
|
||||
|
||||
if (flags & SOCAM_DATAWIDTH_8)
|
||||
mt9v022->num_fmts++;
|
||||
|
||||
mt9v022->fmt = &mt9v022->fmts[0];
|
||||
|
||||
dev_info(&client->dev, "Detected a MT9V022 chip ID %x, %s sensor\n",
|
||||
data, mt9v022->model == MT9V022IX7ATM ?
|
||||
"monochrome" : "colour");
|
||||
|
||||
ret = mt9v022_init(client);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "Failed to initialise the camera\n");
|
||||
|
||||
ei2c:
|
||||
mt9v022_s_power(&mt9v022->subdev, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9v022_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
|
||||
*lines = mt9v022->y_skip_top;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ctrl_ops mt9v022_ctrl_ops = {
|
||||
.g_volatile_ctrl = mt9v022_g_volatile_ctrl,
|
||||
.s_ctrl = mt9v022_s_ctrl,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_core_ops mt9v022_subdev_core_ops = {
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
.g_register = mt9v022_g_register,
|
||||
.s_register = mt9v022_s_register,
|
||||
#endif
|
||||
.s_power = mt9v022_s_power,
|
||||
};
|
||||
|
||||
static int mt9v022_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
|
||||
enum v4l2_mbus_pixelcode *code)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
|
||||
if (index >= mt9v022->num_fmts)
|
||||
return -EINVAL;
|
||||
|
||||
*code = mt9v022->fmts[index].code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_g_mbus_config(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_SLAVE |
|
||||
V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING |
|
||||
V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_LOW |
|
||||
V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW |
|
||||
V4L2_MBUS_DATA_ACTIVE_HIGH;
|
||||
cfg->type = V4L2_MBUS_PARALLEL;
|
||||
cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt9v022_s_mbus_config(struct v4l2_subdev *sd,
|
||||
const struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
unsigned long flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
unsigned int bps = soc_mbus_get_fmtdesc(mt9v022->fmt->code)->bits_per_sample;
|
||||
int ret;
|
||||
u16 pixclk = 0;
|
||||
|
||||
if (ssdd->set_bus_param) {
|
||||
ret = ssdd->set_bus_param(ssdd, 1 << (bps - 1));
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (bps != 10) {
|
||||
/*
|
||||
* Without board specific bus width settings we only support the
|
||||
* sensors native bus width
|
||||
*/
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
|
||||
pixclk |= 0x10;
|
||||
|
||||
if (!(flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH))
|
||||
pixclk |= 0x1;
|
||||
|
||||
if (!(flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH))
|
||||
pixclk |= 0x2;
|
||||
|
||||
ret = reg_write(client, mt9v022->reg->pixclk_fv_lv, pixclk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!(flags & V4L2_MBUS_MASTER))
|
||||
mt9v022->chip_control &= ~0x8;
|
||||
|
||||
ret = reg_write(client, MT9V022_CHIP_CONTROL, mt9v022->chip_control);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_dbg(&client->dev, "Calculated pixclk 0x%x, chip control 0x%x\n",
|
||||
pixclk, mt9v022->chip_control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct v4l2_subdev_video_ops mt9v022_subdev_video_ops = {
|
||||
.s_stream = mt9v022_s_stream,
|
||||
.s_mbus_fmt = mt9v022_s_fmt,
|
||||
.g_mbus_fmt = mt9v022_g_fmt,
|
||||
.try_mbus_fmt = mt9v022_try_fmt,
|
||||
.s_crop = mt9v022_s_crop,
|
||||
.g_crop = mt9v022_g_crop,
|
||||
.cropcap = mt9v022_cropcap,
|
||||
.enum_mbus_fmt = mt9v022_enum_fmt,
|
||||
.g_mbus_config = mt9v022_g_mbus_config,
|
||||
.s_mbus_config = mt9v022_s_mbus_config,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_sensor_ops mt9v022_subdev_sensor_ops = {
|
||||
.g_skip_top_lines = mt9v022_g_skip_top_lines,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_ops mt9v022_subdev_ops = {
|
||||
.core = &mt9v022_subdev_core_ops,
|
||||
.video = &mt9v022_subdev_video_ops,
|
||||
.sensor = &mt9v022_subdev_sensor_ops,
|
||||
};
|
||||
|
||||
static int mt9v022_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *did)
|
||||
{
|
||||
struct mt9v022 *mt9v022;
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
||||
struct mt9v022_platform_data *pdata;
|
||||
int ret;
|
||||
|
||||
if (!ssdd) {
|
||||
dev_err(&client->dev, "MT9V022 driver needs platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
|
||||
dev_warn(&adapter->dev,
|
||||
"I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mt9v022 = devm_kzalloc(&client->dev, sizeof(struct mt9v022), GFP_KERNEL);
|
||||
if (!mt9v022)
|
||||
return -ENOMEM;
|
||||
|
||||
pdata = ssdd->drv_priv;
|
||||
v4l2_i2c_subdev_init(&mt9v022->subdev, client, &mt9v022_subdev_ops);
|
||||
v4l2_ctrl_handler_init(&mt9v022->hdl, 6);
|
||||
v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_VFLIP, 0, 1, 1, 0);
|
||||
v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_HFLIP, 0, 1, 1, 0);
|
||||
mt9v022->autogain = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
|
||||
mt9v022->gain = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_GAIN, 0, 127, 1, 64);
|
||||
|
||||
/*
|
||||
* Simulated autoexposure. If enabled, we calculate shutter width
|
||||
* ourselves in the driver based on vertical blanking and frame width
|
||||
*/
|
||||
mt9v022->autoexposure = v4l2_ctrl_new_std_menu(&mt9v022->hdl,
|
||||
&mt9v022_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0,
|
||||
V4L2_EXPOSURE_AUTO);
|
||||
mt9v022->exposure = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_EXPOSURE, 1, 255, 1, 255);
|
||||
|
||||
mt9v022->hblank = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_HBLANK, MT9V022_HORIZONTAL_BLANKING_MIN,
|
||||
MT9V022_HORIZONTAL_BLANKING_MAX, 1,
|
||||
MT9V022_HORIZONTAL_BLANKING_DEF);
|
||||
|
||||
mt9v022->vblank = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
|
||||
V4L2_CID_VBLANK, MT9V022_VERTICAL_BLANKING_MIN,
|
||||
MT9V022_VERTICAL_BLANKING_MAX, 1,
|
||||
MT9V022_VERTICAL_BLANKING_DEF);
|
||||
|
||||
mt9v022->subdev.ctrl_handler = &mt9v022->hdl;
|
||||
if (mt9v022->hdl.error) {
|
||||
int err = mt9v022->hdl.error;
|
||||
|
||||
dev_err(&client->dev, "control initialisation err %d\n", err);
|
||||
return err;
|
||||
}
|
||||
v4l2_ctrl_auto_cluster(2, &mt9v022->autoexposure,
|
||||
V4L2_EXPOSURE_MANUAL, true);
|
||||
v4l2_ctrl_auto_cluster(2, &mt9v022->autogain, 0, true);
|
||||
|
||||
mt9v022->chip_control = MT9V022_CHIP_CONTROL_DEFAULT;
|
||||
|
||||
/*
|
||||
* On some platforms the first read out line is corrupted.
|
||||
* Workaround it by skipping if indicated by platform data.
|
||||
*/
|
||||
mt9v022->y_skip_top = pdata ? pdata->y_skip_top : 0;
|
||||
mt9v022->rect.left = MT9V022_COLUMN_SKIP;
|
||||
mt9v022->rect.top = MT9V022_ROW_SKIP;
|
||||
mt9v022->rect.width = MT9V022_MAX_WIDTH;
|
||||
mt9v022->rect.height = MT9V022_MAX_HEIGHT;
|
||||
|
||||
mt9v022->clk = v4l2_clk_get(&client->dev, "mclk");
|
||||
if (IS_ERR(mt9v022->clk)) {
|
||||
ret = PTR_ERR(mt9v022->clk);
|
||||
goto eclkget;
|
||||
}
|
||||
|
||||
ret = mt9v022_video_probe(client);
|
||||
if (ret) {
|
||||
v4l2_clk_put(mt9v022->clk);
|
||||
eclkget:
|
||||
v4l2_ctrl_handler_free(&mt9v022->hdl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt9v022_remove(struct i2c_client *client)
|
||||
{
|
||||
struct mt9v022 *mt9v022 = to_mt9v022(client);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
v4l2_clk_put(mt9v022->clk);
|
||||
v4l2_device_unregister_subdev(&mt9v022->subdev);
|
||||
if (ssdd->free_bus)
|
||||
ssdd->free_bus(ssdd);
|
||||
v4l2_ctrl_handler_free(&mt9v022->hdl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static const struct i2c_device_id mt9v022_id[] = {
|
||||
{ "mt9v022", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mt9v022_id);
|
||||
|
||||
static struct i2c_driver mt9v022_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "mt9v022",
|
||||
},
|
||||
.probe = mt9v022_probe,
|
||||
.remove = mt9v022_remove,
|
||||
.id_table = mt9v022_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mt9v022_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Micron MT9V022 Camera driver");
|
||||
MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>");
|
||||
MODULE_LICENSE("GPL");
|
||||
1150
drivers/media/i2c/soc_camera/ov2640.c
Normal file
1150
drivers/media/i2c/soc_camera/ov2640.c
Normal file
File diff suppressed because it is too large
Load diff
1072
drivers/media/i2c/soc_camera/ov5642.c
Normal file
1072
drivers/media/i2c/soc_camera/ov5642.c
Normal file
File diff suppressed because it is too large
Load diff
1064
drivers/media/i2c/soc_camera/ov6650.c
Normal file
1064
drivers/media/i2c/soc_camera/ov6650.c
Normal file
File diff suppressed because it is too large
Load diff
1123
drivers/media/i2c/soc_camera/ov772x.c
Normal file
1123
drivers/media/i2c/soc_camera/ov772x.c
Normal file
File diff suppressed because it is too large
Load diff
751
drivers/media/i2c/soc_camera/ov9640.c
Normal file
751
drivers/media/i2c/soc_camera/ov9640.c
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
/*
|
||||
* OmniVision OV96xx Camera Driver
|
||||
*
|
||||
* Copyright (C) 2009 Marek Vasut <marek.vasut@gmail.com>
|
||||
*
|
||||
* Based on ov772x camera driver:
|
||||
*
|
||||
* Copyright (C) 2008 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* Based on ov7670 and soc_camera_platform driver,
|
||||
*
|
||||
* Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
|
||||
* Copyright (C) 2008 Magnus Damm
|
||||
* Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/v4l2-mediabus.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <media/soc_camera.h>
|
||||
#include <media/v4l2-clk.h>
|
||||
#include <media/v4l2-common.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
|
||||
#include "ov9640.h"
|
||||
|
||||
#define to_ov9640_sensor(sd) container_of(sd, struct ov9640_priv, subdev)
|
||||
|
||||
/* default register setup */
|
||||
static const struct ov9640_reg ov9640_regs_dflt[] = {
|
||||
{ OV9640_COM5, OV9640_COM5_SYSCLK | OV9640_COM5_LONGEXP },
|
||||
{ OV9640_COM6, OV9640_COM6_OPT_BLC | OV9640_COM6_ADBLC_BIAS |
|
||||
OV9640_COM6_FMT_RST | OV9640_COM6_ADBLC_OPTEN },
|
||||
{ OV9640_PSHFT, OV9640_PSHFT_VAL(0x01) },
|
||||
{ OV9640_ACOM, OV9640_ACOM_2X_ANALOG | OV9640_ACOM_RSVD },
|
||||
{ OV9640_TSLB, OV9640_TSLB_YUYV_UYVY },
|
||||
{ OV9640_COM16, OV9640_COM16_RB_AVG },
|
||||
|
||||
/* Gamma curve P */
|
||||
{ 0x6c, 0x40 }, { 0x6d, 0x30 }, { 0x6e, 0x4b }, { 0x6f, 0x60 },
|
||||
{ 0x70, 0x70 }, { 0x71, 0x70 }, { 0x72, 0x70 }, { 0x73, 0x70 },
|
||||
{ 0x74, 0x60 }, { 0x75, 0x60 }, { 0x76, 0x50 }, { 0x77, 0x48 },
|
||||
{ 0x78, 0x3a }, { 0x79, 0x2e }, { 0x7a, 0x28 }, { 0x7b, 0x22 },
|
||||
|
||||
/* Gamma curve T */
|
||||
{ 0x7c, 0x04 }, { 0x7d, 0x07 }, { 0x7e, 0x10 }, { 0x7f, 0x28 },
|
||||
{ 0x80, 0x36 }, { 0x81, 0x44 }, { 0x82, 0x52 }, { 0x83, 0x60 },
|
||||
{ 0x84, 0x6c }, { 0x85, 0x78 }, { 0x86, 0x8c }, { 0x87, 0x9e },
|
||||
{ 0x88, 0xbb }, { 0x89, 0xd2 }, { 0x8a, 0xe6 },
|
||||
};
|
||||
|
||||
/* Configurations
|
||||
* NOTE: for YUV, alter the following registers:
|
||||
* COM12 |= OV9640_COM12_YUV_AVG
|
||||
*
|
||||
* for RGB, alter the following registers:
|
||||
* COM7 |= OV9640_COM7_RGB
|
||||
* COM13 |= OV9640_COM13_RGB_AVG
|
||||
* COM15 |= proper RGB color encoding mode
|
||||
*/
|
||||
static const struct ov9640_reg ov9640_regs_qqcif[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x0f) },
|
||||
{ OV9640_COM1, OV9640_COM1_QQFMT | OV9640_COM1_HREF_2SKIP },
|
||||
{ OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD },
|
||||
{ OV9640_COM7, OV9640_COM7_QCIF },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_qqvga[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x07) },
|
||||
{ OV9640_COM1, OV9640_COM1_QQFMT | OV9640_COM1_HREF_2SKIP },
|
||||
{ OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD },
|
||||
{ OV9640_COM7, OV9640_COM7_QVGA },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_qcif[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x07) },
|
||||
{ OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD },
|
||||
{ OV9640_COM7, OV9640_COM7_QCIF },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_qvga[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x03) },
|
||||
{ OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD },
|
||||
{ OV9640_COM7, OV9640_COM7_QVGA },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_cif[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x03) },
|
||||
{ OV9640_COM3, OV9640_COM3_VP },
|
||||
{ OV9640_COM7, OV9640_COM7_CIF },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_vga[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x01) },
|
||||
{ OV9640_COM3, OV9640_COM3_VP },
|
||||
{ OV9640_COM7, OV9640_COM7_VGA },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_sxga[] = {
|
||||
{ OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x01) },
|
||||
{ OV9640_COM3, OV9640_COM3_VP },
|
||||
{ OV9640_COM7, 0 },
|
||||
{ OV9640_COM12, OV9640_COM12_RSVD },
|
||||
{ OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN },
|
||||
{ OV9640_COM15, OV9640_COM15_OR_10F0 },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_yuv[] = {
|
||||
{ OV9640_MTX1, 0x58 },
|
||||
{ OV9640_MTX2, 0x48 },
|
||||
{ OV9640_MTX3, 0x10 },
|
||||
{ OV9640_MTX4, 0x28 },
|
||||
{ OV9640_MTX5, 0x48 },
|
||||
{ OV9640_MTX6, 0x70 },
|
||||
{ OV9640_MTX7, 0x40 },
|
||||
{ OV9640_MTX8, 0x40 },
|
||||
{ OV9640_MTX9, 0x40 },
|
||||
{ OV9640_MTXS, 0x0f },
|
||||
};
|
||||
|
||||
static const struct ov9640_reg ov9640_regs_rgb[] = {
|
||||
{ OV9640_MTX1, 0x71 },
|
||||
{ OV9640_MTX2, 0x3e },
|
||||
{ OV9640_MTX3, 0x0c },
|
||||
{ OV9640_MTX4, 0x33 },
|
||||
{ OV9640_MTX5, 0x72 },
|
||||
{ OV9640_MTX6, 0x00 },
|
||||
{ OV9640_MTX7, 0x2b },
|
||||
{ OV9640_MTX8, 0x66 },
|
||||
{ OV9640_MTX9, 0xd2 },
|
||||
{ OV9640_MTXS, 0x65 },
|
||||
};
|
||||
|
||||
static enum v4l2_mbus_pixelcode ov9640_codes[] = {
|
||||
V4L2_MBUS_FMT_UYVY8_2X8,
|
||||
V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE,
|
||||
V4L2_MBUS_FMT_RGB565_2X8_LE,
|
||||
};
|
||||
|
||||
/* read a register */
|
||||
static int ov9640_reg_read(struct i2c_client *client, u8 reg, u8 *val)
|
||||
{
|
||||
int ret;
|
||||
u8 data = reg;
|
||||
struct i2c_msg msg = {
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = 1,
|
||||
.buf = &data,
|
||||
};
|
||||
|
||||
ret = i2c_transfer(client->adapter, &msg, 1);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
msg.flags = I2C_M_RD;
|
||||
ret = i2c_transfer(client->adapter, &msg, 1);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
*val = data;
|
||||
return 0;
|
||||
|
||||
err:
|
||||
dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* write a register */
|
||||
static int ov9640_reg_write(struct i2c_client *client, u8 reg, u8 val)
|
||||
{
|
||||
int ret;
|
||||
u8 _val;
|
||||
unsigned char data[2] = { reg, val };
|
||||
struct i2c_msg msg = {
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = 2,
|
||||
.buf = data,
|
||||
};
|
||||
|
||||
ret = i2c_transfer(client->adapter, &msg, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* we have to read the register back ... no idea why, maybe HW bug */
|
||||
ret = ov9640_reg_read(client, reg, &_val);
|
||||
if (ret)
|
||||
dev_err(&client->dev,
|
||||
"Failed reading back register 0x%02x!\n", reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Read a register, alter its bits, write it back */
|
||||
static int ov9640_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 unset)
|
||||
{
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
ret = ov9640_reg_read(client, reg, &val);
|
||||
if (ret) {
|
||||
dev_err(&client->dev,
|
||||
"[Read]-Modify-Write of register %02x failed!\n", reg);
|
||||
return val;
|
||||
}
|
||||
|
||||
val |= set;
|
||||
val &= ~unset;
|
||||
|
||||
ret = ov9640_reg_write(client, reg, val);
|
||||
if (ret)
|
||||
dev_err(&client->dev,
|
||||
"Read-Modify-[Write] of register %02x failed!\n", reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Soft reset the camera. This has nothing to do with the RESET pin! */
|
||||
static int ov9640_reset(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ov9640_reg_write(client, OV9640_COM7, OV9640_COM7_SCCB_RESET);
|
||||
if (ret)
|
||||
dev_err(&client->dev,
|
||||
"An error occurred while entering soft reset!\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Start/Stop streaming from the device */
|
||||
static int ov9640_s_stream(struct v4l2_subdev *sd, int enable)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set status of additional camera capabilities */
|
||||
static int ov9640_s_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct ov9640_priv *priv = container_of(ctrl->handler, struct ov9640_priv, hdl);
|
||||
struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev);
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_VFLIP:
|
||||
if (ctrl->val)
|
||||
return ov9640_reg_rmw(client, OV9640_MVFP,
|
||||
OV9640_MVFP_V, 0);
|
||||
return ov9640_reg_rmw(client, OV9640_MVFP, 0, OV9640_MVFP_V);
|
||||
case V4L2_CID_HFLIP:
|
||||
if (ctrl->val)
|
||||
return ov9640_reg_rmw(client, OV9640_MVFP,
|
||||
OV9640_MVFP_H, 0);
|
||||
return ov9640_reg_rmw(client, OV9640_MVFP, 0, OV9640_MVFP_H);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
static int ov9640_get_register(struct v4l2_subdev *sd,
|
||||
struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
if (reg->reg & ~0xff)
|
||||
return -EINVAL;
|
||||
|
||||
reg->size = 1;
|
||||
|
||||
ret = ov9640_reg_read(client, reg->reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg->val = (__u64)val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ov9640_set_register(struct v4l2_subdev *sd,
|
||||
const struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg & ~0xff || reg->val & ~0xff)
|
||||
return -EINVAL;
|
||||
|
||||
return ov9640_reg_write(client, reg->reg, reg->val);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ov9640_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 ov9640_priv *priv = to_ov9640_sensor(sd);
|
||||
|
||||
return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
|
||||
}
|
||||
|
||||
/* select nearest higher resolution for capture */
|
||||
static void ov9640_res_roundup(u32 *width, u32 *height)
|
||||
{
|
||||
int i;
|
||||
enum { QQCIF, QQVGA, QCIF, QVGA, CIF, VGA, SXGA };
|
||||
int res_x[] = { 88, 160, 176, 320, 352, 640, 1280 };
|
||||
int res_y[] = { 72, 120, 144, 240, 288, 480, 960 };
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(res_x); i++) {
|
||||
if (res_x[i] >= *width && res_y[i] >= *height) {
|
||||
*width = res_x[i];
|
||||
*height = res_y[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*width = res_x[SXGA];
|
||||
*height = res_y[SXGA];
|
||||
}
|
||||
|
||||
/* Prepare necessary register changes depending on color encoding */
|
||||
static void ov9640_alter_regs(enum v4l2_mbus_pixelcode code,
|
||||
struct ov9640_reg_alt *alt)
|
||||
{
|
||||
switch (code) {
|
||||
default:
|
||||
case V4L2_MBUS_FMT_UYVY8_2X8:
|
||||
alt->com12 = OV9640_COM12_YUV_AVG;
|
||||
alt->com13 = OV9640_COM13_Y_DELAY_EN |
|
||||
OV9640_COM13_YUV_DLY(0x01);
|
||||
break;
|
||||
case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE:
|
||||
alt->com7 = OV9640_COM7_RGB;
|
||||
alt->com13 = OV9640_COM13_RGB_AVG;
|
||||
alt->com15 = OV9640_COM15_RGB_555;
|
||||
break;
|
||||
case V4L2_MBUS_FMT_RGB565_2X8_LE:
|
||||
alt->com7 = OV9640_COM7_RGB;
|
||||
alt->com13 = OV9640_COM13_RGB_AVG;
|
||||
alt->com15 = OV9640_COM15_RGB_565;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup registers according to resolution and color encoding */
|
||||
static int ov9640_write_regs(struct i2c_client *client, u32 width,
|
||||
enum v4l2_mbus_pixelcode code, struct ov9640_reg_alt *alts)
|
||||
{
|
||||
const struct ov9640_reg *ov9640_regs, *matrix_regs;
|
||||
int ov9640_regs_len, matrix_regs_len;
|
||||
int i, ret;
|
||||
u8 val;
|
||||
|
||||
/* select register configuration for given resolution */
|
||||
switch (width) {
|
||||
case W_QQCIF:
|
||||
ov9640_regs = ov9640_regs_qqcif;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qqcif);
|
||||
break;
|
||||
case W_QQVGA:
|
||||
ov9640_regs = ov9640_regs_qqvga;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qqvga);
|
||||
break;
|
||||
case W_QCIF:
|
||||
ov9640_regs = ov9640_regs_qcif;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qcif);
|
||||
break;
|
||||
case W_QVGA:
|
||||
ov9640_regs = ov9640_regs_qvga;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qvga);
|
||||
break;
|
||||
case W_CIF:
|
||||
ov9640_regs = ov9640_regs_cif;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_cif);
|
||||
break;
|
||||
case W_VGA:
|
||||
ov9640_regs = ov9640_regs_vga;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_vga);
|
||||
break;
|
||||
case W_SXGA:
|
||||
ov9640_regs = ov9640_regs_sxga;
|
||||
ov9640_regs_len = ARRAY_SIZE(ov9640_regs_sxga);
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev, "Failed to select resolution!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* select color matrix configuration for given color encoding */
|
||||
if (code == V4L2_MBUS_FMT_UYVY8_2X8) {
|
||||
matrix_regs = ov9640_regs_yuv;
|
||||
matrix_regs_len = ARRAY_SIZE(ov9640_regs_yuv);
|
||||
} else {
|
||||
matrix_regs = ov9640_regs_rgb;
|
||||
matrix_regs_len = ARRAY_SIZE(ov9640_regs_rgb);
|
||||
}
|
||||
|
||||
/* write register settings into the module */
|
||||
for (i = 0; i < ov9640_regs_len; i++) {
|
||||
val = ov9640_regs[i].val;
|
||||
|
||||
switch (ov9640_regs[i].reg) {
|
||||
case OV9640_COM7:
|
||||
val |= alts->com7;
|
||||
break;
|
||||
case OV9640_COM12:
|
||||
val |= alts->com12;
|
||||
break;
|
||||
case OV9640_COM13:
|
||||
val |= alts->com13;
|
||||
break;
|
||||
case OV9640_COM15:
|
||||
val |= alts->com15;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = ov9640_reg_write(client, ov9640_regs[i].reg, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* write color matrix configuration into the module */
|
||||
for (i = 0; i < matrix_regs_len; i++) {
|
||||
ret = ov9640_reg_write(client, matrix_regs[i].reg,
|
||||
matrix_regs[i].val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* program default register values */
|
||||
static int ov9640_prog_dflt(struct i2c_client *client)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ov9640_regs_dflt); i++) {
|
||||
ret = ov9640_reg_write(client, ov9640_regs_dflt[i].reg,
|
||||
ov9640_regs_dflt[i].val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* wait for the changes to actually happen, 140ms are not enough yet */
|
||||
mdelay(150);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set the format we will capture in */
|
||||
static int ov9640_s_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct ov9640_reg_alt alts = {0};
|
||||
enum v4l2_colorspace cspace;
|
||||
enum v4l2_mbus_pixelcode code = mf->code;
|
||||
int ret;
|
||||
|
||||
ov9640_res_roundup(&mf->width, &mf->height);
|
||||
ov9640_alter_regs(mf->code, &alts);
|
||||
|
||||
ov9640_reset(client);
|
||||
|
||||
ret = ov9640_prog_dflt(client);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (code) {
|
||||
case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE:
|
||||
case V4L2_MBUS_FMT_RGB565_2X8_LE:
|
||||
cspace = V4L2_COLORSPACE_SRGB;
|
||||
break;
|
||||
default:
|
||||
code = V4L2_MBUS_FMT_UYVY8_2X8;
|
||||
case V4L2_MBUS_FMT_UYVY8_2X8:
|
||||
cspace = V4L2_COLORSPACE_JPEG;
|
||||
}
|
||||
|
||||
ret = ov9640_write_regs(client, mf->width, code, &alts);
|
||||
if (!ret) {
|
||||
mf->code = code;
|
||||
mf->colorspace = cspace;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ov9640_try_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
ov9640_res_roundup(&mf->width, &mf->height);
|
||||
|
||||
mf->field = V4L2_FIELD_NONE;
|
||||
|
||||
switch (mf->code) {
|
||||
case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE:
|
||||
case V4L2_MBUS_FMT_RGB565_2X8_LE:
|
||||
mf->colorspace = V4L2_COLORSPACE_SRGB;
|
||||
break;
|
||||
default:
|
||||
mf->code = V4L2_MBUS_FMT_UYVY8_2X8;
|
||||
case V4L2_MBUS_FMT_UYVY8_2X8:
|
||||
mf->colorspace = V4L2_COLORSPACE_JPEG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ov9640_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
|
||||
enum v4l2_mbus_pixelcode *code)
|
||||
{
|
||||
if (index >= ARRAY_SIZE(ov9640_codes))
|
||||
return -EINVAL;
|
||||
|
||||
*code = ov9640_codes[index];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ov9640_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
|
||||
{
|
||||
a->c.left = 0;
|
||||
a->c.top = 0;
|
||||
a->c.width = W_SXGA;
|
||||
a->c.height = H_SXGA;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ov9640_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
|
||||
{
|
||||
a->bounds.left = 0;
|
||||
a->bounds.top = 0;
|
||||
a->bounds.width = W_SXGA;
|
||||
a->bounds.height = H_SXGA;
|
||||
a->defrect = a->bounds;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
a->pixelaspect.numerator = 1;
|
||||
a->pixelaspect.denominator = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ov9640_video_probe(struct i2c_client *client)
|
||||
{
|
||||
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||||
struct ov9640_priv *priv = to_ov9640_sensor(sd);
|
||||
u8 pid, ver, midh, midl;
|
||||
const char *devname;
|
||||
int ret;
|
||||
|
||||
ret = ov9640_s_power(&priv->subdev, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* check and show product ID and manufacturer ID
|
||||
*/
|
||||
|
||||
ret = ov9640_reg_read(client, OV9640_PID, &pid);
|
||||
if (!ret)
|
||||
ret = ov9640_reg_read(client, OV9640_VER, &ver);
|
||||
if (!ret)
|
||||
ret = ov9640_reg_read(client, OV9640_MIDH, &midh);
|
||||
if (!ret)
|
||||
ret = ov9640_reg_read(client, OV9640_MIDL, &midl);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
switch (VERSION(pid, ver)) {
|
||||
case OV9640_V2:
|
||||
devname = "ov9640";
|
||||
priv->revision = 2;
|
||||
break;
|
||||
case OV9640_V3:
|
||||
devname = "ov9640";
|
||||
priv->revision = 3;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev, "Product ID error %x:%x\n", pid, ver);
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "%s Product ID %0x:%0x Manufacturer ID %x:%x\n",
|
||||
devname, pid, ver, midh, midl);
|
||||
|
||||
ret = v4l2_ctrl_handler_setup(&priv->hdl);
|
||||
|
||||
done:
|
||||
ov9640_s_power(&priv->subdev, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct v4l2_ctrl_ops ov9640_ctrl_ops = {
|
||||
.s_ctrl = ov9640_s_ctrl,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_core_ops ov9640_core_ops = {
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
.g_register = ov9640_get_register,
|
||||
.s_register = ov9640_set_register,
|
||||
#endif
|
||||
.s_power = ov9640_s_power,
|
||||
};
|
||||
|
||||
/* Request bus settings on camera side */
|
||||
static int ov9640_g_mbus_config(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
|
||||
V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH |
|
||||
V4L2_MBUS_DATA_ACTIVE_HIGH;
|
||||
cfg->type = V4L2_MBUS_PARALLEL;
|
||||
cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct v4l2_subdev_video_ops ov9640_video_ops = {
|
||||
.s_stream = ov9640_s_stream,
|
||||
.s_mbus_fmt = ov9640_s_fmt,
|
||||
.try_mbus_fmt = ov9640_try_fmt,
|
||||
.enum_mbus_fmt = ov9640_enum_fmt,
|
||||
.cropcap = ov9640_cropcap,
|
||||
.g_crop = ov9640_g_crop,
|
||||
.g_mbus_config = ov9640_g_mbus_config,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_ops ov9640_subdev_ops = {
|
||||
.core = &ov9640_core_ops,
|
||||
.video = &ov9640_video_ops,
|
||||
};
|
||||
|
||||
/*
|
||||
* i2c_driver function
|
||||
*/
|
||||
static int ov9640_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *did)
|
||||
{
|
||||
struct ov9640_priv *priv;
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
int ret;
|
||||
|
||||
if (!ssdd) {
|
||||
dev_err(&client->dev, "Missing platform_data for driver\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(&client->dev, sizeof(struct ov9640_priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(&client->dev,
|
||||
"Failed to allocate memory for private data!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
v4l2_i2c_subdev_init(&priv->subdev, client, &ov9640_subdev_ops);
|
||||
|
||||
v4l2_ctrl_handler_init(&priv->hdl, 2);
|
||||
v4l2_ctrl_new_std(&priv->hdl, &ov9640_ctrl_ops,
|
||||
V4L2_CID_VFLIP, 0, 1, 1, 0);
|
||||
v4l2_ctrl_new_std(&priv->hdl, &ov9640_ctrl_ops,
|
||||
V4L2_CID_HFLIP, 0, 1, 1, 0);
|
||||
priv->subdev.ctrl_handler = &priv->hdl;
|
||||
if (priv->hdl.error)
|
||||
return priv->hdl.error;
|
||||
|
||||
priv->clk = v4l2_clk_get(&client->dev, "mclk");
|
||||
if (IS_ERR(priv->clk)) {
|
||||
ret = PTR_ERR(priv->clk);
|
||||
goto eclkget;
|
||||
}
|
||||
|
||||
ret = ov9640_video_probe(client);
|
||||
if (ret) {
|
||||
v4l2_clk_put(priv->clk);
|
||||
eclkget:
|
||||
v4l2_ctrl_handler_free(&priv->hdl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ov9640_remove(struct i2c_client *client)
|
||||
{
|
||||
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||||
struct ov9640_priv *priv = to_ov9640_sensor(sd);
|
||||
|
||||
v4l2_clk_put(priv->clk);
|
||||
v4l2_device_unregister_subdev(&priv->subdev);
|
||||
v4l2_ctrl_handler_free(&priv->hdl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ov9640_id[] = {
|
||||
{ "ov9640", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ov9640_id);
|
||||
|
||||
static struct i2c_driver ov9640_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "ov9640",
|
||||
},
|
||||
.probe = ov9640_probe,
|
||||
.remove = ov9640_remove,
|
||||
.id_table = ov9640_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(ov9640_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV96xx");
|
||||
MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
208
drivers/media/i2c/soc_camera/ov9640.h
Normal file
208
drivers/media/i2c/soc_camera/ov9640.h
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* OmniVision OV96xx Camera Header File
|
||||
*
|
||||
* Copyright (C) 2009 Marek Vasut <marek.vasut@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.
|
||||
*/
|
||||
|
||||
#ifndef __DRIVERS_MEDIA_VIDEO_OV9640_H__
|
||||
#define __DRIVERS_MEDIA_VIDEO_OV9640_H__
|
||||
|
||||
/* Register definitions */
|
||||
#define OV9640_GAIN 0x00
|
||||
#define OV9640_BLUE 0x01
|
||||
#define OV9640_RED 0x02
|
||||
#define OV9640_VFER 0x03
|
||||
#define OV9640_COM1 0x04
|
||||
#define OV9640_BAVE 0x05
|
||||
#define OV9640_GEAVE 0x06
|
||||
#define OV9640_RSID 0x07
|
||||
#define OV9640_RAVE 0x08
|
||||
#define OV9640_COM2 0x09
|
||||
#define OV9640_PID 0x0a
|
||||
#define OV9640_VER 0x0b
|
||||
#define OV9640_COM3 0x0c
|
||||
#define OV9640_COM4 0x0d
|
||||
#define OV9640_COM5 0x0e
|
||||
#define OV9640_COM6 0x0f
|
||||
#define OV9640_AECH 0x10
|
||||
#define OV9640_CLKRC 0x11
|
||||
#define OV9640_COM7 0x12
|
||||
#define OV9640_COM8 0x13
|
||||
#define OV9640_COM9 0x14
|
||||
#define OV9640_COM10 0x15
|
||||
/* 0x16 - RESERVED */
|
||||
#define OV9640_HSTART 0x17
|
||||
#define OV9640_HSTOP 0x18
|
||||
#define OV9640_VSTART 0x19
|
||||
#define OV9640_VSTOP 0x1a
|
||||
#define OV9640_PSHFT 0x1b
|
||||
#define OV9640_MIDH 0x1c
|
||||
#define OV9640_MIDL 0x1d
|
||||
#define OV9640_MVFP 0x1e
|
||||
#define OV9640_LAEC 0x1f
|
||||
#define OV9640_BOS 0x20
|
||||
#define OV9640_GBOS 0x21
|
||||
#define OV9640_GROS 0x22
|
||||
#define OV9640_ROS 0x23
|
||||
#define OV9640_AEW 0x24
|
||||
#define OV9640_AEB 0x25
|
||||
#define OV9640_VPT 0x26
|
||||
#define OV9640_BBIAS 0x27
|
||||
#define OV9640_GBBIAS 0x28
|
||||
/* 0x29 - RESERVED */
|
||||
#define OV9640_EXHCH 0x2a
|
||||
#define OV9640_EXHCL 0x2b
|
||||
#define OV9640_RBIAS 0x2c
|
||||
#define OV9640_ADVFL 0x2d
|
||||
#define OV9640_ADVFH 0x2e
|
||||
#define OV9640_YAVE 0x2f
|
||||
#define OV9640_HSYST 0x30
|
||||
#define OV9640_HSYEN 0x31
|
||||
#define OV9640_HREF 0x32
|
||||
#define OV9640_CHLF 0x33
|
||||
#define OV9640_ARBLM 0x34
|
||||
/* 0x35..0x36 - RESERVED */
|
||||
#define OV9640_ADC 0x37
|
||||
#define OV9640_ACOM 0x38
|
||||
#define OV9640_OFON 0x39
|
||||
#define OV9640_TSLB 0x3a
|
||||
#define OV9640_COM11 0x3b
|
||||
#define OV9640_COM12 0x3c
|
||||
#define OV9640_COM13 0x3d
|
||||
#define OV9640_COM14 0x3e
|
||||
#define OV9640_EDGE 0x3f
|
||||
#define OV9640_COM15 0x40
|
||||
#define OV9640_COM16 0x41
|
||||
#define OV9640_COM17 0x42
|
||||
/* 0x43..0x4e - RESERVED */
|
||||
#define OV9640_MTX1 0x4f
|
||||
#define OV9640_MTX2 0x50
|
||||
#define OV9640_MTX3 0x51
|
||||
#define OV9640_MTX4 0x52
|
||||
#define OV9640_MTX5 0x53
|
||||
#define OV9640_MTX6 0x54
|
||||
#define OV9640_MTX7 0x55
|
||||
#define OV9640_MTX8 0x56
|
||||
#define OV9640_MTX9 0x57
|
||||
#define OV9640_MTXS 0x58
|
||||
/* 0x59..0x61 - RESERVED */
|
||||
#define OV9640_LCC1 0x62
|
||||
#define OV9640_LCC2 0x63
|
||||
#define OV9640_LCC3 0x64
|
||||
#define OV9640_LCC4 0x65
|
||||
#define OV9640_LCC5 0x66
|
||||
#define OV9640_MANU 0x67
|
||||
#define OV9640_MANV 0x68
|
||||
#define OV9640_HV 0x69
|
||||
#define OV9640_MBD 0x6a
|
||||
#define OV9640_DBLV 0x6b
|
||||
#define OV9640_GSP 0x6c /* ... till 0x7b */
|
||||
#define OV9640_GST 0x7c /* ... till 0x8a */
|
||||
|
||||
#define OV9640_CLKRC_DPLL_EN 0x80
|
||||
#define OV9640_CLKRC_DIRECT 0x40
|
||||
#define OV9640_CLKRC_DIV(x) ((x) & 0x3f)
|
||||
|
||||
#define OV9640_PSHFT_VAL(x) ((x) & 0xff)
|
||||
|
||||
#define OV9640_ACOM_2X_ANALOG 0x80
|
||||
#define OV9640_ACOM_RSVD 0x12
|
||||
|
||||
#define OV9640_MVFP_V 0x10
|
||||
#define OV9640_MVFP_H 0x20
|
||||
|
||||
#define OV9640_COM1_HREF_NOSKIP 0x00
|
||||
#define OV9640_COM1_HREF_2SKIP 0x04
|
||||
#define OV9640_COM1_HREF_3SKIP 0x08
|
||||
#define OV9640_COM1_QQFMT 0x20
|
||||
|
||||
#define OV9640_COM2_SSM 0x10
|
||||
|
||||
#define OV9640_COM3_VP 0x04
|
||||
|
||||
#define OV9640_COM4_QQ_VP 0x80
|
||||
#define OV9640_COM4_RSVD 0x40
|
||||
|
||||
#define OV9640_COM5_SYSCLK 0x80
|
||||
#define OV9640_COM5_LONGEXP 0x01
|
||||
|
||||
#define OV9640_COM6_OPT_BLC 0x40
|
||||
#define OV9640_COM6_ADBLC_BIAS 0x08
|
||||
#define OV9640_COM6_FMT_RST 0x82
|
||||
#define OV9640_COM6_ADBLC_OPTEN 0x01
|
||||
|
||||
#define OV9640_COM7_RAW_RGB 0x01
|
||||
#define OV9640_COM7_RGB 0x04
|
||||
#define OV9640_COM7_QCIF 0x08
|
||||
#define OV9640_COM7_QVGA 0x10
|
||||
#define OV9640_COM7_CIF 0x20
|
||||
#define OV9640_COM7_VGA 0x40
|
||||
#define OV9640_COM7_SCCB_RESET 0x80
|
||||
|
||||
#define OV9640_TSLB_YVYU_YUYV 0x04
|
||||
#define OV9640_TSLB_YUYV_UYVY 0x08
|
||||
|
||||
#define OV9640_COM12_YUV_AVG 0x04
|
||||
#define OV9640_COM12_RSVD 0x40
|
||||
|
||||
#define OV9640_COM13_GAMMA_NONE 0x00
|
||||
#define OV9640_COM13_GAMMA_Y 0x40
|
||||
#define OV9640_COM13_GAMMA_RAW 0x80
|
||||
#define OV9640_COM13_RGB_AVG 0x20
|
||||
#define OV9640_COM13_MATRIX_EN 0x10
|
||||
#define OV9640_COM13_Y_DELAY_EN 0x08
|
||||
#define OV9640_COM13_YUV_DLY(x) ((x) & 0x07)
|
||||
|
||||
#define OV9640_COM15_OR_00FF 0x00
|
||||
#define OV9640_COM15_OR_01FE 0x40
|
||||
#define OV9640_COM15_OR_10F0 0xc0
|
||||
#define OV9640_COM15_RGB_NORM 0x00
|
||||
#define OV9640_COM15_RGB_565 0x10
|
||||
#define OV9640_COM15_RGB_555 0x30
|
||||
|
||||
#define OV9640_COM16_RB_AVG 0x01
|
||||
|
||||
/* IDs */
|
||||
#define OV9640_V2 0x9648
|
||||
#define OV9640_V3 0x9649
|
||||
#define VERSION(pid, ver) (((pid) << 8) | ((ver) & 0xFF))
|
||||
|
||||
/* supported resolutions */
|
||||
enum {
|
||||
W_QQCIF = 88,
|
||||
W_QQVGA = 160,
|
||||
W_QCIF = 176,
|
||||
W_QVGA = 320,
|
||||
W_CIF = 352,
|
||||
W_VGA = 640,
|
||||
W_SXGA = 1280
|
||||
};
|
||||
#define H_SXGA 960
|
||||
|
||||
/* Misc. structures */
|
||||
struct ov9640_reg_alt {
|
||||
u8 com7;
|
||||
u8 com12;
|
||||
u8 com13;
|
||||
u8 com15;
|
||||
};
|
||||
|
||||
struct ov9640_reg {
|
||||
u8 reg;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
struct ov9640_priv {
|
||||
struct v4l2_subdev subdev;
|
||||
struct v4l2_ctrl_handler hdl;
|
||||
struct v4l2_clk *clk;
|
||||
|
||||
int model;
|
||||
int revision;
|
||||
};
|
||||
|
||||
#endif /* __DRIVERS_MEDIA_VIDEO_OV9640_H__ */
|
||||
1008
drivers/media/i2c/soc_camera/ov9740.c
Normal file
1008
drivers/media/i2c/soc_camera/ov9740.c
Normal file
File diff suppressed because it is too large
Load diff
1410
drivers/media/i2c/soc_camera/rj54n1cb0c.c
Normal file
1410
drivers/media/i2c/soc_camera/rj54n1cb0c.c
Normal file
File diff suppressed because it is too large
Load diff
973
drivers/media/i2c/soc_camera/tw9910.c
Normal file
973
drivers/media/i2c/soc_camera/tw9910.c
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
/*
|
||||
* tw9910 Video Driver
|
||||
*
|
||||
* Copyright (C) 2008 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* Based on ov772x driver,
|
||||
*
|
||||
* Copyright (C) 2008 Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
* Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
|
||||
* Copyright (C) 2008 Magnus Damm
|
||||
* Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/v4l2-mediabus.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <media/soc_camera.h>
|
||||
#include <media/tw9910.h>
|
||||
#include <media/v4l2-clk.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
|
||||
#define GET_ID(val) ((val & 0xF8) >> 3)
|
||||
#define GET_REV(val) (val & 0x07)
|
||||
|
||||
/*
|
||||
* register offset
|
||||
*/
|
||||
#define ID 0x00 /* Product ID Code Register */
|
||||
#define STATUS1 0x01 /* Chip Status Register I */
|
||||
#define INFORM 0x02 /* Input Format */
|
||||
#define OPFORM 0x03 /* Output Format Control Register */
|
||||
#define DLYCTR 0x04 /* Hysteresis and HSYNC Delay Control */
|
||||
#define OUTCTR1 0x05 /* Output Control I */
|
||||
#define ACNTL1 0x06 /* Analog Control Register 1 */
|
||||
#define CROP_HI 0x07 /* Cropping Register, High */
|
||||
#define VDELAY_LO 0x08 /* Vertical Delay Register, Low */
|
||||
#define VACTIVE_LO 0x09 /* Vertical Active Register, Low */
|
||||
#define HDELAY_LO 0x0A /* Horizontal Delay Register, Low */
|
||||
#define HACTIVE_LO 0x0B /* Horizontal Active Register, Low */
|
||||
#define CNTRL1 0x0C /* Control Register I */
|
||||
#define VSCALE_LO 0x0D /* Vertical Scaling Register, Low */
|
||||
#define SCALE_HI 0x0E /* Scaling Register, High */
|
||||
#define HSCALE_LO 0x0F /* Horizontal Scaling Register, Low */
|
||||
#define BRIGHT 0x10 /* BRIGHTNESS Control Register */
|
||||
#define CONTRAST 0x11 /* CONTRAST Control Register */
|
||||
#define SHARPNESS 0x12 /* SHARPNESS Control Register I */
|
||||
#define SAT_U 0x13 /* Chroma (U) Gain Register */
|
||||
#define SAT_V 0x14 /* Chroma (V) Gain Register */
|
||||
#define HUE 0x15 /* Hue Control Register */
|
||||
#define CORING1 0x17
|
||||
#define CORING2 0x18 /* Coring and IF compensation */
|
||||
#define VBICNTL 0x19 /* VBI Control Register */
|
||||
#define ACNTL2 0x1A /* Analog Control 2 */
|
||||
#define OUTCTR2 0x1B /* Output Control 2 */
|
||||
#define SDT 0x1C /* Standard Selection */
|
||||
#define SDTR 0x1D /* Standard Recognition */
|
||||
#define TEST 0x1F /* Test Control Register */
|
||||
#define CLMPG 0x20 /* Clamping Gain */
|
||||
#define IAGC 0x21 /* Individual AGC Gain */
|
||||
#define AGCGAIN 0x22 /* AGC Gain */
|
||||
#define PEAKWT 0x23 /* White Peak Threshold */
|
||||
#define CLMPL 0x24 /* Clamp level */
|
||||
#define SYNCT 0x25 /* Sync Amplitude */
|
||||
#define MISSCNT 0x26 /* Sync Miss Count Register */
|
||||
#define PCLAMP 0x27 /* Clamp Position Register */
|
||||
#define VCNTL1 0x28 /* Vertical Control I */
|
||||
#define VCNTL2 0x29 /* Vertical Control II */
|
||||
#define CKILL 0x2A /* Color Killer Level Control */
|
||||
#define COMB 0x2B /* Comb Filter Control */
|
||||
#define LDLY 0x2C /* Luma Delay and H Filter Control */
|
||||
#define MISC1 0x2D /* Miscellaneous Control I */
|
||||
#define LOOP 0x2E /* LOOP Control Register */
|
||||
#define MISC2 0x2F /* Miscellaneous Control II */
|
||||
#define MVSN 0x30 /* Macrovision Detection */
|
||||
#define STATUS2 0x31 /* Chip STATUS II */
|
||||
#define HFREF 0x32 /* H monitor */
|
||||
#define CLMD 0x33 /* CLAMP MODE */
|
||||
#define IDCNTL 0x34 /* ID Detection Control */
|
||||
#define CLCNTL1 0x35 /* Clamp Control I */
|
||||
#define ANAPLLCTL 0x4C
|
||||
#define VBIMIN 0x4D
|
||||
#define HSLOWCTL 0x4E
|
||||
#define WSS3 0x4F
|
||||
#define FILLDATA 0x50
|
||||
#define SDID 0x51
|
||||
#define DID 0x52
|
||||
#define WSS1 0x53
|
||||
#define WSS2 0x54
|
||||
#define VVBI 0x55
|
||||
#define LCTL6 0x56
|
||||
#define LCTL7 0x57
|
||||
#define LCTL8 0x58
|
||||
#define LCTL9 0x59
|
||||
#define LCTL10 0x5A
|
||||
#define LCTL11 0x5B
|
||||
#define LCTL12 0x5C
|
||||
#define LCTL13 0x5D
|
||||
#define LCTL14 0x5E
|
||||
#define LCTL15 0x5F
|
||||
#define LCTL16 0x60
|
||||
#define LCTL17 0x61
|
||||
#define LCTL18 0x62
|
||||
#define LCTL19 0x63
|
||||
#define LCTL20 0x64
|
||||
#define LCTL21 0x65
|
||||
#define LCTL22 0x66
|
||||
#define LCTL23 0x67
|
||||
#define LCTL24 0x68
|
||||
#define LCTL25 0x69
|
||||
#define LCTL26 0x6A
|
||||
#define HSBEGIN 0x6B
|
||||
#define HSEND 0x6C
|
||||
#define OVSDLY 0x6D
|
||||
#define OVSEND 0x6E
|
||||
#define VBIDELAY 0x6F
|
||||
|
||||
/*
|
||||
* register detail
|
||||
*/
|
||||
|
||||
/* INFORM */
|
||||
#define FC27_ON 0x40 /* 1 : Input crystal clock frequency is 27MHz */
|
||||
#define FC27_FF 0x00 /* 0 : Square pixel mode. */
|
||||
/* Must use 24.54MHz for 60Hz field rate */
|
||||
/* source or 29.5MHz for 50Hz field rate */
|
||||
#define IFSEL_S 0x10 /* 01 : S-video decoding */
|
||||
#define IFSEL_C 0x00 /* 00 : Composite video decoding */
|
||||
/* Y input video selection */
|
||||
#define YSEL_M0 0x00 /* 00 : Mux0 selected */
|
||||
#define YSEL_M1 0x04 /* 01 : Mux1 selected */
|
||||
#define YSEL_M2 0x08 /* 10 : Mux2 selected */
|
||||
#define YSEL_M3 0x10 /* 11 : Mux3 selected */
|
||||
|
||||
/* OPFORM */
|
||||
#define MODE 0x80 /* 0 : CCIR601 compatible YCrCb 4:2:2 format */
|
||||
/* 1 : ITU-R-656 compatible data sequence format */
|
||||
#define LEN 0x40 /* 0 : 8-bit YCrCb 4:2:2 output format */
|
||||
/* 1 : 16-bit YCrCb 4:2:2 output format.*/
|
||||
#define LLCMODE 0x20 /* 1 : LLC output mode. */
|
||||
/* 0 : free-run output mode */
|
||||
#define AINC 0x10 /* Serial interface auto-indexing control */
|
||||
/* 0 : auto-increment */
|
||||
/* 1 : non-auto */
|
||||
#define VSCTL 0x08 /* 1 : Vertical out ctrl by DVALID */
|
||||
/* 0 : Vertical out ctrl by HACTIVE and DVALID */
|
||||
#define OEN_TRI_SEL_MASK 0x07
|
||||
#define OEN_TRI_SEL_ALL_ON 0x00 /* Enable output for Rev0/Rev1 */
|
||||
#define OEN_TRI_SEL_ALL_OFF_r0 0x06 /* All tri-stated for Rev0 */
|
||||
#define OEN_TRI_SEL_ALL_OFF_r1 0x07 /* All tri-stated for Rev1 */
|
||||
|
||||
/* OUTCTR1 */
|
||||
#define VSP_LO 0x00 /* 0 : VS pin output polarity is active low */
|
||||
#define VSP_HI 0x80 /* 1 : VS pin output polarity is active high. */
|
||||
/* VS pin output control */
|
||||
#define VSSL_VSYNC 0x00 /* 0 : VSYNC */
|
||||
#define VSSL_VACT 0x10 /* 1 : VACT */
|
||||
#define VSSL_FIELD 0x20 /* 2 : FIELD */
|
||||
#define VSSL_VVALID 0x30 /* 3 : VVALID */
|
||||
#define VSSL_ZERO 0x70 /* 7 : 0 */
|
||||
#define HSP_LOW 0x00 /* 0 : HS pin output polarity is active low */
|
||||
#define HSP_HI 0x08 /* 1 : HS pin output polarity is active high.*/
|
||||
/* HS pin output control */
|
||||
#define HSSL_HACT 0x00 /* 0 : HACT */
|
||||
#define HSSL_HSYNC 0x01 /* 1 : HSYNC */
|
||||
#define HSSL_DVALID 0x02 /* 2 : DVALID */
|
||||
#define HSSL_HLOCK 0x03 /* 3 : HLOCK */
|
||||
#define HSSL_ASYNCW 0x04 /* 4 : ASYNCW */
|
||||
#define HSSL_ZERO 0x07 /* 7 : 0 */
|
||||
|
||||
/* ACNTL1 */
|
||||
#define SRESET 0x80 /* resets the device to its default state
|
||||
* but all register content remain unchanged.
|
||||
* This bit is self-resetting.
|
||||
*/
|
||||
#define ACNTL1_PDN_MASK 0x0e
|
||||
#define CLK_PDN 0x08 /* system clock power down */
|
||||
#define Y_PDN 0x04 /* Luma ADC power down */
|
||||
#define C_PDN 0x02 /* Chroma ADC power down */
|
||||
|
||||
/* ACNTL2 */
|
||||
#define ACNTL2_PDN_MASK 0x40
|
||||
#define PLL_PDN 0x40 /* PLL power down */
|
||||
|
||||
/* VBICNTL */
|
||||
|
||||
/* RTSEL : control the real time signal output from the MPOUT pin */
|
||||
#define RTSEL_MASK 0x07
|
||||
#define RTSEL_VLOSS 0x00 /* 0000 = Video loss */
|
||||
#define RTSEL_HLOCK 0x01 /* 0001 = H-lock */
|
||||
#define RTSEL_SLOCK 0x02 /* 0010 = S-lock */
|
||||
#define RTSEL_VLOCK 0x03 /* 0011 = V-lock */
|
||||
#define RTSEL_MONO 0x04 /* 0100 = MONO */
|
||||
#define RTSEL_DET50 0x05 /* 0101 = DET50 */
|
||||
#define RTSEL_FIELD 0x06 /* 0110 = FIELD */
|
||||
#define RTSEL_RTCO 0x07 /* 0111 = RTCO ( Real Time Control ) */
|
||||
|
||||
/* HSYNC start and end are constant for now */
|
||||
#define HSYNC_START 0x0260
|
||||
#define HSYNC_END 0x0300
|
||||
|
||||
/*
|
||||
* structure
|
||||
*/
|
||||
|
||||
struct regval_list {
|
||||
unsigned char reg_num;
|
||||
unsigned char value;
|
||||
};
|
||||
|
||||
struct tw9910_scale_ctrl {
|
||||
char *name;
|
||||
unsigned short width;
|
||||
unsigned short height;
|
||||
u16 hscale;
|
||||
u16 vscale;
|
||||
};
|
||||
|
||||
struct tw9910_priv {
|
||||
struct v4l2_subdev subdev;
|
||||
struct v4l2_clk *clk;
|
||||
struct tw9910_video_info *info;
|
||||
const struct tw9910_scale_ctrl *scale;
|
||||
v4l2_std_id norm;
|
||||
u32 revision;
|
||||
};
|
||||
|
||||
static const struct tw9910_scale_ctrl tw9910_ntsc_scales[] = {
|
||||
{
|
||||
.name = "NTSC SQ",
|
||||
.width = 640,
|
||||
.height = 480,
|
||||
.hscale = 0x0100,
|
||||
.vscale = 0x0100,
|
||||
},
|
||||
{
|
||||
.name = "NTSC CCIR601",
|
||||
.width = 720,
|
||||
.height = 480,
|
||||
.hscale = 0x0100,
|
||||
.vscale = 0x0100,
|
||||
},
|
||||
{
|
||||
.name = "NTSC SQ (CIF)",
|
||||
.width = 320,
|
||||
.height = 240,
|
||||
.hscale = 0x0200,
|
||||
.vscale = 0x0200,
|
||||
},
|
||||
{
|
||||
.name = "NTSC CCIR601 (CIF)",
|
||||
.width = 360,
|
||||
.height = 240,
|
||||
.hscale = 0x0200,
|
||||
.vscale = 0x0200,
|
||||
},
|
||||
{
|
||||
.name = "NTSC SQ (QCIF)",
|
||||
.width = 160,
|
||||
.height = 120,
|
||||
.hscale = 0x0400,
|
||||
.vscale = 0x0400,
|
||||
},
|
||||
{
|
||||
.name = "NTSC CCIR601 (QCIF)",
|
||||
.width = 180,
|
||||
.height = 120,
|
||||
.hscale = 0x0400,
|
||||
.vscale = 0x0400,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct tw9910_scale_ctrl tw9910_pal_scales[] = {
|
||||
{
|
||||
.name = "PAL SQ",
|
||||
.width = 768,
|
||||
.height = 576,
|
||||
.hscale = 0x0100,
|
||||
.vscale = 0x0100,
|
||||
},
|
||||
{
|
||||
.name = "PAL CCIR601",
|
||||
.width = 720,
|
||||
.height = 576,
|
||||
.hscale = 0x0100,
|
||||
.vscale = 0x0100,
|
||||
},
|
||||
{
|
||||
.name = "PAL SQ (CIF)",
|
||||
.width = 384,
|
||||
.height = 288,
|
||||
.hscale = 0x0200,
|
||||
.vscale = 0x0200,
|
||||
},
|
||||
{
|
||||
.name = "PAL CCIR601 (CIF)",
|
||||
.width = 360,
|
||||
.height = 288,
|
||||
.hscale = 0x0200,
|
||||
.vscale = 0x0200,
|
||||
},
|
||||
{
|
||||
.name = "PAL SQ (QCIF)",
|
||||
.width = 192,
|
||||
.height = 144,
|
||||
.hscale = 0x0400,
|
||||
.vscale = 0x0400,
|
||||
},
|
||||
{
|
||||
.name = "PAL CCIR601 (QCIF)",
|
||||
.width = 180,
|
||||
.height = 144,
|
||||
.hscale = 0x0400,
|
||||
.vscale = 0x0400,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* general function
|
||||
*/
|
||||
static struct tw9910_priv *to_tw9910(const struct i2c_client *client)
|
||||
{
|
||||
return container_of(i2c_get_clientdata(client), struct tw9910_priv,
|
||||
subdev);
|
||||
}
|
||||
|
||||
static int tw9910_mask_set(struct i2c_client *client, u8 command,
|
||||
u8 mask, u8 set)
|
||||
{
|
||||
s32 val = i2c_smbus_read_byte_data(client, command);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
val &= ~mask;
|
||||
val |= set & mask;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, command, val);
|
||||
}
|
||||
|
||||
static int tw9910_set_scale(struct i2c_client *client,
|
||||
const struct tw9910_scale_ctrl *scale)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, SCALE_HI,
|
||||
(scale->vscale & 0x0F00) >> 4 |
|
||||
(scale->hscale & 0x0F00) >> 8);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, HSCALE_LO,
|
||||
scale->hscale & 0x00FF);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, VSCALE_LO,
|
||||
scale->vscale & 0x00FF);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tw9910_set_hsync(struct i2c_client *client)
|
||||
{
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
int ret;
|
||||
|
||||
/* bit 10 - 3 */
|
||||
ret = i2c_smbus_write_byte_data(client, HSBEGIN,
|
||||
(HSYNC_START & 0x07F8) >> 3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* bit 10 - 3 */
|
||||
ret = i2c_smbus_write_byte_data(client, HSEND,
|
||||
(HSYNC_END & 0x07F8) >> 3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* So far only revisions 0 and 1 have been seen */
|
||||
/* bit 2 - 0 */
|
||||
if (1 == priv->revision)
|
||||
ret = tw9910_mask_set(client, HSLOWCTL, 0x77,
|
||||
(HSYNC_START & 0x0007) << 4 |
|
||||
(HSYNC_END & 0x0007));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tw9910_reset(struct i2c_client *client)
|
||||
{
|
||||
tw9910_mask_set(client, ACNTL1, SRESET, SRESET);
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
static int tw9910_power(struct i2c_client *client, int enable)
|
||||
{
|
||||
int ret;
|
||||
u8 acntl1;
|
||||
u8 acntl2;
|
||||
|
||||
if (enable) {
|
||||
acntl1 = 0;
|
||||
acntl2 = 0;
|
||||
} else {
|
||||
acntl1 = CLK_PDN | Y_PDN | C_PDN;
|
||||
acntl2 = PLL_PDN;
|
||||
}
|
||||
|
||||
ret = tw9910_mask_set(client, ACNTL1, ACNTL1_PDN_MASK, acntl1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return tw9910_mask_set(client, ACNTL2, ACNTL2_PDN_MASK, acntl2);
|
||||
}
|
||||
|
||||
static const struct tw9910_scale_ctrl *tw9910_select_norm(v4l2_std_id norm,
|
||||
u32 width, u32 height)
|
||||
{
|
||||
const struct tw9910_scale_ctrl *scale;
|
||||
const struct tw9910_scale_ctrl *ret = NULL;
|
||||
__u32 diff = 0xffffffff, tmp;
|
||||
int size, i;
|
||||
|
||||
if (norm & V4L2_STD_NTSC) {
|
||||
scale = tw9910_ntsc_scales;
|
||||
size = ARRAY_SIZE(tw9910_ntsc_scales);
|
||||
} else if (norm & V4L2_STD_PAL) {
|
||||
scale = tw9910_pal_scales;
|
||||
size = ARRAY_SIZE(tw9910_pal_scales);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
tmp = abs(width - scale[i].width) +
|
||||
abs(height - scale[i].height);
|
||||
if (tmp < diff) {
|
||||
diff = tmp;
|
||||
ret = scale + i;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* subdevice operations
|
||||
*/
|
||||
static int tw9910_s_stream(struct v4l2_subdev *sd, int enable)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
if (!enable) {
|
||||
switch (priv->revision) {
|
||||
case 0:
|
||||
val = OEN_TRI_SEL_ALL_OFF_r0;
|
||||
break;
|
||||
case 1:
|
||||
val = OEN_TRI_SEL_ALL_OFF_r1;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev, "un-supported revision\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
val = OEN_TRI_SEL_ALL_ON;
|
||||
|
||||
if (!priv->scale) {
|
||||
dev_err(&client->dev, "norm select error\n");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev, "%s %dx%d\n",
|
||||
priv->scale->name,
|
||||
priv->scale->width,
|
||||
priv->scale->height);
|
||||
}
|
||||
|
||||
ret = tw9910_mask_set(client, OPFORM, OEN_TRI_SEL_MASK, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return tw9910_power(client, enable);
|
||||
}
|
||||
|
||||
static int tw9910_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
|
||||
*norm = priv->norm;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
|
||||
if (!(norm & (V4L2_STD_NTSC | V4L2_STD_PAL)))
|
||||
return -EINVAL;
|
||||
|
||||
priv->norm = norm;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
static int tw9910_g_register(struct v4l2_subdev *sd,
|
||||
struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
int ret;
|
||||
|
||||
if (reg->reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
reg->size = 1;
|
||||
ret = i2c_smbus_read_byte_data(client, reg->reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* ret = int
|
||||
* reg->val = __u64
|
||||
*/
|
||||
reg->val = (__u64)ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_s_register(struct v4l2_subdev *sd,
|
||||
const struct v4l2_dbg_register *reg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
if (reg->reg > 0xff ||
|
||||
reg->val > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, reg->reg, reg->val);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int tw9910_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 tw9910_priv *priv = to_tw9910(client);
|
||||
|
||||
return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
|
||||
}
|
||||
|
||||
static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
int ret = -EINVAL;
|
||||
u8 val;
|
||||
|
||||
/*
|
||||
* select suitable norm
|
||||
*/
|
||||
priv->scale = tw9910_select_norm(priv->norm, *width, *height);
|
||||
if (!priv->scale)
|
||||
goto tw9910_set_fmt_error;
|
||||
|
||||
/*
|
||||
* reset hardware
|
||||
*/
|
||||
tw9910_reset(client);
|
||||
|
||||
/*
|
||||
* set bus width
|
||||
*/
|
||||
val = 0x00;
|
||||
if (SOCAM_DATAWIDTH_16 == priv->info->buswidth)
|
||||
val = LEN;
|
||||
|
||||
ret = tw9910_mask_set(client, OPFORM, LEN, val);
|
||||
if (ret < 0)
|
||||
goto tw9910_set_fmt_error;
|
||||
|
||||
/*
|
||||
* select MPOUT behavior
|
||||
*/
|
||||
switch (priv->info->mpout) {
|
||||
case TW9910_MPO_VLOSS:
|
||||
val = RTSEL_VLOSS; break;
|
||||
case TW9910_MPO_HLOCK:
|
||||
val = RTSEL_HLOCK; break;
|
||||
case TW9910_MPO_SLOCK:
|
||||
val = RTSEL_SLOCK; break;
|
||||
case TW9910_MPO_VLOCK:
|
||||
val = RTSEL_VLOCK; break;
|
||||
case TW9910_MPO_MONO:
|
||||
val = RTSEL_MONO; break;
|
||||
case TW9910_MPO_DET50:
|
||||
val = RTSEL_DET50; break;
|
||||
case TW9910_MPO_FIELD:
|
||||
val = RTSEL_FIELD; break;
|
||||
case TW9910_MPO_RTCO:
|
||||
val = RTSEL_RTCO; break;
|
||||
default:
|
||||
val = 0;
|
||||
}
|
||||
|
||||
ret = tw9910_mask_set(client, VBICNTL, RTSEL_MASK, val);
|
||||
if (ret < 0)
|
||||
goto tw9910_set_fmt_error;
|
||||
|
||||
/*
|
||||
* set scale
|
||||
*/
|
||||
ret = tw9910_set_scale(client, priv->scale);
|
||||
if (ret < 0)
|
||||
goto tw9910_set_fmt_error;
|
||||
|
||||
/*
|
||||
* set hsync
|
||||
*/
|
||||
ret = tw9910_set_hsync(client);
|
||||
if (ret < 0)
|
||||
goto tw9910_set_fmt_error;
|
||||
|
||||
*width = priv->scale->width;
|
||||
*height = priv->scale->height;
|
||||
|
||||
return ret;
|
||||
|
||||
tw9910_set_fmt_error:
|
||||
|
||||
tw9910_reset(client);
|
||||
priv->scale = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tw9910_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
|
||||
a->c.left = 0;
|
||||
a->c.top = 0;
|
||||
if (priv->norm & V4L2_STD_NTSC) {
|
||||
a->c.width = 640;
|
||||
a->c.height = 480;
|
||||
} else {
|
||||
a->c.width = 768;
|
||||
a->c.height = 576;
|
||||
}
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
|
||||
a->bounds.left = 0;
|
||||
a->bounds.top = 0;
|
||||
if (priv->norm & V4L2_STD_NTSC) {
|
||||
a->bounds.width = 640;
|
||||
a->bounds.height = 480;
|
||||
} else {
|
||||
a->bounds.width = 768;
|
||||
a->bounds.height = 576;
|
||||
}
|
||||
a->defrect = a->bounds;
|
||||
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
a->pixelaspect.numerator = 1;
|
||||
a->pixelaspect.denominator = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_g_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
|
||||
if (!priv->scale) {
|
||||
priv->scale = tw9910_select_norm(priv->norm, 640, 480);
|
||||
if (!priv->scale)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mf->width = priv->scale->width;
|
||||
mf->height = priv->scale->height;
|
||||
mf->code = V4L2_MBUS_FMT_UYVY8_2X8;
|
||||
mf->colorspace = V4L2_COLORSPACE_JPEG;
|
||||
mf->field = V4L2_FIELD_INTERLACED_BT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_s_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
u32 width = mf->width, height = mf->height;
|
||||
int ret;
|
||||
|
||||
WARN_ON(mf->field != V4L2_FIELD_ANY &&
|
||||
mf->field != V4L2_FIELD_INTERLACED_BT);
|
||||
|
||||
/*
|
||||
* check color format
|
||||
*/
|
||||
if (mf->code != V4L2_MBUS_FMT_UYVY8_2X8)
|
||||
return -EINVAL;
|
||||
|
||||
mf->colorspace = V4L2_COLORSPACE_JPEG;
|
||||
|
||||
ret = tw9910_set_frame(sd, &width, &height);
|
||||
if (!ret) {
|
||||
mf->width = width;
|
||||
mf->height = height;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tw9910_try_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_framefmt *mf)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
const struct tw9910_scale_ctrl *scale;
|
||||
|
||||
if (V4L2_FIELD_ANY == mf->field) {
|
||||
mf->field = V4L2_FIELD_INTERLACED_BT;
|
||||
} else if (V4L2_FIELD_INTERLACED_BT != mf->field) {
|
||||
dev_err(&client->dev, "Field type %d invalid.\n", mf->field);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mf->code = V4L2_MBUS_FMT_UYVY8_2X8;
|
||||
mf->colorspace = V4L2_COLORSPACE_JPEG;
|
||||
|
||||
/*
|
||||
* select suitable norm
|
||||
*/
|
||||
scale = tw9910_select_norm(priv->norm, mf->width, mf->height);
|
||||
if (!scale)
|
||||
return -EINVAL;
|
||||
|
||||
mf->width = scale->width;
|
||||
mf->height = scale->height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_video_probe(struct i2c_client *client)
|
||||
{
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
s32 id;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* tw9910 only use 8 or 16 bit bus width
|
||||
*/
|
||||
if (SOCAM_DATAWIDTH_16 != priv->info->buswidth &&
|
||||
SOCAM_DATAWIDTH_8 != priv->info->buswidth) {
|
||||
dev_err(&client->dev, "bus width error\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = tw9910_s_power(&priv->subdev, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* check and show Product ID
|
||||
* So far only revisions 0 and 1 have been seen
|
||||
*/
|
||||
id = i2c_smbus_read_byte_data(client, ID);
|
||||
priv->revision = GET_REV(id);
|
||||
id = GET_ID(id);
|
||||
|
||||
if (0x0B != id ||
|
||||
0x01 < priv->revision) {
|
||||
dev_err(&client->dev,
|
||||
"Product ID error %x:%x\n",
|
||||
id, priv->revision);
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
dev_info(&client->dev,
|
||||
"tw9910 Product ID %0x:%0x\n", id, priv->revision);
|
||||
|
||||
priv->norm = V4L2_STD_NTSC;
|
||||
|
||||
done:
|
||||
tw9910_s_power(&priv->subdev, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct v4l2_subdev_core_ops tw9910_subdev_core_ops = {
|
||||
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||||
.g_register = tw9910_g_register,
|
||||
.s_register = tw9910_s_register,
|
||||
#endif
|
||||
.s_power = tw9910_s_power,
|
||||
};
|
||||
|
||||
static int tw9910_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
|
||||
enum v4l2_mbus_pixelcode *code)
|
||||
{
|
||||
if (index)
|
||||
return -EINVAL;
|
||||
|
||||
*code = V4L2_MBUS_FMT_UYVY8_2X8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_g_mbus_config(struct v4l2_subdev *sd,
|
||||
struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
|
||||
cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
|
||||
V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW |
|
||||
V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_LOW |
|
||||
V4L2_MBUS_DATA_ACTIVE_HIGH;
|
||||
cfg->type = V4L2_MBUS_PARALLEL;
|
||||
cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tw9910_s_mbus_config(struct v4l2_subdev *sd,
|
||||
const struct v4l2_mbus_config *cfg)
|
||||
{
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
|
||||
u8 val = VSSL_VVALID | HSSL_DVALID;
|
||||
unsigned long flags = soc_camera_apply_board_flags(ssdd, cfg);
|
||||
|
||||
/*
|
||||
* set OUTCTR1
|
||||
*
|
||||
* We use VVALID and DVALID signals to control VSYNC and HSYNC
|
||||
* outputs, in this mode their polarity is inverted.
|
||||
*/
|
||||
if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
|
||||
val |= HSP_HI;
|
||||
|
||||
if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
|
||||
val |= VSP_HI;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, OUTCTR1, val);
|
||||
}
|
||||
|
||||
static int tw9910_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm)
|
||||
{
|
||||
*norm = V4L2_STD_NTSC | V4L2_STD_PAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct v4l2_subdev_video_ops tw9910_subdev_video_ops = {
|
||||
.s_std = tw9910_s_std,
|
||||
.g_std = tw9910_g_std,
|
||||
.s_stream = tw9910_s_stream,
|
||||
.g_mbus_fmt = tw9910_g_fmt,
|
||||
.s_mbus_fmt = tw9910_s_fmt,
|
||||
.try_mbus_fmt = tw9910_try_fmt,
|
||||
.cropcap = tw9910_cropcap,
|
||||
.g_crop = tw9910_g_crop,
|
||||
.enum_mbus_fmt = tw9910_enum_fmt,
|
||||
.g_mbus_config = tw9910_g_mbus_config,
|
||||
.s_mbus_config = tw9910_s_mbus_config,
|
||||
.g_tvnorms = tw9910_g_tvnorms,
|
||||
};
|
||||
|
||||
static struct v4l2_subdev_ops tw9910_subdev_ops = {
|
||||
.core = &tw9910_subdev_core_ops,
|
||||
.video = &tw9910_subdev_video_ops,
|
||||
};
|
||||
|
||||
/*
|
||||
* i2c_driver function
|
||||
*/
|
||||
|
||||
static int tw9910_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *did)
|
||||
|
||||
{
|
||||
struct tw9910_priv *priv;
|
||||
struct tw9910_video_info *info;
|
||||
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 || !ssdd->drv_priv) {
|
||||
dev_err(&client->dev, "TW9910: missing platform data!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
info = ssdd->drv_priv;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(&client->dev,
|
||||
"I2C-Adapter doesn't support "
|
||||
"I2C_FUNC_SMBUS_BYTE_DATA\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->info = info;
|
||||
|
||||
v4l2_i2c_subdev_init(&priv->subdev, client, &tw9910_subdev_ops);
|
||||
|
||||
priv->clk = v4l2_clk_get(&client->dev, "mclk");
|
||||
if (IS_ERR(priv->clk))
|
||||
return PTR_ERR(priv->clk);
|
||||
|
||||
ret = tw9910_video_probe(client);
|
||||
if (ret < 0)
|
||||
v4l2_clk_put(priv->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tw9910_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tw9910_priv *priv = to_tw9910(client);
|
||||
v4l2_clk_put(priv->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id tw9910_id[] = {
|
||||
{ "tw9910", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tw9910_id);
|
||||
|
||||
static struct i2c_driver tw9910_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "tw9910",
|
||||
},
|
||||
.probe = tw9910_probe,
|
||||
.remove = tw9910_remove,
|
||||
.id_table = tw9910_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(tw9910_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SoC Camera driver for tw9910");
|
||||
MODULE_AUTHOR("Kuninori Morimoto");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Loading…
Add table
Add a link
Reference in a new issue