From: Darius Augulis Soc camera driver for OV7670 sensor from OMNIVISION. Signed-off-by: Darius Augulis Index: linux-2.6.29-rc5/drivers/media/video/Kconfig =================================================================== --- linux-2.6.29-rc5.orig/drivers/media/video/Kconfig +++ linux-2.6.29-rc5/drivers/media/video/Kconfig @@ -779,6 +779,13 @@ config SOC_CAMERA_OV772X help This is a ov772x camera driver +config SOC_CAMERA_OV7670 + tristate "ov7670 support" + depends on SOC_CAMERA && I2C + help + This is a driver for OmniVision OV7670 VGA camera. + It currently only works with SoC Camera interface. + config VIDEO_PXA27x tristate "PXA27x Quick Capture Interface driver" depends on VIDEO_DEV && PXA27x && SOC_CAMERA Index: linux-2.6.29-rc5/drivers/media/video/Makefile =================================================================== --- linux-2.6.29-rc5.orig/drivers/media/video/Makefile +++ linux-2.6.29-rc5/drivers/media/video/Makefile @@ -104,7 +104,7 @@ obj-$(CONFIG_VIDEO_UPD64083) += upd64083 obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o -obj-$(CONFIG_VIDEO_OV7670) += ov7670.o +obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o @@ -144,6 +144,7 @@ obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o +obj-$(CONFIG_SOC_CAMERA_OV7670) += ov7670_soc.o obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o Index: linux-2.6.29-rc5/drivers/media/video/ov7670_soc.c =================================================================== --- /dev/null +++ linux-2.6.29-rc5/drivers/media/video/ov7670_soc.c @@ -0,0 +1,1411 @@ +/* + * Driver for OV7670 CMOS Image Sensor from OmniVision + * + * Copyright (C) 2008, Darius Augulis + * + * 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. + */ + +/* Remove comment to compile in debug messages */ +//#define DEBUG + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* OV7670 has slave I2C address 0x42 */ +#define OV7670_I2C_ADDR 0x42 + +/* OV7670 registers */ +#define OV7670_PID 0x0A /* 76 R */ +#define OV7670_VER 0x0B /* 73 R */ +#define OV7670_COM7 0x12 /* 0 RW */ +#define OV7670_COM10 0x15 /* 0 RW */ +#define OV7670_MIDH 0x1C /* 7F R */ +#define OV7670_MIDL 0x1D /* A2 R */ +#define OV7670_MVFP 0x1E /* 1 RW */ +#define OV7670_BRIGHT 0x55 /* 0 RW */ +#define OV7670_CONTRAS 0x56 /* 40 RW */ + +static const struct soc_camera_data_format ov7670_formats[] = { + { + .name = "YUV 4:2:2", + .depth = 16, + .fourcc = V4L2_PIX_FMT_YUYV, + .colorspace = V4L2_COLORSPACE_SRGB, + } +}; + +struct ov7670 { + struct i2c_client *client; + struct soc_camera_device icd; + int model; +}; + +/* Camera sensor register access functions */ +static u8 reg_read(struct i2c_client *client, const u8 reg) +{ + u8 i2c_buf[2]; + i2c_buf[0] = reg; + i2c_master_send(client, i2c_buf, 1); + i2c_master_recv(client, i2c_buf, 1); + return i2c_buf[0]; +} + +static u8 reg_write(struct i2c_client *client, const u8 reg, u8 data) +{ + u8 i2c_buf[2]; + i2c_buf[0] = reg; + i2c_buf[1] = data; + return i2c_master_send(client, i2c_buf, 2); +} + +/* YCbCr, VGA * 15fps @ 24MHz input clock + */ +u8 init_array_vga[][2] = { + { 0x11, 0x03 }, + { 0x3a, 0x04 }, + { 0x12, 0x00 }, + { 0x17, 0x13 }, + { 0x18, 0x01 }, + { 0x32, 0xb6 }, + { 0x19, 0x02 }, + { 0x1a, 0x7a }, + { 0x03, 0x0a }, + { 0x0c, 0x00 }, + { 0x3e, 0x00 }, + { 0x70, 0x3a }, + { 0x71, 0x35 }, + { 0x72, 0x11 }, + { 0x73, 0xf0 }, + { 0xa2, 0x02 }, + + { 0x7a, 0x20 }, + { 0x7b, 0x10 }, + { 0x7c, 0x1e }, + { 0x7d, 0x35 }, + { 0x7e, 0x5a }, + { 0x7f, 0x69 }, + { 0x80, 0x76 }, + { 0x81, 0x80 }, + { 0x82, 0x88 }, + { 0x83, 0x8f }, + { 0x84, 0x96 }, + { 0x85, 0xa3 }, + { 0x86, 0xaf }, + { 0x87, 0xc4 }, + { 0x88, 0xd7 }, + { 0x89, 0xe8 }, + + { 0x13, 0xe0 }, + { 0x00, 0x00 }, + { 0x10, 0x00 }, + { 0x0d, 0x40 }, + { 0x14, 0x18 }, + { 0xa5, 0x05 }, + { 0xab, 0x07 }, + { 0x24, 0x95 }, + { 0x25, 0x33 }, + { 0x26, 0xe3 }, + { 0x9f, 0x78 }, + { 0xa0, 0x68 }, + { 0xa1, 0x03 }, + { 0xa6, 0xd8 }, + { 0xa7, 0xd8 }, + { 0xa8, 0xf0 }, + { 0xa9, 0x90 }, + { 0xaa, 0x94 }, + { 0x13, 0xe5 }, + + { 0x0e, 0x61 }, + { 0x0f, 0x4b }, + { 0x16, 0x02 }, + { 0x1e, 0x07 }, + { 0x21, 0x02 }, + { 0x22, 0x91 }, + { 0x29, 0x07 }, + { 0x33, 0x0b }, + { 0x35, 0x0b }, + { 0x37, 0x1d }, + { 0x38, 0x71 }, + { 0x39, 0x2a }, + { 0x3c, 0x78 }, + { 0x4d, 0x40 }, + { 0x4e, 0x20 }, + { 0x69, 0x00 }, + { 0x6b, 0x4a }, + { 0x74, 0x10 }, + { 0x8d, 0x4f }, + { 0x8e, 0x00 }, + { 0x8f, 0x00 }, + { 0x90, 0x00 }, + { 0x91, 0x00 }, + { 0x96, 0x00 }, + { 0x9a, 0x80 }, + { 0xb0, 0x84 }, + { 0xb1, 0x0c }, + { 0xb2, 0x0e }, + { 0xb3, 0x82 }, + { 0xb8, 0x0a }, + + { 0x43, 0x0a }, + { 0x44, 0xf0 }, + { 0x45, 0x34 }, + { 0x46, 0x58 }, + { 0x47, 0x28 }, + { 0x48, 0x3a }, + { 0x59, 0x88 }, + { 0x5a, 0x88 }, + { 0x5b, 0x44 }, + { 0x5c, 0x67 }, + { 0x5d, 0x49 }, + { 0x5e, 0x0e }, + { 0x6c, 0x0a }, + { 0x6d, 0x55 }, + { 0x6e, 0x11 }, + { 0x6f, 0x9f }, /* 9e */ + { 0x6a, 0x40 }, + { 0x01, 0x40 }, + { 0x02, 0x40 }, + { 0x13, 0xe7 }, + + { 0x4f, 0x80 }, + { 0x50, 0x80 }, + { 0x51, 0x00 }, + { 0x52, 0x22 }, + { 0x53, 0x5e }, + { 0x54, 0x80 }, + { 0x58, 0x9e }, + + { 0x41, 0x08 }, + { 0x3f, 0x00 }, + { 0x75, 0x05 }, + { 0x76, 0xe1 }, + { 0x4c, 0x00 }, + { 0x77, 0x01 }, + { 0x3d, 0xc2 }, + { 0x4b, 0x09 }, + { 0xc9, 0x60 }, + { 0x41, 0x38 }, + { 0x56, 0x40 }, + + { 0x34, 0x11 }, + { 0x3b, 0x02 }, + { 0xa4, 0x88 }, + { 0x96, 0x00 }, + { 0x97, 0x30 }, + { 0x98, 0x20 }, + { 0x99, 0x30 }, + { 0x9a, 0x84 }, + { 0x9b, 0x29 }, + { 0x9c, 0x03 }, + { 0x9d, 0x4c }, + { 0x9e, 0x3f }, + { 0x78, 0x04 }, +}; + +/* YCbCr, QVGA + * 15fps @ 24MHz input clock + */ +u8 init_array_qvga[][2] = { + { 0x11, 0x03 }, + { 0x3a, 0x04 }, + { 0x12, 0x00 }, + { 0x32, 0x80 }, + { 0x17, 0x16 }, + { 0x18, 0x04 }, + { 0x19, 0x02 }, + { 0x1a, 0x7a }, + { 0x03, 0x0a }, + { 0x0c, 0x04 }, + { 0x3e, 0x19 }, + { 0x70, 0x3a }, + { 0x71, 0x35 }, + { 0x72, 0x11 }, + { 0x73, 0xf1 }, + { 0xa2, 0x02 }, + + { 0x7a, 0x20 }, + { 0x7b, 0x10 }, + { 0x7c, 0x1e }, + { 0x7d, 0x35 }, + { 0x7e, 0x5a }, + { 0x7f, 0x69 }, + { 0x80, 0x76 }, + { 0x81, 0x80 }, + { 0x82, 0x88 }, + { 0x83, 0x8f }, + { 0x84, 0x96 }, + { 0x85, 0xa3 }, + { 0x86, 0xaf }, + { 0x87, 0xc4 }, + { 0x88, 0xd7 }, + { 0x89, 0xe8 }, + + { 0x13, 0xe0 }, + { 0x00, 0x00 }, + { 0x10, 0x00 }, + { 0x0d, 0x40 }, + { 0x14, 0x18 }, + { 0xa5, 0x05 }, + { 0xab, 0x07 }, + { 0x24, 0x95 }, + { 0x25, 0x33 }, + { 0x26, 0xe3 }, + { 0x9f, 0x78 }, + { 0xa0, 0x68 }, + { 0xa1, 0x03 }, + { 0xa6, 0xd8 }, + { 0xa7, 0xd8 }, + { 0xa8, 0xf0 }, + { 0xa9, 0x90 }, + { 0xaa, 0x94 }, + { 0x13, 0xe5 }, + + { 0x0e, 0x61 }, + { 0x0f, 0x4b }, + { 0x16, 0x02 }, + { 0x1e, 0x07 }, + { 0x21, 0x02 }, + { 0x22, 0x91 }, + { 0x29, 0x07 }, + { 0x33, 0x0b }, + { 0x35, 0x0b }, + { 0x37, 0x1d }, + { 0x38, 0x71 }, + { 0x39, 0x2a }, + { 0x3c, 0x78 }, + { 0x4d, 0x40 }, + { 0x4e, 0x20 }, + { 0x69, 0x00 }, + { 0x6b, 0x4a }, + { 0x74, 0x10 }, + { 0x8d, 0x4f }, + { 0x8e, 0x00 }, + { 0x8f, 0x00 }, + { 0x90, 0x00 }, + { 0x91, 0x00 }, + { 0x96, 0x00 }, + { 0x9a, 0x80 }, + { 0xb0, 0x84 }, + { 0xb1, 0x0c }, + { 0xb2, 0x0e }, + { 0xb3, 0x82 }, + { 0xb8, 0x0a }, + + { 0x43, 0x0a }, + { 0x44, 0xf0 }, + { 0x45, 0x34 }, + { 0x46, 0x58 }, + { 0x47, 0x28 }, + { 0x48, 0x3a }, + { 0x59, 0x88 }, + { 0x5a, 0x88 }, + { 0x5b, 0x44 }, + { 0x5c, 0x67 }, + { 0x5d, 0x49 }, + { 0x5e, 0x0e }, + { 0x6c, 0x0a }, + { 0x6d, 0x55 }, + { 0x6e, 0x11 }, + { 0x6f, 0x9f }, /*9e*/ + { 0x6a, 0x40 }, + { 0x01, 0x40 }, + { 0x02, 0x40 }, + { 0x13, 0xe7 }, + + { 0x4f, 0x80 }, + { 0x50, 0x80 }, + { 0x51, 0x00 }, + { 0x52, 0x22 }, + { 0x53, 0x5e }, + { 0x54, 0x80 }, + { 0x58, 0x9e }, + + { 0x41, 0x08 }, + { 0x3f, 0x00 }, + { 0x75, 0x05 }, + { 0x76, 0xe1 }, + { 0x4c, 0x00 }, + { 0x77, 0x01 }, + { 0x3d, 0xc0 }, + { 0x4b, 0x09 }, + { 0xc9, 0x60 }, + { 0x41, 0x38 }, + { 0x56, 0x40 }, + + { 0x34, 0x11 }, + { 0x3b, 0x02 }, + { 0xa4, 0x88 }, + { 0x96, 0x00 }, + { 0x97, 0x30 }, + { 0x98, 0x20 }, + { 0x99, 0x30 }, + { 0x9a, 0x84 }, + { 0x9b, 0x29 }, + { 0x9c, 0x03 }, + { 0x9d, 0x4c }, + { 0x9e, 0x3f }, + { 0x78, 0x04 }, + + { 0x79, 0x01 }, + { 0xc8, 0xf0 }, + { 0x79, 0x0f }, + { 0xc8, 0x00 }, + { 0x79, 0x10 }, + { 0xc8, 0x7e }, + { 0x79, 0x0a }, + { 0xc8, 0x80 }, + { 0x79, 0x0b }, + { 0xc8, 0x01 }, + { 0x79, 0x0c }, + { 0xc8, 0x0f }, + { 0x79, 0x0d }, + { 0xc8, 0x20 }, + { 0x79, 0x09 }, + { 0xc8, 0x80 }, + { 0x79, 0x02 }, + { 0xc8, 0xc0 }, + { 0x79, 0x03 }, + { 0xc8, 0x40 }, + { 0x79, 0x05 }, + { 0xc8, 0x30 }, + { 0x79, 0x26 }, +}; + +/* Not tested */ +u8 init_array_qqvga[][2] = { + { 0x11, 0x03}, /* CLKRC */ + { 0x3a, 0x04 }, /* TSLB */ + { 0x12, 0x00 }, /* COM7 */ + { 0x17, 0x16 }, /* HSTART */ + { 0x18, 0x04 }, /* HSTOP */ + { 0x32, 0xA4 }, /* HREF */ + { 0x19, 0x02 }, /* VSTRT */ + { 0x1a, 0x7a }, /* VSTOP */ + { 0x03, 0x0a }, /* VREF */ + { 0x0c, 0x04 }, /* COM3 */ + { 0x3e, 0x1A }, /* COM14 */ + { 0x70, 0x3a }, /* SCALING_XSC */ + { 0x71, 0x35 }, /* SCALING_YSC */ + { 0x72, 0x22 }, /* SCALING_DCWCTR */ + { 0x73, 0xf2 }, /* SCALING_PCLK_DIV */ + { 0xa2, 0x02 }, /* SCALING_PCLK_DELAY */ + /* Gamma */ + { 0x7a, 0x20 }, + { 0x7b, 0x1c }, + { 0x7c, 0x28 }, + { 0x7d, 0x3c }, + { 0x7e, 0x5a }, + { 0x7f, 0x68 }, + { 0x80, 0x76 }, + { 0x81, 0x80 }, + { 0x82, 0x88 }, + { 0x83, 0x8f }, + { 0x84, 0x96 }, + { 0x85, 0xa3 }, + { 0x86, 0xaf }, + { 0x87, 0xc4 }, + { 0x88, 0xd7 }, + { 0x89, 0xe8 }, + /* Gain, exposure, banding, AGC/AEC */ + { 0x13, 0xe0 }, + { 0x00, 0x00 }, + { 0x10, 0x00 }, + { 0x0d, 0x40 }, + { 0x14, 0x18 }, + { 0xa5, 0x05 }, + { 0xab, 0x07 }, + { 0x24, 0x95 }, + { 0x25, 0x33 }, + { 0x26, 0xe3 }, + { 0x9f, 0x78 }, + { 0xa0, 0x68 }, + { 0xa1, 0x03 }, + { 0xa6, 0xd8 }, + { 0xa7, 0xd8 }, + { 0xa8, 0xf0 }, + { 0xa9, 0x90 }, + { 0xaa, 0x94 }, + { 0x13, 0xe5 }, + /* Gain, ADC, PLL, etc... */ + { 0x0e, 0x61 }, + { 0x0f, 0x4b }, + { 0x16, 0x02 }, + { 0x1E, 0x07 }, + { 0x21, 0x02 }, + { 0x22, 0x91 }, + { 0x29, 0x07 }, + { 0x33, 0x0B }, + { 0x35, 0x0B }, + { 0x37, 0x1D }, + { 0x38, 0x71 }, + { 0x39, 0x2a }, + { 0x3c, 0x78 }, + { 0x4d, 0x40 }, + { 0x4e, 0x20 }, + { 0x69, 0x00 }, + { 0x74, 0x10 }, + { 0x8d, 0x4f }, + { 0x8e, 0x00 }, + { 0x8f, 0x00 }, + { 0x90, 0x00 }, + { 0x91, 0x00 }, + { 0x96, 0x00 }, + { 0x9a, 0x80 }, + { 0xb0, 0x84 }, + { 0xb1, 0x0c }, + { 0xb2, 0x0e }, + { 0xb3, 0x82 }, + { 0xb8, 0x0a }, + /* Reserved, AWB, Blue-Red Channel gain */ + { 0x43, 0x0a }, + { 0x44, 0xf0 }, + { 0x45, 0x34 }, + { 0x46, 0x58 }, + { 0x47, 0x28 }, + { 0x48, 0x3a }, + { 0x59, 0x88 }, + { 0x5a, 0x88 }, + { 0x5b, 0x44 }, + { 0x5c, 0x67 }, + { 0x5d, 0x49 }, + { 0x5e, 0x0e }, + { 0x6c, 0x0a }, + { 0x6d, 0x55 }, + { 0x6e, 0x11 }, + { 0x6f, 0x9f }, + { 0x6a, 0x40 }, + { 0x01, 0x40 }, + { 0x02, 0x40 }, + { 0x13, 0xe7 }, + /* Matrix coefficient */ + { 0x4f, 0x80 }, + { 0x50, 0x80 }, + { 0x51, 0x00 }, + { 0x52, 0x22 }, + { 0x53, 0x5e }, + { 0x54, 0x80 }, + { 0x58, 0x9e }, + /* Edge, pixel correction, de-noise, COM13, UV, Contrast */ + { 0x41, 0x08 }, + { 0x3f, 0x00 }, + { 0x75, 0x05 }, + { 0x76, 0xe1 }, + { 0x4c, 0x00 }, + { 0x77, 0x01 }, + { 0x3d, 0xc1 }, + { 0x4b, 0x09 }, + { 0xc9, 0x60 }, + { 0x41, 0x38 }, + { 0x56, 0x40 }, + /* Exposure timing, banding filter */ + { 0x34, 0x11 }, + { 0x3b, 0x02 }, + { 0xa4, 0x88 }, + { 0x96, 0x00 }, + { 0x97, 0x30 }, + { 0x98, 0x20 }, + { 0x99, 0x30 }, + { 0x9a, 0x84 }, + { 0x9b, 0x29 }, + { 0x9c, 0x03 }, + { 0x9d, 0x4c }, + { 0x9e, 0x3f }, + { 0x78, 0x04 }, + /* ??? */ + { 0x79, 0x01 }, + { 0xc8, 0xf0 }, + { 0x79, 0x0f }, + { 0xc8, 0x20 }, + { 0x79, 0x10 }, + { 0xc8, 0x7e }, + { 0x79, 0x0b }, + { 0xc8, 0x01 }, + { 0x79, 0x0c }, + { 0xc8, 0x07 }, + { 0x79, 0x0d }, + { 0xc8, 0x20 }, + { 0x79, 0x09 }, + { 0xc8, 0x80 }, + { 0x79, 0x02 }, + { 0xc8, 0xc0 }, + { 0x79, 0x03 }, + { 0xc8, 0x40 }, + { 0x79, 0x05 }, + { 0xc8, 0x30 }, + { 0x79, 0x26 }, + /* Lens correction */ + { 0x64, 0x50 }, + { 0x65, 0x50 }, + { 0x66, 0x01 }, + { 0x94, 0x50 }, + { 0x95, 0x50 }, +}; + +/* Not tested */ +u8 init_array_cif[][2] = { + { 0x11, 0x03}, /* CLKRC */ + { 0x3a, 0x04 }, /* TSLB */ + { 0x12, 0x00 }, /* COM7 */ + { 0x17, 0x15 }, /* HSTART */ + { 0x18, 0x0b }, /* HSTOP */ + { 0x32, 0xb6 }, /* HREF */ + { 0x19, 0x03 }, /* VSTRT */ + { 0x1a, 0x7b }, /* VSTOP */ + { 0x03, 0x02 }, /* VREF */ + { 0x0c, 0x08 }, /* COM3 */ + { 0x3e, 0x11 }, /* COM14 */ + { 0x70, 0x3a }, /* SCALING_XSC */ + { 0x71, 0x35 }, /* SCALING_YSC */ + { 0x72, 0x11 }, /* SCALING_DCWCTR */ + { 0x73, 0xf1 }, /* SCALING_PCLK_DIV */ + { 0xa2, 0x02 }, /* SCALING_PCLK_DELAY */ + /* Gamma */ + { 0x7a, 0x20 }, + { 0x7b, 0x10 }, + { 0x7c, 0x1e }, + { 0x7d, 0x35 }, + { 0x7e, 0x5a }, + { 0x7f, 0x69 }, + { 0x80, 0x76 }, + { 0x81, 0x80 }, + { 0x82, 0x88 }, + { 0x83, 0x8f }, + { 0x84, 0x96 }, + { 0x85, 0xa3 }, + { 0x86, 0xaf }, + { 0x87, 0xc4 }, + { 0x88, 0xd7 }, + { 0x89, 0xe8 }, + /* Gain, exposure, banding, AGC/AEC */ + { 0x13, 0xe0 }, + { 0x00, 0x00 }, + { 0x10, 0x00 }, + { 0x0d, 0x40 }, + { 0x14, 0x18 }, + { 0xa5, 0x05 }, + { 0xab, 0x07 }, + { 0x24, 0x95 }, + { 0x25, 0x33 }, + { 0x26, 0xe3 }, + { 0x9f, 0x78 }, + { 0xa0, 0x68 }, + { 0xa1, 0x03 }, + { 0xa6, 0xd8 }, + { 0xa7, 0xd8 }, + { 0xa8, 0xf0 }, + { 0xa9, 0x90 }, + { 0xaa, 0x94 }, + { 0x13, 0xe5 }, + /* Gain, ADC, PLL, etc... */ + { 0x0e, 0x61 }, + { 0x0f, 0x4b }, + { 0x16, 0x02 }, + { 0x1E, 0x07 }, + { 0x21, 0x02 }, + { 0x22, 0x91 }, + { 0x29, 0x07 }, + { 0x33, 0x0B }, + { 0x35, 0x0B }, + { 0x37, 0x1D }, + { 0x38, 0x71 }, + { 0x39, 0x2a }, + { 0x3c, 0x78 }, + { 0x4d, 0x40 }, + { 0x4e, 0x20 }, + { 0x69, 0x00 }, + { 0x74, 0x10 }, + { 0x8d, 0x4f }, + { 0x8e, 0x00 }, + { 0x8f, 0x00 }, + { 0x90, 0x00 }, + { 0x91, 0x00 }, + { 0x96, 0x00 }, + { 0x9a, 0x80 }, + { 0xb0, 0x84 }, + { 0xb1, 0x0c }, + { 0xb2, 0x0e }, + { 0xb3, 0x82 }, + { 0xb8, 0x0a }, + /* Reserved, AWB, Blue-Red Channel gain */ + { 0x43, 0x0a }, + { 0x44, 0xf0 }, + { 0x45, 0x34 }, + { 0x46, 0x58 }, + { 0x47, 0x28 }, + { 0x48, 0x3a }, + { 0x59, 0x88 }, + { 0x5a, 0x88 }, + { 0x5b, 0x44 }, + { 0x5c, 0x67 }, + { 0x5d, 0x49 }, + { 0x5e, 0x0e }, + { 0x6c, 0x0a }, + { 0x6d, 0x55 }, + { 0x6e, 0x11 }, + { 0x6f, 0x9f }, + { 0x6a, 0x40 }, + { 0x01, 0x40 }, + { 0x02, 0x40 }, + { 0x13, 0xe7 }, + /* Matrix coefficient */ + { 0x4f, 0x80 }, + { 0x50, 0x80 }, + { 0x51, 0x00 }, + { 0x52, 0x22 }, + { 0x53, 0x5e }, + { 0x54, 0x80 }, + { 0x58, 0x9e }, + /* Edge, pixel correction, de-noise, COM13, UV, Contrast */ + { 0x41, 0x08 }, + { 0x3f, 0x00 }, + { 0x75, 0x05 }, + { 0x76, 0xe1 }, + { 0x4c, 0x00 }, + { 0x77, 0x01 }, + { 0x3d, 0xc0 }, + { 0x4b, 0x09 }, + { 0xc9, 0x60 }, + { 0x41, 0x38 }, + { 0x56, 0x40 }, + /* Exposure timing, banding filter */ + { 0x34, 0x11 }, + { 0x3b, 0x02 }, + { 0xa4, 0x88 }, + { 0x96, 0x00 }, + { 0x97, 0x30 }, + { 0x98, 0x20 }, + { 0x99, 0x30 }, + { 0x9a, 0x84 }, + { 0x9b, 0x29 }, + { 0x9c, 0x03 }, + { 0x9d, 0x4c }, + { 0x9e, 0x3f }, + { 0x78, 0x04 }, + /* ??? */ + { 0x79, 0x01 }, + { 0xc8, 0xf0 }, + { 0x79, 0x0f }, + { 0xc8, 0x20 }, + { 0x79, 0x10 }, + { 0xc8, 0x7e }, + { 0x79, 0x0b }, + { 0xc8, 0x01 }, + { 0x79, 0x0c }, + { 0xc8, 0x07 }, + { 0x79, 0x0d }, + { 0xc8, 0x20 }, + { 0x79, 0x09 }, + { 0xc8, 0x80 }, + { 0x79, 0x02 }, + { 0xc8, 0xc0 }, + { 0x79, 0x03 }, + { 0xc8, 0x40 }, + { 0x79, 0x05 }, + { 0xc8, 0x30 }, + { 0x79, 0x26 }, + /* Lens correction */ + { 0x64, 0x50 }, + { 0x65, 0x50 }, + { 0x66, 0x01 }, + { 0x94, 0x50 }, + { 0x95, 0x50 }, +}; + +/* Not tested */ +u8 init_array_qcif[][2] = { + { 0x11, 0x03 }, /* CLKRC */ + { 0x3a, 0x04 }, /* TSLB */ + { 0x12, 0x00 }, /* COM7 */ + { 0x17, 0x39 }, /* HSTART */ + { 0x18, 0x03 }, /* HSTOP */ + { 0x32, 0x80 }, /* HREF */ + { 0x19, 0x03 }, /* VSTRT */ + { 0x1a, 0x7b }, /* VSTOP */ + { 0x03, 0x02 }, /* VREF */ + { 0x0c, 0x0c }, /* COM3 */ + { 0x3e, 0x11 }, /* COM14 */ + { 0x70, 0x3a }, /* SCALING_XSC */ + { 0x71, 0x35 }, /* SCALING_YSC */ + { 0x72, 0x11 }, /* SCALING_DCWCTR */ + { 0x73, 0xf1 }, /* SCALING_PCLK_DIV */ + { 0xa2, 0x52 }, /* SCALING_PCLK_DELAY */ + /* Gamma */ + { 0x7a, 0x20 }, + { 0x7b, 0x10 }, + { 0x7c, 0x1e }, + { 0x7d, 0x35 }, + { 0x7e, 0x5a }, + { 0x7f, 0x69 }, + { 0x80, 0x76 }, + { 0x81, 0x80 }, + { 0x82, 0x88 }, + { 0x83, 0x8f }, + { 0x84, 0x96 }, + { 0x85, 0xa3 }, + { 0x86, 0xaf }, + { 0x87, 0xc4 }, + { 0x88, 0xd7 }, + { 0x89, 0xe8 }, + /* Gain, exposure, banding, AGC/AEC */ + { 0x13, 0xe0 }, + { 0x00, 0x00 }, + { 0x10, 0x00 }, + { 0x0d, 0x40 }, + { 0x14, 0x18 }, + { 0xa5, 0x05 }, + { 0xab, 0x07 }, + { 0x24, 0x95 }, + { 0x25, 0x33 }, + { 0x26, 0xe3 }, + { 0x9f, 0x78 }, + { 0xa0, 0x68 }, + { 0xa1, 0x03 }, + { 0xa6, 0xd8 }, + { 0xa7, 0xd8 }, + { 0xa8, 0xf0 }, + { 0xa9, 0x90 }, + { 0xaa, 0x94 }, + { 0x13, 0xe5 }, + /* Gain, ADC, PLL, etc... */ + { 0x0e, 0x61 }, + { 0x0f, 0x4b }, + { 0x16, 0x02 }, + { 0x1E, 0x07 }, + { 0x21, 0x02 }, + { 0x22, 0x91 }, + { 0x29, 0x07 }, + { 0x33, 0x0B }, + { 0x35, 0x0B }, + { 0x37, 0x1D }, + { 0x38, 0x71 }, + { 0x39, 0x2a }, + { 0x3c, 0x78 }, + { 0x4d, 0x40 }, + { 0x4e, 0x20 }, + { 0x69, 0x00 }, + { 0x74, 0x10 }, + { 0x8d, 0x4f }, + { 0x8e, 0x00 }, + { 0x8f, 0x00 }, + { 0x90, 0x00 }, + { 0x91, 0x00 }, + { 0x96, 0x00 }, + { 0x9a, 0x80 }, + { 0xb0, 0x84 }, + { 0xb1, 0x0c }, + { 0xb2, 0x0e }, + { 0xb3, 0x82 }, + { 0xb8, 0x0a }, + /* Reserved, AWB, Blue-Red Channel gain */ + { 0x43, 0x0a }, + { 0x44, 0xf0 }, + { 0x45, 0x34 }, + { 0x46, 0x58 }, + { 0x47, 0x28 }, + { 0x48, 0x3a }, + { 0x59, 0x88 }, + { 0x5a, 0x88 }, + { 0x5b, 0x44 }, + { 0x5c, 0x67 }, + { 0x5d, 0x49 }, + { 0x5e, 0x0e }, + { 0x6c, 0x0a }, + { 0x6d, 0x55 }, + { 0x6e, 0x11 }, + { 0x6f, 0x9f }, + { 0x6a, 0x40 }, + { 0x01, 0x40 }, + { 0x02, 0x40 }, + { 0x13, 0xe7 }, + /* Matrix coefficient */ + { 0x4f, 0x80 }, + { 0x50, 0x80 }, + { 0x51, 0x00 }, + { 0x52, 0x22 }, + { 0x53, 0x5e }, + { 0x54, 0x80 }, + { 0x58, 0x9e }, + /* Edge, pixel correction, de-noise, COM13, UV, Contrast */ + { 0x41, 0x08 }, + { 0x3f, 0x00 }, + { 0x75, 0x05 }, + { 0x76, 0xe1 }, + { 0x4c, 0x00 }, + { 0x77, 0x01 }, + { 0x3d, 0xc1 }, + { 0x4b, 0x09 }, + { 0xc9, 0x60 }, + { 0x41, 0x38 }, + { 0x56, 0x40 }, + /* Exposure timing, banding filter */ + { 0x34, 0x11 }, + { 0x3b, 0x02 }, + { 0xa4, 0x88 }, + { 0x96, 0x00 }, + { 0x97, 0x30 }, + { 0x98, 0x20 }, + { 0x99, 0x30 }, + { 0x9a, 0x84 }, + { 0x9b, 0x29 }, + { 0x9c, 0x03 }, + { 0x9d, 0x4c }, + { 0x9e, 0x3f }, + { 0x78, 0x04 }, + /* ??? */ + { 0x79, 0x01 }, + { 0xc8, 0xf0 }, + { 0x79, 0x0f }, + { 0xc8, 0x20 }, + { 0x79, 0x10 }, + { 0xc8, 0x7e }, + { 0x79, 0x0b }, + { 0xc8, 0x01 }, + { 0x79, 0x0c }, + { 0xc8, 0x07 }, + { 0x79, 0x0d }, + { 0xc8, 0x20 }, + { 0x79, 0x09 }, + { 0xc8, 0x80 }, + { 0x79, 0x02 }, + { 0xc8, 0xc0 }, + { 0x79, 0x03 }, + { 0xc8, 0x40 }, + { 0x79, 0x05 }, + { 0xc8, 0x30 }, + { 0x79, 0x26 }, + /* Lens correction */ + { 0x64, 0x50 }, + { 0x65, 0x50 }, + { 0x66, 0x01 }, + { 0x94, 0x50 }, + { 0x95, 0x50 }, +}; + +/* Camera sensor device functions */ +static int ov7670_video_probe(struct soc_camera_device *icd) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + u8 data; + int x; + + dev_dbg(&icd->dev, "%s\n", __func__); + + if (!icd->dev.parent || + to_soc_camera_host(icd->dev.parent)->nr != icd->iface) + return -ENODEV; + + /* Read out Chip Id and version */ + data = reg_read(ov7670->client, OV7670_MIDH); + dev_dbg(&icd->dev, "Chip IdH=0x%X\n", data); + if (data != 0x7F) + return -ENODEV; + data = reg_read(ov7670->client, OV7670_MIDL); + dev_dbg(&icd->dev, "Chip IdL=0x%X\n", data); + if (data != 0xA2) + return -ENODEV; + data = reg_read(ov7670->client, OV7670_PID); + dev_dbg(&icd->dev, "Chip PID=0x%X\n", data); + if (data != 0x76) + return -ENODEV; + data = reg_read(ov7670->client, OV7670_VER); + dev_dbg(&icd->dev, "Chip VER=0x%X\n", data); + if (data != 0x73) + return -ENODEV; + + /* Now we already know that OV7670 chip is there */ + dev_dbg(&icd->dev, "OmniVision camera OV7670 detected\n"); + + local_irq_disable(); + /* Setup registers */ + for (x = 0; x < ARRAY_SIZE(init_array_vga); x++) + reg_write(ov7670->client, init_array_vga[x][0], + init_array_vga[x][1]); + data = reg_read(ov7670->client, 0x11); + dev_dbg(&icd->dev, "Reg 0x11 is 0x%02X\n", data); + local_irq_enable(); + + /* Chip info setup */ + ov7670->model = V4L2_IDENT_OV7670; + icd->formats = ov7670_formats; + icd->num_formats = ARRAY_SIZE(ov7670_formats); + + /* If chip is detected, start video */ + return soc_camera_video_start(icd); +} + +static void ov7670_video_remove(struct soc_camera_device *icd) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + dev_dbg(&icd->dev, "%s\n", __func__); + soc_camera_video_stop(&ov7670->icd); +} + +static int ov7670_init(struct soc_camera_device *icd) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + + dev_dbg(&icd->dev, "%s\n", __func__); + + /* Reset chip */ + msleep(2); + reg_write(ov7670->client, OV7670_COM7, 0x80); + msleep(2); + + return 0; +} + +static int ov7670_release(struct soc_camera_device *icd) +{ + dev_dbg(&icd->dev, "%s\n", __func__); + return 0; +} + +static int ov7670_start_capture(struct soc_camera_device *icd) +{ + dev_dbg(&icd->dev, "%s\n", __func__); + return 0; +} + +static int ov7670_stop_capture(struct soc_camera_device *icd) +{ + dev_dbg(&icd->dev, "%s\n", __func__); + return 0; +} + +static int ov7670_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + dev_dbg(&icd->dev, "%s\n", __func__); + return 0; +} + +static unsigned long ov7670_query_bus_param(struct soc_camera_device *icd) +{ + dev_dbg(&icd->dev, "%s\n", __func__); + + /* Return OV7670 sensor bus capabilities */ + return SOCAM_MASTER | + SOCAM_PCLK_SAMPLE_RISING | + SOCAM_HSYNC_ACTIVE_HIGH | + SOCAM_VSYNC_ACTIVE_HIGH | + SOCAM_DATAWIDTH_8; +} + +static int ov7670_set_fmt_cap(struct soc_camera_device *icd, __u32 pixfmt, + struct v4l2_rect *rect) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + int x; + u8 data; + + /* Pixel format setup */ + switch (pixfmt) { + case V4L2_PIX_FMT_YUYV: /* YUV 4:2:2 */ + dev_dbg(&icd->dev, + "%s: set format V4L2_PIX_FMT_YUYV\n", __func__); + break; + default: + goto exit1; + } + + + /* Resolution setup */ + dev_dbg(&icd->dev, "%s: ask resolution W=%d, H=%d\n", __func__, + rect->width, rect->height); + dev_dbg(&icd->dev, "%s: current resolution is W=%d, H=%d\n", __func__, + icd->width, icd->height); + + if ((rect->width != icd->width) && (rect->height != icd->height)) { + + /* VGA */ + if ((rect->width == 640) && (rect->height == 480)) { + dev_dbg(&icd->dev, + "%s: change resolution to W=%d, H=%d\n", + __func__, rect->width, rect->height); + local_irq_disable(); + reg_write(ov7670->client, OV7670_COM7, 0x80); + msleep(2); + for (x = 0; x < ARRAY_SIZE(init_array_vga); x++) + reg_write(ov7670->client, init_array_vga[x][0], + init_array_vga[x][1]); + data = reg_read(ov7670->client, 0x11); + dev_dbg(&icd->dev, "Reg 0x11 is 0x%X\n", data); + local_irq_enable(); + } + /* QVGA */ + else if ((rect->width == 320) && (rect->height == 240)) { + dev_dbg(&icd->dev, + "%s: change resolution to W=%d, H=%d\n", + __func__, rect->width, rect->height); + local_irq_disable(); + reg_write(ov7670->client, OV7670_COM7, 0x80); + msleep(2); + for (x = 0; x < ARRAY_SIZE(init_array_qvga); x++) + reg_write(ov7670->client, init_array_qvga[x][0], + init_array_qvga[x][1]); + data = reg_read(ov7670->client, 0x11); + dev_dbg(&icd->dev, "Reg 0x11 is 0x%X\n", data); + local_irq_enable(); + } + /* QQVGA */ + else if ((rect->width == 160) && (rect->height == 120)) { + dev_dbg(&icd->dev, + "%s: change resolution to W=%d, H=%d\n", + __func__, rect->width, rect->height); + local_irq_disable(); + reg_write(ov7670->client, OV7670_COM7, 0x80); + msleep(2); + for (x = 0; x < ARRAY_SIZE(init_array_qqvga); x++) + reg_write(ov7670->client, + init_array_qqvga[x][0], + init_array_qqvga[x][1]); + data = reg_read(ov7670->client, 0x11); + dev_dbg(&icd->dev, "Reg 0x11 is 0x%X\n", data); + local_irq_enable(); + } + /* CIF */ + else if ((rect->width == 352) && (rect->height == 288)) { + dev_dbg(&icd->dev, + "%s: change resolution to W=%d, H=%d\n", + __func__, rect->width, rect->height); + local_irq_disable(); + reg_write(ov7670->client, OV7670_COM7, 0x80); + msleep(2); + for (x = 0; x < ARRAY_SIZE(init_array_cif); x++) + reg_write(ov7670->client, init_array_cif[x][0], + init_array_cif[x][1]); + data = reg_read(ov7670->client, 0x11); + dev_dbg(&icd->dev, "Reg 0x11 is 0x%X\n", data); + local_irq_enable(); + } + /* QCIF */ + else if ((rect->width == 176) && (rect->height == 144)) { + dev_dbg(&icd->dev, + "%s: change resolution to W=%d, H=%d\n", + __func__, rect->width, rect->height); + local_irq_disable(); + reg_write(ov7670->client, OV7670_COM7, 0x80); + msleep(2); + for (x = 0; x < ARRAY_SIZE(init_array_qcif); x++) + reg_write(ov7670->client, init_array_qcif[x][0], + init_array_qcif[x][1]); + data = reg_read(ov7670->client, 0x11); + dev_dbg(&icd->dev, "Reg 0x11 is 0x%X\n", data); + local_irq_enable(); + } else + goto exit1; + } + return 0; +exit1: + return -EINVAL; +} + +static int ov7670_try_fmt_cap(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + /* VGA */ + if ((f->fmt.pix.width == 640) && (f->fmt.pix.height == 480)) + goto exit1; + /* QVGA */ + else if ((f->fmt.pix.width == 320) && (f->fmt.pix.height == 240)) + goto exit1; + /* QQVGA */ + else if ((f->fmt.pix.width == 160) && (f->fmt.pix.height == 120)) + goto exit1; + /* CIF */ + else if ((f->fmt.pix.width == 352) && (f->fmt.pix.height == 288)) + goto exit1; + /* QCIF */ + else if ((f->fmt.pix.width == 176) && (f->fmt.pix.height == 144)) + goto exit1; + else + goto exit0; + +exit1: + dev_dbg(&icd->dev, "%s: Resolution W=%d, H=%d supported\n", + __func__, f->fmt.pix.width, f->fmt.pix.height); + return 0; +exit0: + dev_dbg(&icd->dev, "%s: Resolution W=%d, H=%d NOT supported!\n", + __func__, f->fmt.pix.width, f->fmt.pix.height); + return -EINVAL; +} + +static int ov7670_get_chip_id(struct soc_camera_device *icd, + struct v4l2_dbg_chip_ident *id) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + + dev_dbg(&icd->dev, "%s\n", __func__); + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != ov7670->client->addr) + return -ENODEV; + + id->ident = ov7670->model; + id->revision = 0; + return 0; +} + +static const struct v4l2_queryctrl ov7670_controls[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = -127, + .maximum = 127, + .step = 1, + .default_value = 0, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 0x40, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "V Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, +}; + +static int ov7670_get_control(struct soc_camera_device *, + struct v4l2_control *); +static int ov7670_set_control(struct soc_camera_device *, + struct v4l2_control *); + +static struct soc_camera_ops ov7670_ops = { + .owner = THIS_MODULE, + .probe = ov7670_video_probe, + .remove = ov7670_video_remove, + .init = ov7670_init, + .release = ov7670_release, + .start_capture = ov7670_start_capture, + .stop_capture = ov7670_stop_capture, + .set_fmt = ov7670_set_fmt_cap, + .try_fmt = ov7670_try_fmt_cap, + .set_bus_param = ov7670_set_bus_param, + .query_bus_param = ov7670_query_bus_param, + .controls = ov7670_controls, + .num_controls = ARRAY_SIZE(ov7670_controls), + .get_control = ov7670_get_control, + .set_control = ov7670_set_control, + .get_chip_id = ov7670_get_chip_id, +}; + +static int ov7670_get_control(struct soc_camera_device *icd, + struct v4l2_control *ctrl) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + + dev_dbg(&icd->dev, "%s\n", __func__); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = reg_read(ov7670->client, OV7670_BRIGHT); + if (ctrl->value & 0x80) + ctrl->value = -(ctrl->value & ~0x80); + dev_dbg(&icd->dev, "%s Get control [V4L2_CID_BRIGHTNESS]=%d\n", + __func__, ctrl->value); + break; + case V4L2_CID_CONTRAST: + ctrl->value = reg_read(ov7670->client, OV7670_CONTRAS); + dev_dbg(&icd->dev, "%s Get control [V4L2_CID_CONTRAST]=%d\n", + __func__, ctrl->value); + break; + case V4L2_CID_VFLIP: + ctrl->value = (reg_read(ov7670->client, + OV7670_MVFP) >> 4) & 0x01; + dev_dbg(&icd->dev, "%s Get control [V4L2_CID_VFLIP]=%d\n", + __func__, ctrl->value); + break; + case V4L2_CID_HFLIP: + ctrl->value = (reg_read(ov7670->client, + OV7670_MVFP) >> 5) & 0x01; + dev_dbg(&icd->dev, "%s Get control [V4L2_CID_HFLIP]=%d\n", + __func__, ctrl->value); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ov7670_set_control(struct soc_camera_device *icd, + struct v4l2_control *ctrl) +{ + struct ov7670 *ov7670 = container_of(icd, struct ov7670, icd); + const struct v4l2_queryctrl *qctrl; + int ctrl_byte; + + dev_dbg(&icd->dev, "%s\n", __func__); + + qctrl = soc_camera_find_qctrl(&ov7670_ops, ctrl->id); + if (!qctrl) + return -EINVAL; + + if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) + return -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&icd->dev, "%s Set control [V4L2_CID_BRIGHTNESS]=%d\n", + __func__, ctrl->value); + if (ctrl->value < 0) + reg_write(ov7670->client, OV7670_BRIGHT, + abs(ctrl->value) | 0x80); + else + reg_write(ov7670->client, OV7670_BRIGHT, ctrl->value); + break; + case V4L2_CID_CONTRAST: + dev_dbg(&icd->dev, "%s Set control [V4L2_CID_CONTRAST]=%d\n", + __func__, ctrl->value); + reg_write(ov7670->client, OV7670_CONTRAS, ctrl->value); + break; + case V4L2_CID_VFLIP: + dev_dbg(&icd->dev, "%s Set control [V4L2_CID_VFLIP]=%d\n", + __func__, ctrl->value); + ctrl_byte = reg_read(ov7670->client, OV7670_MVFP); + ctrl_byte &= ~(1<<4); + ctrl_byte |= ctrl->value << 4; + reg_write(ov7670->client, OV7670_MVFP, ctrl_byte); + break; + case V4L2_CID_HFLIP: + dev_dbg(&icd->dev, "%s Set control [V4L2_CID_HFLIP]=%d\n", + __func__, ctrl->value); + ctrl_byte = reg_read(ov7670->client, OV7670_MVFP); + ctrl_byte &= ~(1<<5); + ctrl_byte |= ctrl->value << 5; + reg_write(ov7670->client, OV7670_MVFP, ctrl_byte); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ov7670_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov7670 *ov7670; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct soc_camera_device *icd; + struct soc_camera_link *icl = client->dev.platform_data; + int ret; + + dev_dbg(&client->dev, "%s\n", __func__); + + /* I2C adapter probe */ + if (!icl) { + dev_err(&client->dev, "OV7670 driver needs platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_I2C\n"); + return -EIO; + } + + ov7670 = kzalloc(sizeof(struct ov7670), GFP_KERNEL); + if (!ov7670) + return -ENOMEM; + ov7670->client = client; + i2c_set_clientdata(client, ov7670); + + /* Setup camera capabilities in device structure for later use */ + icd = &ov7670->icd; + icd->ops = &ov7670_ops; + icd->control = &client->dev; + icd->width = 640; + icd->height = 480; + icd->x_min = 0; + icd->y_min = 0; + icd->x_current = 0; + icd->y_current = 0; + icd->width_min = 40; + icd->width_max = 640; + icd->height_min = 30; + icd->height_max = 480; + icd->y_skip_top = 0; + icd->iface = icl->bus_id; + + /* Register camera device */ + ret = soc_camera_device_register(icd); + if (ret) + goto eisdr; + return 0; +eisdr: + kfree(ov7670); + return ret; +} + +static int ov7670_remove(struct i2c_client *client) +{ + struct ov7670 *ov7670 = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s\n", __func__); + soc_camera_device_unregister(&ov7670->icd); + kfree(ov7670); + return 0; +} + +static const struct i2c_device_id ov7670_id[] = { + { "ov7xxx", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov7670_id); + +static struct i2c_driver ov7670_i2c_driver = { + .driver = + { + .name = "ov7670", + }, + .probe = ov7670_probe, + .remove = ov7670_remove, + .id_table = ov7670_id, +}; + +static int __init ov7670_mod_init(void) +{ + return i2c_add_driver(&ov7670_i2c_driver); +} + +static void __exit ov7670_mod_exit(void) +{ + i2c_del_driver(&ov7670_i2c_driver); +} + +module_init(ov7670_mod_init); +module_exit(ov7670_mod_exit); + +MODULE_DESCRIPTION("OmniVision OV7670 Camera driver"); +MODULE_AUTHOR("Darius Augulis "); +MODULE_LICENSE("GPL");