blob: 02c18a5c46f6520890c18ca406c06c5a4e1dbc71 [file] [log] [blame]
/*
* drivers/media/video/mt9t111.c
*
* mt9t111 sensor driver
*
* Copyright (C) 2010 Texas Instruments Inc
* Author: Vaibhav Hiremath <hvaibhav@ti.com>
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*/
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/v4l2-mediabus.h>
#include <media/v4l2-chip-ident.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <media/v4l2-ctrls.h>
#include <media/mt9t111.h>
#include "mt9t111_reg.h"
/*
* struct mt9t111 - main structure for storage of sensor information
* @subdev: V4L2 sub-device structure structure
* @pad: Media entity pad structure
* @format: Media-bus format
* @rect:
* @pix: V4L2 pixel format information structure
* @ctrls: v4l2 control handler
* @frameival: sub-dev pad frame interval
* @pdata: access functions and data for platform level information
* @ver: mt9t111 chip version
* @power: Turn on OR off the interface
*/
struct mt9t111 {
struct v4l2_subdev subdev;
struct media_pad pad;
struct v4l2_mbus_framefmt format;
struct v4l2_rect rect;
struct v4l2_pix_format pix;
struct v4l2_ctrl_handler ctrls;
struct mt9t111_platform_data *pdata;
int ver;
unsigned int power;
};
#define to_mt9t111(sd) container_of(sd, struct mt9t111, subdev)
/*
* struct mt9t111_fmt -
* @mbus_code: associated media bus code
* @fmt: format descriptor
*/
struct mt9t111_fmt {
unsigned int mbus_code;
struct v4l2_fmtdesc fmt;
};
/*
* List of image formats supported by mt9t111
* Currently we are using 8 bit and 8x2 bit mode only, but can be
* extended to 10 bit mode.
*/
static const struct mt9t111_fmt mt9t111_fmt_list[] = {
{
.mbus_code = V4L2_MBUS_FMT_UYVY8_2X8,
.fmt = {
.index = 0,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.flags = 0,
.description = "8-bit UYVY 4:2:2 Format",
.pixelformat = V4L2_PIX_FMT_UYVY,
},
},
{
.mbus_code = V4L2_MBUS_FMT_YUYV8_2X8,
.fmt = {
.index = 1,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.flags = 0,
.description = "8-bit YUYV 4:2:2 Format",
.pixelformat = V4L2_PIX_FMT_YUYV,
},
},
{
.mbus_code = V4L2_MBUS_FMT_RGB565_2X8_LE,
.fmt = {
.index = 2,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.flags = 0,
.description = "16-bit RGB565 format",
.pixelformat = V4L2_PIX_FMT_RGB565,
},
},
};
/*
* mt9t111_read_reg - Read a value from a register in an mt9t111 sensor device
* @client: i2c driver client structure
* @data_length: length of data to be read
* @reg: register address / offset
* @val: stores the value that gets read
*
* Read a value from a register in an mt9t111 sensor device.
* The value is returned in 'val'.
* Returns zero if successful, or non-zero otherwise.
*/
static int mt9t111_read_reg(struct i2c_client *client, u16 reg, u16 *val)
{
struct i2c_msg msg[1];
u8 data[4];
int err;
msg->addr = client->addr;
msg->flags = 0;
msg->len = 2;
msg->buf = data;
data[0] = (reg & 0xff00) >> 8;
data[1] = (reg & 0x00ff);
err = i2c_transfer(client->adapter, msg, 1);
if (err >= 0) {
msg->flags = I2C_M_RD;
msg->len = 2; /* 2 byte read */
err = i2c_transfer(client->adapter, msg, 1);
if (err >= 0) {
*val = ((data[0] & 0x00ff) << 8)
| (data[1] & 0x00ff);
return 0;
}
}
return err;
}
/*
* mt9t111_write_reg - Write a value to a register in an mt9t111 sensor device
* @client: i2c driver client structure
* @data_length: length of data to be read
* @reg: register address / offset
* @val: value to be written to specified register
*
* Write a value to a register in an mt9t111 sensor device.
* Returns zero if successful, or non-zero otherwise.
*/
static int mt9t111_write_reg(struct i2c_client *client, u16 reg, u16 val)
{
struct i2c_msg msg[1];
u8 data[20];
int err;
msg->addr = client->addr;
msg->flags = 0;
msg->len = 4;
msg->buf = data;
data[0] = (u8)((reg & 0xff00) >> 8);
data[1] = (u8)(reg & 0x00ff);
data[2] = (u8)((val & 0xff00) >> 8);
data[3] = (u8)(val & 0x00ff);
err = i2c_transfer(client->adapter, msg, 1);
if (err < 0)
return err;
return 0;
}
/*
* mt9t111_write_regs - Write registers to an mt9t111 sensor device
* @client: i2c driver client structure
* @reg_in: pointer to registers to write
* @cnt: the number of registers
*
* Write registers .
* Returns zero if successful, or non-zero otherwise.
*/
static int mt9t111_write_regs(struct i2c_client *client,
mt9t111_regs *reg_in, int cnt)
{
int err = 0, i;
mt9t111_regs *reg = reg_in;
for (i = 0; i < cnt; i++) {
if (reg->delay_time == 0) {
err |= mt9t111_write_reg(client, reg->addr, reg->data);
} else if (reg->addr != 0 || reg->data != 0) {
err |= mt9t111_write_reg(client, reg->addr, reg->data);
mdelay(reg->delay_time);
} else {
mdelay(reg->delay_time);
}
if (err < 0) {
dev_warn(&client->dev, "write reg error, addr = 0x%x,"
" data = 0x%x \n",
reg->addr, reg->data);
return err;
}
reg++;
}
return err;
}
static int mt9t111_refresh(struct i2c_client *client)
{
int i, err = 0;
unsigned short value;
/* MCU_ADDRESS [SEQ_CMD] -- refresh mode */
err = mt9t111_write_reg(client, 0x098E, 0x8400);
if (err)
return err;
err = mt9t111_write_reg(client, 0x0990, 0x0006);
if (err)
return err;
/* refresh command */
err = mt9t111_write_reg(client, 0x098E, 0x8400);
if (err)
return err;
err = mt9t111_write_reg(client, 0x0990, 0x0005);
if (err)
return err;
for (i = 0; i < 100; i++) {
err = mt9t111_write_reg(client, 0x098E, 0x8400);
if (err)
break;
err = mt9t111_read_reg(client, 0x0990, &value);
if ((value == 0) || (err))
break;
mdelay(5);
}
return err;
}
static int mt9t111_enable_pll(struct i2c_client *client)
{
int i, err = 0;
unsigned short value;
err = mt9t111_write_regs(client, pll_regs1,
sizeof(pll_regs1) / sizeof(mt9t111_regs));
if (err)
return err;
for (i = 0; i < 100; i++) {
err = mt9t111_read_reg(client, 0x0014, &value);
if (((value & 0x8000) != 0) || (err))
break;
mdelay(2);
}
err = mt9t111_write_regs(client, pll_regs2,
sizeof(pll_regs2) / sizeof(mt9t111_regs));
return err;
}
/*
* mt9t111_configure - Configure the mt9t111 for the specified image mode
* @s: pointer to standard V4L2 device structure
*
* Configure the mt9t111 for a specified image size, pixel format, and frame
* period. xclk is the frequency (in Hz) of the xclk input to the mt9t111.
* fper is the frame period (in seconds) expressed as a fraction.
* Returns zero if successful, or non-zero otherwise.
* The actual frame period is returned in fper.
*/
static int mt9t111_configure(struct v4l2_subdev *subdev)
{
struct i2c_client *client = v4l2_get_subdevdata(subdev);
int err = 0;
err = mt9t111_write_reg(client, 0x001A, 0x001D);
if (err < 0)
return err;
msleep(1);
err = mt9t111_write_reg(client, 0x001A, 0x0018);
if (err < 0)
goto out;
err = mt9t111_enable_pll(client);
if (err)
goto out;
// Enable Clock pad
err = mt9t111_write_reg(client, 0x0016, 0x0400);
err = mt9t111_write_regs(client, def_regs1,
sizeof(def_regs1) / sizeof(mt9t111_regs));
if (err)
goto out;
err = mt9t111_write_regs(client, patch_rev6,
sizeof(patch_rev6) / sizeof(mt9t111_regs));
if (err)
goto out;
err = mt9t111_write_regs(client, def_regs2,
sizeof(def_regs2) / sizeof(mt9t111_regs));
if (err)
goto out;
err = mt9t111_refresh(client);
out:
return err;
}
/*
* mt9t111_detect - Detect if an mt9t111 is present, and if so which revision
* @subdev: pointer to the V4L2 sub-device driver structure
*
* Detect if an mt9t111 is present
* Returns a negative error number if no device is detected, or the
* non-negative value of the version ID register if a device is detected.
*/
static int mt9t111_detect(struct v4l2_subdev *subdev)
{
u16 val;
struct i2c_client *client = v4l2_get_subdevdata(subdev);
/* chip ID is at address 0 */
if (mt9t111_read_reg(client, MT9T111_CHIP_ID, &val) < 0)
return -ENODEV;
if (val != MT9T111_CHIP_ID_VALUE) {
dev_err(&client->dev, "model id mismatch received 0x%x"
" expecting 0x%x\n",
val, MT9T111_CHIP_ID_VALUE);
return -ENODEV;
}
return (int)val;
}
/* --------------------------------------------------------------------------
* V4L2 subdev core operations
*/
static int mt9t111_g_chip_ident(struct v4l2_subdev *subdev,
struct v4l2_dbg_chip_ident *chip)
{
struct i2c_client *client = v4l2_get_subdevdata(subdev);
return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_MT9T111, 0);
}
/*
* mt9t111_dev_init - sensor init, tries to detect the sensor
* @subdev: pointer to standard V4L2 subdev structure
*/
static int mt9t111_dev_init(struct v4l2_subdev *subdev)
{
struct mt9t111 *mt9t111= to_mt9t111(subdev);
struct i2c_client *client = v4l2_get_subdevdata(subdev);
int rval;
rval = mt9t111->pdata->s_power(subdev, 1);
if (rval)
return rval;
rval = mt9t111_detect(subdev);
if (rval < 0) {
v4l_err(client, "Unable to detect"
MT9T111_MODULE_NAME "sensor\n");
goto out;
}
mt9t111->ver = rval;
v4l_info(client, MT9T111_MODULE_NAME
" chip version 0x%02x detected\n", mt9t111->ver);
rval = 0;
out:
mt9t111->pdata->s_power(subdev, 0);
return rval;
}
/*
* mt9t111_s_config - set the platform data for future use
* @subdev: pointer to standard V4L2 subdev structure
* @irq:
* @platform_data: sensor platform_data
*/
static int mt9t111_s_config(struct v4l2_subdev *subdev, int irq,
void *platform_data)
{
struct mt9t111 *mt9t111= to_mt9t111(subdev);
if (platform_data == NULL)
return -ENODEV;
mt9t111->pdata = platform_data;
return mt9t111_dev_init(subdev);
}
/*
* mt9t111_s_power - V4L2 sensor interface handler for s_power
* @s: pointer to standard V4L2 device structure
* @on: power state to which device is to be set
*
* Sets devices power state to requrested state, if possible.
*/
static int mt9t111_s_power(struct v4l2_subdev *subdev, int on)
{
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
struct i2c_client *client = v4l2_get_subdevdata(subdev);
int rval;
if (on) {
rval = mt9t111->pdata->s_power(subdev, 1);
if (rval)
goto out;
rval = mt9t111_configure(subdev);
if (rval) {
mt9t111->pdata->s_power(subdev, 0);
goto out;
}
} else {
rval = mt9t111->pdata->s_power(subdev, 0);
if (rval)
goto out;
}
mt9t111->power = on;
out:
if (rval)
v4l_err(client, "Unable to set target power state\n");
return rval;
}
/* --------------------------------------------------------------------------
* V4L2 subdev file operations
*/
static int mt9t111_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
{
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *crop;
/*
* Default configuration -
* Resolution: VGA
* Format: UYVY
* crop = window
*/
crop = v4l2_subdev_get_try_crop(fh, 0);
crop->left = 0;
crop->top = 0;
crop->width = 640;
crop->height = 480;
format = v4l2_subdev_get_try_format(fh, 0);
format->code = V4L2_MBUS_FMT_UYVY8_2X8;
format->width = 640;
format->height = 480;
format->field = V4L2_FIELD_NONE;
format->colorspace = V4L2_COLORSPACE_JPEG;
return 0;
}
/* --------------------------------------------------------------------------
* V4L2 subdev video operations
*/
static int mt9t111_s_stream(struct v4l2_subdev *subdev, int streaming)
{
/*
* FIXME: We should put here the specific reg setting to turn on
* streaming in sensor.
*/
return 0;
}
/* --------------------------------------------------------------------------
* V4L2 subdev pad operations
*/
/*
* mt9t111_enum_mbus_code - V4L2 sensor interface handler for pad_ops
* @subdev: pointer to standard V4L2 sub-device structure
* @qctrl: standard V4L2 VIDIOC_QUERYCTRL ioctl structure
*
* If the requested control is supported, returns the control information
* from the video_control[] array. Otherwise, returns -EINVAL if the
* control is not supported.
*/
static int mt9t111_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_mbus_code_enum *code)
{
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
if (code->index >= ARRAY_SIZE(mt9t111_fmt_list))
return -EINVAL;
code->code = mt9t111->format.code;
return 0;
}
static int mt9t111_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_frame_size_enum *fse)
{
int i;
/* Is requested media-bus format/pixelformat not found on sensor? */
for (i = 0; i < ARRAY_SIZE(mt9t111_fmt_list); i++) {
if (fse->code == mt9t111_fmt_list[i].mbus_code )
goto fmt_found;
}
if (i >= ARRAY_SIZE(mt9t111_fmt_list))
return -EINVAL;
fmt_found:
/*
* Currently only supports VGA resolution
*/
fse->min_width = fse->max_width = 640;
fse->min_height = fse->max_height = 480;
return 0;
}
static int mt9t111_get_pad_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
fmt->format = mt9t111->format;
return 0;
}
static int mt9t111_set_pad_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
int i;
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
for (i = 0; i < ARRAY_SIZE(mt9t111_fmt_list); i++) {
if (fmt->format.code == mt9t111_fmt_list[i].mbus_code )
goto fmt_found;
}
if (i >= ARRAY_SIZE(mt9t111_fmt_list))
return -EINVAL;
fmt_found:
/*
* Only VGA resolution supported
*/
fmt->format.width = 640;
fmt->format.height = 480;
fmt->format.field = V4L2_FIELD_NONE;
fmt->format.colorspace = V4L2_COLORSPACE_JPEG;
mt9t111->format = fmt->format;
return 0;
}
static int mt9t111_get_crop(struct v4l2_subdev *subdev,
struct v4l2_subdev_fh *fh, struct v4l2_subdev_crop *crop)
{
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
crop->rect = mt9t111->rect;
return 0;
}
static int mt9t111_set_crop(struct v4l2_subdev *subdev,
struct v4l2_subdev_fh *fh, struct v4l2_subdev_crop *crop)
{
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
struct v4l2_rect rect;
/*
* Only VGA resolution/window is supported
*/
rect.left = 0;
rect.top = 0;
rect.width = 640;
rect.height = 480;
mt9t111->rect = rect;
crop->rect = rect;
return 0;
}
static const struct v4l2_subdev_core_ops mt9t111_core_ops = {
.g_chip_ident = mt9t111_g_chip_ident,
.s_config = mt9t111_s_config,
.s_power = mt9t111_s_power,
};
static struct v4l2_subdev_file_ops mt9t111_subdev_file_ops = {
.open = mt9t111_open,
};
static const struct v4l2_subdev_video_ops mt9t111_video_ops = {
.s_stream = mt9t111_s_stream,
};
static const struct v4l2_subdev_pad_ops mt9t111_pad_ops = {
.enum_mbus_code = mt9t111_enum_mbus_code,
.enum_frame_size= mt9t111_enum_frame_size,
.get_fmt = mt9t111_get_pad_format,
.set_fmt = mt9t111_set_pad_format,
.get_crop = mt9t111_get_crop,
.set_crop = mt9t111_set_crop,
};
static const struct v4l2_subdev_ops mt9t111_ops = {
.core = &mt9t111_core_ops,
.file = &mt9t111_subdev_file_ops,
.video = &mt9t111_video_ops,
.pad = &mt9t111_pad_ops,
};
/*
* mt9t111_probe - sensor driver i2c probe handler
* @client: i2c driver client device structure
*
* Register sensor as an i2c client device and V4L2
* device.
*/
static int mt9t111_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct mt9t111 *mt9t111;
int ret;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
v4l_err(client, "mt9t111: I2C Adapter doesn't support" \
" I2C_FUNC_SMBUS_WORD\n");
return -EIO;
}
if (!client->dev.platform_data) {
v4l_err(client, "No platform data!!\n");
return -ENODEV;
}
mt9t111 = kzalloc(sizeof(*mt9t111), GFP_KERNEL);
if (mt9t111 == NULL) {
v4l_err(client, "Could not able to alocate memory!!\n");
return -ENOMEM;
}
mt9t111->pdata = client->dev.platform_data;
/*
* Default configuration -
* Resolution: VGA
* Format: UYVY
* crop = window
*/
mt9t111->rect.left = 0;
mt9t111->rect.top = 0;
mt9t111->rect.width = 640;
mt9t111->rect.height = 480;
mt9t111->format.code = V4L2_MBUS_FMT_UYVY8_2X8;
mt9t111->format.width = 640;
mt9t111->format.height = 480;
mt9t111->format.field = V4L2_FIELD_NONE;
mt9t111->format.colorspace = V4L2_COLORSPACE_JPEG;
v4l2_i2c_subdev_init(&mt9t111->subdev, client, &mt9t111_ops);
mt9t111->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
mt9t111->pad.flags = MEDIA_PAD_FLAG_OUTPUT;
ret = media_entity_init(&mt9t111->subdev.entity, 1, &mt9t111->pad, 0);
if (ret < 0)
kfree(mt9t111);
return ret;
}
/*
* mt9t111_remove - sensor driver i2c remove handler
* @client: i2c driver client device structure
*
* Unregister sensor as an i2c client device and V4L2
* device. Complement of mt9t111_probe().
*/
static int __exit mt9t111_remove(struct i2c_client *client)
{
struct v4l2_subdev *subdev = i2c_get_clientdata(client);
struct mt9t111 *mt9t111 = to_mt9t111(subdev);
v4l2_device_unregister_subdev(&mt9t111->subdev);
media_entity_cleanup(&mt9t111->subdev.entity);
kfree(mt9t111);
return 0;
}
static const struct i2c_device_id mt9t111_id[] = {
{ MT9T111_MODULE_NAME, 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, mt9t111_id);
static struct i2c_driver mt9t111_i2c_driver = {
.driver = {
.name = MT9T111_MODULE_NAME,
.owner = THIS_MODULE,
},
.probe = mt9t111_probe,
.remove = __exit_p(mt9t111_remove),
.id_table = mt9t111_id,
};
/*
* mt9t111_init - sensor driver module_init handler
*
* Registers driver as an i2c client driver. Returns 0 on success,
* error code otherwise.
*/
static int __init mt9t111_init(void)
{
return i2c_add_driver(&mt9t111_i2c_driver);
}
/*
* mt9t111_cleanup - sensor driver module_exit handler
*
* Unregisters/deletes driver as an i2c client driver.
* Complement of mt9t111_init.
*/
static void __exit mt9t111_cleanup(void)
{
i2c_del_driver(&mt9t111_i2c_driver);
}
module_init(mt9t111_init);
module_exit(mt9t111_cleanup);
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("mt9t111 camera sensor driver");
MODULE_LICENSE("GPL");