blob: 35e807ad4d9516589e2537bcf5f1f898d19cec18 [file] [log] [blame]
/* mt9v117.c
*
* Driver for aptina mt9v117
*
* Author : Julien BERAUD <julien.beraud@parrot.com>
*
* Date : 23/04/2014
*
*/
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/gcd.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/delay.h>
#include <media/mt9v117.h>
#include <media/v4l2-chip-ident.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include "mt9v117_patches.h"
MODULE_AUTHOR("Julien BERAUD <julien.beraud@parrot.com>");
MODULE_DESCRIPTION("kernel driver for mt9v117");
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-2)");
#define MT9V117_frameperiod_ns_DEF 36353853 //10000รท275074 ~= 27Fps
#define MIN_HORIZONTAL_BLANKING 210
#define MIN_VERTICAL_BLANKING 31
/* mt9v117 data structure */
struct mt9v117 {
struct v4l2_subdev sd;
struct i2c_client *i2c_client;
struct media_pad pad;
struct v4l2_mbus_framefmt format;
struct v4l2_ctrl_handler ctrls;
struct v4l2_ctrl *gains[4];
int input;
struct mt9v117_platform_data *pdata;
uint32_t frameperiod_ns; /* user desired frame rate */
uint32_t real_frameperiod_ns; /* real framerate */
uint16_t line_length;
uint16_t frame_length;
int isStreaming;
bool timings_uptodate;
int (*set_power)(int on);
};
static inline struct mt9v117 *to_mt9v117(struct v4l2_subdev *sd)
{
return container_of(sd, struct mt9v117, sd);
}
/* write a register */
static int mt9v117_write8(struct mt9v117 *mt9v117, u16 reg, u8 val)
{
struct i2c_client *client = mt9v117->i2c_client;
struct i2c_msg msg;
struct {
u16 reg;
u8 val;
} __packed buf;
int ret;
reg = swab16(reg);
buf.reg = reg;
buf.val = val;
msg.addr = client->addr;
msg.flags = 0;
msg.len = 3;
msg.buf = (u8 *)&buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "Failed writing register 0x%04x!\n", reg);
return ret;
}
return 0;
}
/* read a register */
static int mt9v117_read16(struct mt9v117 *mt9v117, u16 reg, u16 *val)
{
struct i2c_client *client = mt9v117->i2c_client;
int ret;
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = (u8 *)&reg,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = 2,
.buf = (u8 *)val,
},
};
reg = swab16(reg);
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
dev_err(&client->dev, "Failed reading register 0x%04x!\n", reg);
return ret;
}
*val = swab16(*val);
return 0;
}
/* write a register */
static int mt9v117_write16(struct mt9v117 *mt9v117, u16 reg, u16 val)
{
struct i2c_client *client = mt9v117->i2c_client;
struct i2c_msg msg;
struct {
u16 reg;
u16 val;
} __packed buf;
int ret;
buf.reg = swab16(reg);
buf.val = swab16(val);
msg.addr = client->addr;
msg.flags = 0;
msg.len = 4;
msg.buf = (u8 *)&buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "Failed writing register 0x%04x!\n", reg);
return ret;
}
return 0;
}
/* write a register */
static int mt9v117_write32(struct mt9v117 *mt9v117, u16 reg, u32 val)
{
struct i2c_client *client = mt9v117->i2c_client;
struct i2c_msg msg;
struct {
u16 reg;
u32 val;
} __packed buf;
int ret;
buf.reg = swab16(reg);
buf.val = swab32(val);
msg.addr = client->addr;
msg.flags = 0;
msg.len = 6;
msg.buf = (u8 *)&buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "Failed to write register 0x%04x!\n",
reg);
return ret;
}
return 0;
}
static int mt9v117_write_var8(struct mt9v117 *mt9v117, u16 var,
u16 offset, u16 value)
{
struct i2c_client *client = mt9v117->i2c_client;
u16 addr = 0x8000 | (var << 10) | offset;
if(var > 0x200) {
dev_err(&client->dev, "write var offset too high\n");
return -EINVAL;
} else
return mt9v117_write8(mt9v117, addr, value);
}
static int mt9v117_read_var16(struct mt9v117 *mt9v117, u16 var,
u16 offset, u16 *value)
{
struct i2c_client *client = mt9v117->i2c_client;
u16 addr = 0x8000 | (var << 10) | offset;
if(var > 0x200) {
dev_err(&client->dev, "read var offset too high\n");
return -EINVAL;
} else
return mt9v117_read16(mt9v117, addr, value);
}
static int mt9v117_write_var16(struct mt9v117 *mt9v117, u16 var,
u16 offset, u16 value)
{
struct i2c_client *client = mt9v117->i2c_client;
u16 addr = 0x8000 | (var << 10) | offset;
if(var > 0x200) {
dev_err(&client->dev, "write var offset too high\n");
return -EINVAL;
} else
return mt9v117_write16(mt9v117, addr, value);
}
static int mt9v117_write_var32(struct mt9v117 *mt9v117, u16 var,
u16 offset, u32 value)
{
struct i2c_client *client = mt9v117->i2c_client;
u16 addr = 0x8000 | (var << 10) | offset;
if(var > 0x200) {
dev_err(&client->dev, "write var offset too high\n");
return -EINVAL;
} else
return mt9v117_write32(mt9v117, addr, value);
}
static int mt9v117_write_patch(struct mt9v117 *mt9v117,
u8 *patch, u16 patch_len)
{
struct i2c_client *client = mt9v117->i2c_client;
struct i2c_msg msg;
int ret;
msg.addr = client->addr;
msg.flags = 0;
msg.len = patch_len;
msg.buf = patch;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "Failed to write patch\n");
return ret;
}
return 0;
}
/* setup functions */
static int mt9v117_config_change(struct mt9v117 *mt9v117)
{
u16 value;
mt9v117_write_var8(mt9v117, 23, 0x0000, 0x28);//SYSMGR_NEXT_STATE
mt9v117_write16(mt9v117, 0x0040, 0x8002);//COMMAND_REGISTER
msleep(500);
mt9v117_read16(mt9v117, 0x0040, &value);
if ((value & 0x0002) != 0)
v4l2_err(&mt9v117->sd,"command not complete...\n");
msleep(500);
mt9v117_read16(mt9v117, 0x0040, &value);
if ((value & 0x8000) == 0) {
v4l2_err(&mt9v117->sd,"config change failed...%x\n",value);
return -EPIPE;
}
return 0;
}
static int mt9v117_soft_reset(struct mt9v117 *mt9v117)
{
if (mt9v117_write16(mt9v117, 0x001A, 0x0001) < 0)
return -EPIPE;
if (mt9v117_write16(mt9v117, 0x001A, 0x0000) < 0)
return -EPIPE;
msleep (50);
return 0;
}
static int mt9v117_patch1(struct mt9v117 *mt9v117)
{
u16 value;
// correct image aberration due to low illumination conditions
// Errata item 2
mt9v117_write16(mt9v117, 0x301A, 0x10D0);
mt9v117_write16(mt9v117, 0x31C0, 0x1404);
mt9v117_write16(mt9v117, 0x3ED8, 0x879C);
mt9v117_write16(mt9v117, 0x3042, 0x20E1);
mt9v117_write16(mt9v117, 0x30D4, 0x8020);
mt9v117_write16(mt9v117, 0x30C0, 0x0026);
mt9v117_write16(mt9v117, 0x301A, 0x10D4);
// Errata item 6
mt9v117_write_var16(mt9v117, 10, 0x0002, 0x00D3);
mt9v117_write_var16(mt9v117, 18, 0x0078, 0x00A0);
mt9v117_write_var16(mt9v117, 18, 0x0076, 0x0140);
// Errata item 8
mt9v117_write_var16(mt9v117, 15, 0x0004, 0x00FC);
mt9v117_write_var16(mt9v117, 15, 0x0038, 0x007F);
mt9v117_write_var16(mt9v117, 15, 0x003A, 0x007F);
mt9v117_write_var16(mt9v117, 15, 0x003C, 0x007F);
mt9v117_write_var16(mt9v117, 15, 0x0004, 0x00F4);
// Load Patch 0403
mt9v117_write16(mt9v117, 0x0982, 0x0001); //ACCESS_CTL_STAT
mt9v117_write16(mt9v117, 0x098A, 0x7000); // PHYSICAL_ADDRESS_ACCESS
mt9v117_write_patch(mt9v117,patch_line1, sizeof(patch_line1));
mt9v117_write_patch(mt9v117,patch_line2, sizeof(patch_line2));
mt9v117_write_patch(mt9v117,patch_line3, sizeof(patch_line3));
mt9v117_write_patch(mt9v117,patch_line4, sizeof(patch_line4));
mt9v117_write_patch(mt9v117,patch_line5, sizeof(patch_line5));
mt9v117_write_patch(mt9v117,patch_line6, sizeof(patch_line6));
mt9v117_write_patch(mt9v117,patch_line7, sizeof(patch_line7));
mt9v117_write_patch(mt9v117,patch_line8, sizeof(patch_line8));
mt9v117_write_patch(mt9v117,patch_line9, sizeof(patch_line9));
mt9v117_write_patch(mt9v117,patch_line10, sizeof(patch_line10));
mt9v117_write_patch(mt9v117,patch_line11, sizeof(patch_line11));
mt9v117_write_patch(mt9v117,patch_line12, sizeof(patch_line12));
mt9v117_write_patch(mt9v117,patch_line13, sizeof(patch_line13));
mt9v117_write16(mt9v117, 0x098E, 0x0000); //LOGICAL_ADDRESS_ACCESS
// apply patch 0403
mt9v117_write_var16(mt9v117, 0x18, 0x0000, 0x05d8); //PATCHLDR_LOADER_ADDRESS
mt9v117_write_var16(mt9v117, 0x18, 0x0002, 0x0403); // PATCHLDR_PATCH_ID
mt9v117_write_var32(mt9v117, 0x18, 0x0004, 0x00430104); // PATCHLDR_FIRMWARE_ID doute
mt9v117_write16(mt9v117, 0x0040, 0x8001); //apply patch
msleep(500);
mt9v117_read16(mt9v117, 0x0040, &value);
if ((value & 0x8001) != 0x8000) {
v4l2_err(&mt9v117->sd,"mt9v117 patch apply failed...%x\n",value);
return -ENODEV;
}
return 0;
}
static int mt9v117_set_basic_settings(struct mt9v117 *mt9v117)
{
u32 ths = 50000;
u16 value;
//awb_pixel_threshold_count
mt9v117_write_var32(mt9v117,11,0x0040,ths);
// set ae_rule_algo to average
mt9v117_write_var16(mt9v117,9,0x4,0x0);
// pad slew more agressive
mt9v117_read16(mt9v117, 0x0030, &value);
value |= 0x0001; // data slew rate = 1 (default value 0)
value |= 0x0600; // clock slew rate = 6 (default value 4)
mt9v117_write16(mt9v117, 0x0030, value);
return 0;
}
static int mt9v117_update_timings(struct mt9v117 *mt9v117)
{
struct v4l2_mbus_framefmt *fmt = &mt9v117->format;
// find the best couple Line Length&frame_length to reach desired framerate
uint32_t lineLenght, frameLength;
uint32_t bestLineLenght = 1;
uint32_t bestFrameLength = 1;
int64_t error, bestError = -1;
uint64_t tmp64;
uint32_t frameperiod_ns;
uint32_t bestFramePeriod = 0;
uint32_t pixelClock = mt9v117->pdata->ext_clk_freq_hz / 2;
for (lineLenght = 640 + MIN_HORIZONTAL_BLANKING; lineLenght < 2048; lineLenght++) {
for (frameLength = fmt->height + MIN_VERTICAL_BLANKING; frameLength < 600; frameLength++) {
// compute framerate
tmp64 = (u64)lineLenght * frameLength * NSEC_PER_SEC;
frameperiod_ns = div_u64((u64)tmp64, pixelClock);
// compute error
error = abs64((u64)frameperiod_ns - mt9v117->frameperiod_ns);
if (((error < bestError) || (bestError < 0))) {
bestError = error;
bestLineLenght = lineLenght;
bestFrameLength = frameLength;
bestFramePeriod = frameperiod_ns;
}
if (frameperiod_ns > mt9v117->frameperiod_ns)
break;
}
}
/* check a solution has been found */
if (bestError < 0) {
v4l2_err(&mt9v117->sd, "error setting framerate\n");
return -1;
}
mt9v117->real_frameperiod_ns = bestFramePeriod;
mt9v117->line_length = bestLineLenght;
mt9v117->frame_length = bestFrameLength;
v4l2_info(&mt9v117->sd, "pixelClock %u\n", mt9v117->pdata->ext_clk_freq_hz);
v4l2_info(&mt9v117->sd, "setting framerate: frame period %u->%u, line length %u, frame length %u\n", mt9v117->frameperiod_ns, bestFramePeriod, bestLineLenght, bestFrameLength);
mt9v117->timings_uptodate = 1;
return 0;
}
static int mt9v117_blanking_write(struct mt9v117 *mt9v117)
{
uint32_t pixelClock = mt9v117->pdata->ext_clk_freq_hz / 2;
uint64_t tmp64;
uint32_t flicker_periods;
mt9v117_write_var16(mt9v117, 18, 0x10, mt9v117->line_length);
mt9v117_write_var16(mt9v117, 18, 0xE, mt9v117->frame_length);
// configure FD zone
mt9v117_write_var16(mt9v117, 18, 0x16,
pixelClock / mt9v117->line_length / 120); //FD Period 60Hz
mt9v117_write_var16(mt9v117, 18, 0x18,
pixelClock / mt9v117->line_length / 100); //FD Period 50Hz
tmp64 = ((u64)mt9v117->real_frameperiod_ns * 120);
flicker_periods = div_u64(tmp64, NSEC_PER_SEC);
mt9v117_write_var16(mt9v117, 18, 0x1A,
flicker_periods); //Max FD Zone 60Hz
mt9v117_write_var16(mt9v117, 18, 0x1E,
flicker_periods); //Target FD Zone 60Hz
tmp64 = ((u64)mt9v117->real_frameperiod_ns * 100);
flicker_periods = div_u64(tmp64, NSEC_PER_SEC);
mt9v117_write_var16(mt9v117, 18, 0x1C,
flicker_periods); //Max FD Zone 50Hz
mt9v117_write_var16(mt9v117, 18, 0x20,
flicker_periods); //Target FD Zone 50Hz
return 0;
}
static int mt9v117_set_VGA(struct mt9v117 *mt9v117)
{
// QVGA, NO Y-skipping, framerate up to 35 FPS
mt9v117_write_var16(mt9v117, 18, 0x0, 0x000C); //Row Start = 12
mt9v117_write_var16(mt9v117, 18, 0x2, 0x0010); //Column Start = 16
mt9v117_write_var16(mt9v117, 18, 0x4, 0x01F3); //Row End = 499
mt9v117_write_var16(mt9v117, 18, 0x6, 0x0297); //Column End = 663
mt9v117_write_var16(mt9v117, 18, 0x8, 0x0111); //Row Speed = 273
mt9v117_write_var16(mt9v117, 18, 0xA, 0x00A4); //Sensor_fine_IT_min = 164
mt9v117_write_var16(mt9v117, 18, 0xC, 0x02FA); //Sensor_fine_IT_max = 762
mt9v117_write_var16(mt9v117, 18, 0x12, 0x0031);//Sensor_fine_correction = 49
mt9v117_write_var16(mt9v117, 18, 0x14, 0x01E3);//Cpipe Last Row = 483
mt9v117_write_var16(mt9v117, 18, 0x28, 0x0000);//Read Mode = 3 - Mirror + Flip
mt9v117_write_var16(mt9v117, 18, 0x4C, 0x0280);//Crop Width = 640
mt9v117_write_var16(mt9v117, 18, 0x4E, 0x01E0);//Crop Height = 480
mt9v117_write_var8(mt9v117, 18, 0x50, 0x03); //Crop Mode = 3
mt9v117_write_var16(mt9v117, 18, 0x54, 0x0280);//Output Width = 640
mt9v117_write_var16(mt9v117, 18, 0x56, 0x01E0);//Output Height = 480
mt9v117_write_var16(mt9v117, 18, 0xEC, 0x0000);//AWB Window Xstart = 0
mt9v117_write_var16(mt9v117, 18, 0xEE, 0x0000);//AWB Window Ystart = 0
mt9v117_write_var16(mt9v117, 18, 0xF0, 0x027F);//AWB Window Xend = 639
mt9v117_write_var16(mt9v117, 18, 0xF2, 0x01DF);//AWB Window Yend = 479
mt9v117_write_var16(mt9v117, 18, 0xF4, 0x0002);//AE Window Xstart = 2
mt9v117_write_var16(mt9v117, 18, 0xF6, 0x0002);//AE Window Ystart = 2
mt9v117_write_var16(mt9v117, 18, 0xF8, 0x007F);//AE Window Xend = 127
mt9v117_write_var16(mt9v117, 18, 0xFA, 0x005F);//AE Window Yend = 95
return 0;
}
static int mt9v117_set_QVGA(struct mt9v117 *mt9v117)
{
// QVGA, Y-skipping, framerate up to 60 FPS
mt9v117_write_var16(mt9v117, 18, 0x0, 0x0008); //Row Start = 8
mt9v117_write_var16(mt9v117, 18, 0x2, 0x0010); //Column Start = 16
mt9v117_write_var16(mt9v117, 18, 0x4, 0x01F5); //Row End = 501
mt9v117_write_var16(mt9v117, 18, 0x6, 0x0297); //Column End = 663
mt9v117_write_var16(mt9v117, 18, 0x8, 0x0111); //Row Speed = 273
mt9v117_write_var16(mt9v117, 18, 0xA, 0x00A4); //Sensor_fine_IT_min = 164
mt9v117_write_var16(mt9v117, 18, 0xC, 0x02FA); //Sensor_fine_IT_max = 762
mt9v117_write_var16(mt9v117, 18, 0x12, 0x0031); //Sensor_fine_correction = 49
mt9v117_write_var16(mt9v117, 18, 0x14, 0x00F3); //Cpipe Last Row = 243
mt9v117_write_var16(mt9v117, 18, 0x28, 0x0007); //Read Mode = 6 - skipping + Mirror + Flip
mt9v117_write_var16(mt9v117, 18, 0x4C, 0x0280); //Crop Width = 640
mt9v117_write_var16(mt9v117, 18, 0x4E, 0x00F0); //Crop Height = 240
mt9v117_write_var8(mt9v117, 18, 0x50, 0x03); //Crop Mode = 3
mt9v117_write_var16(mt9v117, 18, 0x54, 0x0140); //Output Width = 320
mt9v117_write_var16(mt9v117, 18, 0x56, 0x00F0); //Output Height = 240
mt9v117_write_var16(mt9v117, 18, 0xEC, 0x0000); //AWB Window Xstart = 0
mt9v117_write_var16(mt9v117, 18, 0xEE, 0x0000); //AWB Window Ystart = 0
mt9v117_write_var16(mt9v117, 18, 0xF0, 0x013F); //AWB Window Xend = 319
mt9v117_write_var16(mt9v117, 18, 0xF2, 0x00EF); //AWB Window Yend = 239
mt9v117_write_var16(mt9v117, 18, 0xF4, 0x0002); //AE Window Xstart = 2
mt9v117_write_var16(mt9v117, 18, 0xF6, 0x0002); //AE Window Ystart = 2
mt9v117_write_var16(mt9v117, 18, 0xF8, 0x003F); //AE Window Xend = 63
mt9v117_write_var16(mt9v117, 18, 0xFA, 0x002F); //AE Window Yend = 47
return 0;
}
static int mt9v117_set_QCIF(struct mt9v117 *mt9v117)
{
// QCIF, Y-skipping, framerate up to 60 FPS
mt9v117_write_var16(mt9v117, 18, 0x0, 0x0008);//Row Start = 8
mt9v117_write_var16(mt9v117, 18, 0x2, 0x0010);//Column Start = 16
mt9v117_write_var16(mt9v117, 18, 0x4, 0x01F5);//Row End = 501
mt9v117_write_var16(mt9v117, 18, 0x6, 0x0297);//Column End = 663
mt9v117_write_var16(mt9v117, 18, 0x8, 0x0111);//Row Speed = 273
mt9v117_write_var16(mt9v117, 18, 0xA, 0x00A4);//Sensor_fine_IT_min = 164
mt9v117_write_var16(mt9v117, 18, 0xC, 0x02FA);//Sensor_fine_IT_max = 762
mt9v117_write_var16(mt9v117, 18, 0x12, 0x0031); //Sensor_fine_correction = 49
mt9v117_write_var16(mt9v117, 18, 0x14, 0x00F3); //Cpipe Last Row = 243
mt9v117_write_var16(mt9v117, 18, 0x28, 0x0007); //Read Mode = 7 Skipping + Mirror + Flip
mt9v117_write_var16(mt9v117, 18, 0x4C, 0x0280); //Crop Width = 640
mt9v117_write_var16(mt9v117, 18, 0x4E, 0x00F0); //Crop Height = 240
mt9v117_write_var8(mt9v117, 18, 0x50, 0x03); //Crop Mode = 3
mt9v117_write_var16(mt9v117, 18, 0x54, 0x00A0); //Output Width = 160
mt9v117_write_var16(mt9v117, 18, 0x56, 0x0078); //Output Height = 120
mt9v117_write_var16(mt9v117, 18, 0xEC, 0x0000); //AWB Window Xstart = 0
mt9v117_write_var16(mt9v117, 18, 0xEE, 0x0000); //AWB Window Ystart = 0
mt9v117_write_var16(mt9v117, 18, 0xF0, 0x00AF); //AWB Window Xend = 175
mt9v117_write_var16(mt9v117, 18, 0xF2, 0x008F); //AWB Window Yend = 143
mt9v117_write_var16(mt9v117, 18, 0xF4, 0x0002); //AE Window Xstart = 2
mt9v117_write_var16(mt9v117, 18, 0xF6, 0x0002); //AE Window Ystart = 2
mt9v117_write_var16(mt9v117, 18, 0xF8, 0x0022); //AE Window Xend = 34
mt9v117_write_var16(mt9v117, 18, 0xFA, 0x001B); //AE Window Yend = 27
return 0;
}
static int mt9v117_set_itu_bt656(struct mt9v117 *mt9v117, int itu_bt656)
{
u16 value;
mt9v117_read_var16(mt9v117,18,0x0058, &value);
if (itu_bt656)
value |= (1<<3);
else
value &= ~(1<<3);
mt9v117_write_var16(mt9v117,18,0x0058,value);
return 0;
}
/* v4l2 ops */
static struct v4l2_mbus_framefmt *
__mt9v117_get_fmt(struct mt9v117 *mt9v117,
struct v4l2_subdev_fh *fh,
unsigned int pad,
enum v4l2_subdev_format_whence which)
{
if(pad)
return NULL;
if (which == V4L2_SUBDEV_FORMAT_TRY)
return v4l2_subdev_get_try_format(fh, pad);
else
return &mt9v117->format;
}
static int mt9v117_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct mt9v117 *mt9v117 = to_mt9v117(sd);
struct v4l2_mbus_framefmt *format;
format = __mt9v117_get_fmt(mt9v117, fh, fmt->pad, fmt->which);
if (format == NULL)
return -EINVAL;
fmt->format = *format;
return 0;
}
static int mt9v117_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct mt9v117 *mt9v117 = to_mt9v117(sd);
struct v4l2_mbus_framefmt *format;
format = __mt9v117_get_fmt(mt9v117, fh, fmt->pad, fmt->which);
if(!format)
return -EINVAL;
if((fmt->format.width == 640 && fmt->format.height == 480) ||
(fmt->format.width == 320 && fmt->format.height == 240) ||
(fmt->format.width == 160 && fmt->format.height == 120)) {
format->width = fmt->format.width;
format->height = fmt->format.height;
fmt->format = *format;
} else
return -EINVAL;
mt9v117->timings_uptodate = 0;
return 0;
}
static int mt9v117_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_mbus_code_enum *code)
{
struct mt9v117 *mt9v117 = to_mt9v117(sd);
if (code->index)
return -EINVAL;
code->code = mt9v117->format.code;
return 0;
}
static int mt9v117_apply_config(struct mt9v117 *mt9v117)
{
struct v4l2_mbus_framefmt *format = &mt9v117->format;
int ret = 0;
ret = mt9v117_soft_reset(mt9v117);
mt9v117_patch1(mt9v117);
mt9v117_set_basic_settings(mt9v117);
ret = mt9v117_update_timings(mt9v117);
if (ret < 0) {
v4l2_err(&mt9v117->sd, "Unable to calculate Video Timing\n");
return ret;
}
switch (format->height) {
case 480:
ret = mt9v117_set_VGA(mt9v117);
break;
case 240:
ret = mt9v117_set_QVGA(mt9v117);
break;
case 120:
ret = mt9v117_set_QCIF(mt9v117);
break;
default:
v4l2_err(&mt9v117->sd, "resolution not supported\n");
return -EINVAL;
}
if(ret < 0) {
v4l2_err(&mt9v117->sd, "error setting resolution\n");
return ret;
}
mt9v117_blanking_write(mt9v117);
mt9v117_set_itu_bt656(mt9v117,1);
// apply settings
ret = mt9v117_config_change(mt9v117);
return ret;
}
static int mt9v117_s_stream(struct v4l2_subdev *sd, int enable)
{
struct mt9v117 *mt9v117 = to_mt9v117(sd);
int ret = 0;
if(enable) {
if(mt9v117->set_power)
mt9v117->set_power(MT9V117_POWER_ON);
msleep(500); /* time has to be confirmed */
ret = mt9v117_apply_config(mt9v117);
if (ret < 0) {
v4l2_err(&mt9v117->sd, "failed to apply config\n");
goto power_off;
}
mt9v117->isStreaming = true;
} else {
mt9v117->isStreaming = false;
mt9v117->set_power(MT9V117_POWER_OFF);
}
return 0;
power_off:
mt9v117->isStreaming = false;
mt9v117->set_power(MT9V117_POWER_OFF);
return ret;
}
static const struct v4l2_subdev_core_ops mt9v117_core_ops = {
};
static int mt9v117_g_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct mt9v117 *mt9v117 = to_mt9v117(sd);
unsigned g;
memset(fi, 0, sizeof(*fi));
fi->interval.denominator = NSEC_PER_SEC;
fi->interval.numerator = mt9v117->frameperiod_ns;
/* I could return there but I prefer to reduce the fraction first */
g = gcd(fi->interval.numerator, fi->interval.denominator);
fi->interval.numerator /= g;
fi->interval.denominator /= g;
return 0;
}
static int mt9v117_s_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct mt9v117 *mt9v117 = to_mt9v117(sd);
uint64_t tmp64;
int ret;
/* Protect against division by 0. */
if (fi->interval.denominator == 0)
fi->interval.denominator = 30;
if (fi->interval.numerator == 0)
fi->interval.numerator = 1;
tmp64 = (u64)fi->interval.numerator * NSEC_PER_SEC;
mt9v117->frameperiod_ns = div_u64((u64)tmp64, fi->interval.denominator);
mt9v117->timings_uptodate = 0;
if (!mt9v117->isStreaming) {
ret = mt9v117_update_timings(mt9v117);
if (ret < 0) {
v4l2_err(&mt9v117->sd,
"Unable to calculate Video Timing\n");
return ret;
}
}
return 0;
}
static const struct v4l2_subdev_video_ops mt9v117_video_ops = {
.s_stream = mt9v117_s_stream,
.g_frame_interval = mt9v117_g_frame_interval,
.s_frame_interval = mt9v117_s_frame_interval,
};
static const struct v4l2_subdev_pad_ops mt9v117_pad_ops = {
.get_fmt = mt9v117_get_fmt,
.set_fmt = mt9v117_set_fmt,
.enum_mbus_code = mt9v117_enum_mbus_code,
};
static const struct v4l2_subdev_ops mt9v117_ops = {
.core = &mt9v117_core_ops,
.video = &mt9v117_video_ops,
.pad = &mt9v117_pad_ops,
};
static int mt9v117_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct mt9v117 *mt9v117;
struct mt9v117_platform_data *pdata = NULL;
struct v4l2_subdev *sd;
int ret = -ENODEV;
struct v4l2_mbus_framefmt *format;
u16 model_id;
pdata = client->dev.platform_data;
if(!pdata)
return -EINVAL;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
ret = -EIO;
goto ei2c;
}
dev_info(&client->dev, "detecting mt9v117 client on address 0x%x\n",
client->addr << 1);
mt9v117 = kzalloc(sizeof(*mt9v117), GFP_KERNEL);
if(!mt9v117) {
v4l_err(client, "alloc failed for data structure\n");
ret = -ENOMEM;
goto enomem;
}
mt9v117->set_power = pdata->set_power;
mt9v117->pdata = client->dev.platform_data;
mt9v117->frameperiod_ns = MT9V117_frameperiod_ns_DEF;
mt9v117->i2c_client = client;
format = &mt9v117->format;
format->width = 640;
format->height = 480;
format->field = V4L2_FIELD_NONE;
format->code = V4L2_MBUS_FMT_UYVY8_2X8;
format->colorspace = V4L2_COLORSPACE_SMPTE170M;
sd = &mt9v117->sd;
v4l2_i2c_subdev_init(sd, client, &mt9v117_ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
mt9v117->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_init(&sd->entity, 1, &mt9v117->pad, 0);
if(ret < 0) {
v4l_err(client, "failed to init media entity\n");
goto emedia;
}
/*see if chip is present */
if(pdata->set_power) {
pdata->set_power(MT9V117_POWER_ON);
msleep(500);
}
mt9v117_read16(mt9v117, 0x3000, &model_id);
if(model_id != 0x2282) {
v4l2_err(sd, "Error Model ID = 0x%04X\n", model_id);
ret = -ENODEV;
goto echipident;
}
if(pdata->set_power)
pdata->set_power(MT9V117_POWER_OFF);
ret = mt9v117_update_timings(mt9v117);
if (ret < 0) {
v4l2_err(&mt9v117->sd, "Unable to calculate Video Timing\n");
goto echipident;
}
return ret;
echipident:
if(pdata->set_power)
pdata->set_power(MT9V117_POWER_OFF);
media_entity_cleanup(&sd->entity);
emedia:
kfree(mt9v117);
enomem:
ei2c:
return ret;
}
static int mt9v117_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct mt9v117 *mt9v117 = to_mt9v117(sd);
v4l2_device_unregister_subdev(sd);
media_entity_cleanup(&sd->entity);
kfree(mt9v117);
return 0;
}
/* ----------------------------------------------------------------------- */
static const struct i2c_device_id mt9v117_id[] = {
{ "mt9v117", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, mt9v117_id);
static struct i2c_driver mt9v117_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "mt9v117",
},
.probe = mt9v117_probe,
.remove = mt9v117_remove,
.id_table = mt9v117_id,
};
module_i2c_driver(mt9v117_driver);