@@ -746,6 +746,12 @@ config SOC_CAMERA_OV772X
help
This is a ov772x camera driver
+config SOC_CAMERA_OV7670_SOC
+ tristate "ov7670 soc camera support"
+ depends on SOC_CAMERA && I2C
+ help
+ This is an ov7670 soc camera driver
+
config MX1_VIDEO
bool
@@ -141,6 +141,7 @@ obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o
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_SOC) += ov7670_soc.o
obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o
obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o
# soc-camera host drivers have to be linked after camera drivers
new file mode 100644
@@ -0,0 +1,1475 @@
+/*
+ * A V4L2 driver for OmniVision OV7670 cameras via soc interface
+ *
+ * Copyright 2006 One Laptop Per Child Association, Inc. Written
+ * by Jonathan Corbet with substantial inspiration from Mark
+ * McClelland's ovcamchip code.
+ *
+ * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
+ *
+ * Copyright 2008-9 Jonathan Cameron <jic23@cam.ac.uk>
+ *
+ * Copyright 2008 Darius Augulis <augulis.darius@gmail.com>
+ *
+ * This file may be distributed under the terms of the GNU General
+ * Public License, version 2.
+ *
+ * Todo: Add control for auto saturation control
+ * Inversion of sync signals etc.
+ * Driver 2 had a qqvga mode, but register settings don't seem to
+ * be right so I've removed it.
+ *
+ * Queries for review:
+ * 1) Here I'm using brightness controls for what are effectively shutter
+ * timings. How should this be done?
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-chip-ident.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <media/soc_camera.h>
+#include <media/ov7670_soc.h>
+
+#define MAX_WIDTH 640
+#define MAX_HEIGHT 480
+
+/* Registers */
+#define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */
+#define REG_BLUE 0x01 /* blue gain */
+#define REG_RED 0x02 /* red gain */
+#define REG_VREF 0x03 /* Pieces of GAIN, VSTART, VSTOP */
+#define REG_COM1 0x04 /* Control 1 */
+#define COM1_CCIR656 0x40 /* CCIR656 enable */
+
+#define REG_AECHH 0x07 /* AEC MS 5 bits */
+
+#define REG_COM2 0x09 /* Control 2 */
+#define COM2_SSLEEP 0x10 /* Soft sleep mode */
+#define REG_PID 0x0a /* Product ID MSB */
+#define REG_VER 0x0b /* Product ID LSB */
+#define REG_COM3 0x0c /* Control 3 */
+#define COM3_SWAP 0x40 /* Byte swap */
+#define COM3_SCALEEN 0x08 /* Enable scaling */
+#define COM3_DCWEN 0x04 /* Enable downsamp/crop/window */
+#define REG_COM4 0x0d /* Control 4 */
+#define REG_COM5 0x0e /* All "reserved" */
+#define REG_COM6 0x0f /* Control 6 */
+#define REG_AECH 0x10 /* More bits of AEC value */
+#define REG_CLKRC 0x11 /* Clock control */
+#define CLK_EXT 0x40 /* Use external clock directly */
+#define REG_COM7 0x12 /* Control 7 */
+#define COM7_RESET 0x80 /* Register reset */
+#define COM7_FMT_MASK 0x38
+#define COM7_FMT_VGA 0x00
+#define COM7_FMT_CIF 0x20 /* CIF format */
+#define COM7_FMT_QVGA 0x10 /* QVGA format */
+#define COM7_FMT_QCIF 0x08 /* QCIF format */
+#define COM7_RGB 0x04 /* bits 0 and 2 - RGB format */
+#define COM7_YUV 0x00 /* YUV */
+#define COM7_BAYER 0x01 /* Bayer format */
+#define COM7_PBAYER 0x05 /* "Processed bayer?" */
+#define REG_COM8 0x13 /* Control 8 */
+#define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */
+#define COM8_AECSTEP 0x40 /* Unlimited AEC step size */
+#define COM8_BFILT 0x20 /* Band filter enable */
+#define COM8_AGC 0x04 /* Auto gain enable */
+#define COM8_AWB 0x02 /* White balance enable */
+#define COM8_AEC 0x01 /* Auto exposure enable */
+#define REG_COM9 0x14 /* Control 9 - gain ceiling */
+#define REG_COM10 0x15 /* Control 10 */
+#define COM10_HSYNC 0x40 /* HSYNC instead of HREF */
+#define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */
+#define COM10_HREF_REV 0x08 /* Reverse HREF */
+#define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */
+#define COM10_VS_NEG 0x02 /* VSYNC negative */
+#define COM10_HS_NEG 0x01 /* HSYNC negative */
+#define REG_HSTART 0x17 /* Horiz start high bits */
+#define REG_HSTOP 0x18 /* Horiz stop high bits */
+#define REG_VSTART 0x19 /* Vert start high bits */
+#define REG_VSTOP 0x1a /* Vert stop high bits */
+#define REG_PSHFT 0x1b /* Pixel delay after HREF */
+#define REG_MIDH 0x1c /* Manuf. ID high */
+#define REG_MIDL 0x1d /* Manuf. ID low */
+#define REG_MVFP 0x1e /* Mirror / vflip */
+#define MVFP_MIRROR 0x20 /* Mirror image */
+#define MVFP_FLIP 0x10 /* Vertical flip */
+
+#define REG_AEW 0x24 /* AGC upper limit */
+#define REG_AEB 0x25 /* AGC lower limit */
+#define REG_VPT 0x26 /* AGC/AEC fast mode op region */
+
+#define REG_HSYST 0x30 /* HSYNC rising edge delay */
+#define REG_HSYEN 0x31 /* HSYNC falling edge delay */
+#define REG_HREF 0x32 /* HREF pieces */
+#define REG_TSLB 0x3a /* Line buffer test options */
+#define TSLB_YLAST 0x04 /* UYVY or VYUY - see com13 */
+#define REG_COM11 0x3b /* Control 11 */
+#define COM11_NIGHT 0x80 /* Night mode enable */
+#define COM11_NMFR 0x60 /* Two bit NM frame rate */
+#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */
+#define COM11_50HZ 0x08 /* Manual 50Hz select */
+#define COM11_EXP 0x02
+#define REG_COM12 0x3c /* Control 12 */
+#define COM12_HREF 0x80 /* HREF always */
+#define REG_COM13 0x3d /* Control 13 */
+#define COM13_GAMMA 0x80 /* Gamma enable */
+#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */
+#define COM13_UVSWAP 0x01 /* V before U - w/TSLB */
+#define REG_COM14 0x3e /* Control 14 */
+#define COM14_DCWEN 0x10 /* DCW/PCLK-scale enable */
+#define REG_EDGE 0x3f /* Edge enhancement factor */
+#define REG_COM15 0x40 /* Control 15 */
+#define COM15_R10F0 0x00 /* Data range 10 to F0 */
+#define COM15_R01FE 0x80 /* 01 to FE */
+#define COM15_R00FF 0xc0 /* 00 to FF */
+#define COM15_RGB565 0x10 /* RGB565 output */
+#define COM15_RGB555 0x30 /* RGB555 output */
+#define REG_COM16 0x41 /* Control 16 */
+#define COM16_AWBGAIN 0x08 /* AWB gain enable */
+#define COM16_DN_THRESH_AUTO 0x10 /* denoise threshold auto adj */
+#define COM16_EDGE_THRESH_AUTO 0x20 /* yuv edge enhancement auto adj*/
+#define REG_COM17 0x42 /* Control 17 */
+#define COM17_AECWIN 0xc0 /* AEC window - must match COM4 */
+#define COM17_CBAR 0x08 /* DSP Color bar */
+
+#define REG_DNSTH 0x4C
+/*
+ * This matrix defines how the colors are generated, must be
+ * tweaked to adjust hue and saturation.
+ *
+ * Order: v-red, v-green, v-blue, u-red, u-green, u-blue
+ *
+ * They are nine-bit signed quantities, with the sign bit
+ * stored in 0x58. Sign for v-red is bit 0, and up from there.
+ */
+#define REG_CMATRIX_BASE 0x4f
+#define CMATRIX_LEN 6
+#define REG_CMATRIX_SIGN 0x58
+
+#define REG_BRIGHT 0x55 /* Brightness */
+#define REG_CONTRAST 0x56 /* Contrast control */
+
+#define REG_GFIX 0x69 /* Fix gain control */
+
+#define REG_SCALING_XSC 0x70 /* Horizontal scale factor */
+#define REG_SCALING_YSC 0x71 /* Vertical scale factor */
+#define REG_SCALING_DCWCTR 0x72 /* DCW control */
+#define REG_SCALING_PCLK_DIV 0x73 /* Clock divider for scale control */
+
+#define REG_REG75 0x75 /* Edge enhancement lower limit */
+#define REG_REG76 0x76 /* OV's name */
+#define R76_BLKPCOR 0x80 /* Black pixel correction enable */
+#define R76_WHTPCOR 0x40 /* White pixel correction enable */
+#define REG_REG77 0x77 /* Denoise offset */
+#define REG_RGB444 0x8c /* RGB 444 control */
+#define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */
+#define R444_RGBX 0x01 /* Empty nibble at end */
+
+#define REG_HAECC1 0x9f /* Hist AEC/AGC control 1 */
+#define REG_HAECC2 0xa0 /* Hist AEC/AGC control 2 */
+#define REG_SCALING_PCLK_DELAY 0xa2/* Scaling output delay */
+#define REG_BD50MAX 0xa5 /* 50hz banding step limit */
+#define REG_HAECC3 0xa6 /* Hist AEC/AGC control 3 */
+#define REG_HAECC4 0xa7 /* Hist AEC/AGC control 4 */
+#define REG_HAECC5 0xa8 /* Hist AEC/AGC control 5 */
+#define REG_HAECC6 0xa9 /* Hist AEC/AGC control 6 */
+#define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */
+#define REG_BD60MAX 0xab /* 60hz banding step limit */
+
+/**
+ * struct regval_list - settings for a particular register
+ * @reg_num: register address
+ * @value: the value to be set
+ * @mask: a mask to optionally set only part of the register
+ **/
+struct regval_list {
+ unsigned char reg_num;
+ unsigned char value;
+ unsigned char mask;
+};
+
+/**
+ * struct ov7670_color_format - colour format specific settings
+ * @name: identifying string
+ * @fourcc: V4L2_PIX_FMT_ idenifier
+ * @regs: colour format specific register values
+ * @cmatrix: colour conversion matrix defaults
+ **/
+struct ov7670_color_format {
+ char *name;
+ __u32 fourcc;
+ const struct regval_list *regs;
+ int cmatrix[6];
+};
+
+/**
+ * struct ov7670_soc_win_size - device parameters for particular resolution
+ * @width: image width
+ * @height: image height
+ * @regs: resolution specific register values
+ **/
+struct ov7670_soc_win_size {
+ int width;
+ int height;
+ struct regval_list *regs;
+};
+
+/* dubious colours */
+static struct regval_list ov7670_vga_regs[] = {
+ { REG_COM7, COM7_FMT_VGA, 0x38 },
+ /* hstart = 158, hstop = 14 */
+ { REG_HSTART, 0x13 }, { REG_HSTOP, 0x01 }, { REG_HREF, 0x3C, 0x3F },
+ /* vstart = 10, vstop = 490 */
+ { REG_VSTART, 0x02 }, { REG_VSTOP, 0x7A }, { REG_VREF, 0x0A, 0x0F },
+ { REG_COM3, 0, 0x0C },
+ { REG_COM14, 0 },
+ { REG_SCALING_PCLK_DIV, 0xf0 },
+ { 0x3d, 0xc1}, /* minor edit to gamma curve */
+ { 0xff, 0xff }, /* END MARKER */
+};
+
+static struct regval_list ov7670_cif_regs[] = {
+ { REG_COM7, COM7_FMT_CIF, 0x38 },
+ /* hstart = 174, hstop = 94 */
+ { REG_HSTART, 0x15 }, { REG_HSTOP, 0x0B }, { REG_HREF, 0x0C, 0x3F },
+ /* vstart = 14, vstop = 492 */
+ { REG_VSTART, 0x03 }, { REG_VSTOP, 0x7A }, { REG_VREF, 0x0E, 0x0F},
+ { REG_COM3, 0x08, 0x0C }, /* enable scale? */
+ { REG_COM14, 0x11 }, /* divide pclk 2 and enable dcw and scaling pclk */
+ { REG_SCALING_PCLK_DIV, 0xF1 }, /* dsp scale ctrl clock div 2 */
+ { 0x3d, 0xc0}, /* minor edit to gamma curve */
+ { 0xff, 0xff }, /* END MARKER */
+};
+
+/* dubious colours */
+static struct regval_list ov7670_qvga_regs[] = {
+ { REG_COM7, COM7_FMT_QVGA, 0x38 },
+ /* hstart = 176, hstop = 32 */
+ { REG_HSTART, 0x16 }, { REG_HSTOP, 0x04 }, { REG_HREF, 0, 0x3F },
+ /* vstart = 10, vstop = 490 */
+ { REG_VSTART, 0x02 }, { REG_VSTOP, 0x7a }, { REG_VREF, 0x0A, 0x0F },
+ { REG_COM3, COM3_DCWEN, 0x0C }, /* downsample/crop/window enable */
+ { REG_COM14, 0x19 },
+ { REG_SCALING_PCLK_DIV, 0xF1 }, /* dsp scale ctrl clock div 2 */
+ { 0x3d, 0xc0}, /* minor edit to gamma curve */
+ { 0xff, 0xff }, /* END MARKER */
+};
+
+/*
+ * This register set is not perfect. Anyone have a better one?
+ * Note that driver 2 suggested a larger value in 0xa2 which is a bad idea.
+ */
+static struct regval_list ov7670_qcif_regs[] = {
+ { REG_COM7, COM7_FMT_QCIF, 0x38 },
+ /* hstart = 174, hstop = 94 */
+ { REG_HSTART, 0x14 }, { REG_HSTOP, 0x03 }, { REG_HREF, 0x00, 0x3F },
+ /* vstart = 14, vstop = 492 */
+ { REG_VSTART, 0x03 }, { REG_VSTOP, 0x7B }, { REG_VREF, 0x02, 0x0F},
+ { REG_COM3, 0x0c, 0x0C }, /* enable scale? */
+ { REG_COM14, 0x12 }, /* divide pclk 2 and enable dcw and scaling pclk */
+ { REG_SCALING_PCLK_DIV, 0xf2 }, /* dsp scale ctrl clock div 2 */
+ { 0x3d, 0xc1}, /* minor edit to gamma curve */
+ { 0xff, 0xff }, /* END MARKER */
+};
+
+static struct ov7670_soc_win_size ov7670_soc_win_sizes[] = {
+ {
+ .width = 640,
+ .height = 480,
+ .regs = ov7670_vga_regs,
+
+ }, {
+ .width = 352,
+ .height = 288,
+ .regs = ov7670_cif_regs,
+ }, {
+ .width = 320,
+ .height = 240,
+ .regs = ov7670_qvga_regs,
+ }, {
+ .width = 176,
+ .height = 144,
+ .regs = ov7670_qcif_regs,
+ },
+};
+
+struct ov7670_soc_priv {
+ struct v4l2_subdev subdev;
+ struct ov7670_soc_camera_info *info;
+ const struct ov7670_color_format *fmt;
+ const struct ov7670_soc_win_size *win;
+ int gpio_reset;
+ int gpio_power;
+ int hue;
+ int sat;
+};
+
+
+static const struct v4l2_queryctrl ov7670_soc_controls[] = {
+ { /* Contrast register documentation is very vague */
+ .id = V4L2_CID_CONTRAST,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Contrast",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 40,
+ }, {
+ .id = V4L2_CID_EXPOSURE_AUTO,
+ .name = "Auto Exposure",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_EXPOSURE_ABSOLUTE,
+ .name = "Exposure Target",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 0xFFFF,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .name = "Auto White Balance",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_AUTOGAIN,
+ .name = "Auto gain",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ V4L2_CID_AUDIO_VOLUME,
+ .name = "gain",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 0x3FF,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Flip Vertically",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_HFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Flip Horizontally",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_BRIGHTNESS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Clock scaling", /* technically clock scaling */
+ .minimum = 1,
+ .maximum = 0x1F,
+ .step = 1,
+ .default_value = 3,
+ }, {
+ .id = V4L2_CID_HUE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Hue",
+ .minimum = -180,
+ .maximum = 180,
+ .step = 5,
+ .default_value = 0,
+ }, {
+ .id = V4L2_CID_SATURATION,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Saturation",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 0x80,
+ }, {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Blue Gain",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 0x40,
+ } , {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Red Gain",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 0x40,
+ }
+};
+
+static struct regval_list ov7670_default_regs[] = {
+ { REG_COM7, COM7_RESET },
+ { REG_CLKRC, 0x03 }, /* clock scale (15 fps) */
+ { REG_TSLB, 0x04 }, /* magic */
+ { REG_COM7, 0 }, /* Disable Color bar */
+ { REG_COM15, COM15_R00FF },
+ { REG_COM3, 0 },
+ { REG_COM14, 0 },
+
+ /* Mystery scaling numbers */
+ { REG_SCALING_XSC, 0x3A },
+ { REG_SCALING_YSC, 0x35 },
+ { REG_SCALING_DCWCTR, 0x11 },
+ { REG_SCALING_PCLK_DELAY, 0x02 },
+
+ { REG_COM10, 0x0 },
+
+ /* Gamma curve values */
+ { 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 },
+
+ /* AGC and AEC parameters. Note we start by disabling those features,
+ then turn them only after tweaking the values. */
+ { REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_BFILT },
+ { REG_GAIN, 0 },
+ { REG_AECH, 0 },
+ { REG_COM4, 0x40 }, /* magic reserved bit */
+ { REG_COM9, 0x18 }, /* 4x gain + magic rsvd bit */
+ { REG_BD50MAX, 0x05 },
+ { REG_BD60MAX, 0x07 },
+ { REG_AEW, 0x95 },
+ { REG_AEB, 0x33 },
+ { REG_VPT, 0xe3 },
+ { REG_HAECC1, 0x78 },
+ { REG_HAECC2, 0x68 },
+ { 0xa1, 0x03 }, /* magic */
+ { REG_HAECC3, 0xd8 },
+ { REG_HAECC4, 0xd8 },
+ { REG_HAECC5, 0xf0 },
+ { REG_HAECC6, 0x90 },
+ { REG_HAECC7, 0x14 },
+ { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC },
+
+ { REG_COM5, 0x61 },
+
+ /*
+ * Reset timing on format change,
+ * Signal output biases enabled (GbBIAS, Red bias, Blue bias)
+ * The rest are magic
+ */
+ { REG_COM6, 0x4b },
+
+ { 0x16, 0x02 }, /* magic */
+ { REG_MVFP, 0x07 },
+ { 0x21, 0x02 }, /* ADCCTR1 - magic */
+ { 0x22, 0x91 }, /* ADCCTR2 - magic */
+ { 0x29, 0x07 }, /* magic */
+ { 0x33, 0x0b }, /* Array current control -magic */
+ { 0x35, 0x0b }, /* magic */
+ { 0x37, 0x1d }, /* ADC control - magic*/
+ { 0x38, 0x71 }, /* ADC and analog common mode control - magic */
+ { 0x39, 0x2a }, /* ADC offset control - magic*/
+ { REG_COM12, 0x78 },
+ { 0x4d, 0x40 }, /* magic */
+ { 0x4e, 0x20 }, /* magic */
+ { REG_GFIX, 0 },
+ { 0x6b, 0x4a }, /* internal reg enabled, pll input clock x4, magic */
+
+ { 0x74, 0x00 }, /* digital gain control by reg 74, bypass set */
+ { 0x8d, 0x4f }, /* magic */
+ { 0x8e, 0 }, /* magic */
+ { 0x8f, 0 }, /* magic */
+ { 0x90, 0 }, /* magic */
+ { 0x91, 0 }, /* magic */
+ { 0x96, 0 }, /* magic */
+ { 0x9a, 0x80 }, /* magic, note in tinyos driver 0x00 */
+ { 0xb0, 0x84 }, /* magic */
+ { 0xb1, 0x0c }, /* ABLC1, magic */
+ { 0xb2, 0x0e }, /* magic */
+ { 0xb3, 0x82 }, /* ABLC target */
+ { 0xb8, 0x0a }, /* magic */
+
+ /* More reserved magic, some of which tweaks white balance */
+ { 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 for advance AWB" */
+
+ { 0x6a, 0x40 }, /* G channel AWB gain */
+ { REG_BLUE, 0x40 },
+ { REG_RED, 0x40 },
+ { REG_COM16, COM16_AWBGAIN },
+ { REG_EDGE, 0 },
+ { REG_REG75, 0x05 }, /* Edge enhancement and black / white pixel cor.*/
+ { REG_REG76, R76_BLKPCOR | R76_WHTPCOR | 0x01 },
+ /* Denoise strength and offest */
+ { REG_DNSTH, 0 },
+ { REG_REG77, 0x01 },
+ { 0x4b, 0x09 }, /* UV everage enable */
+ { 0xc9, 0x60 }, /* Saturation control min as 6 */
+ { REG_COM16, (COM16_AWBGAIN |
+ COM16_DN_THRESH_AUTO |
+ COM16_EDGE_THRESH_AUTO)},
+ { 0x56, 0x40 }, /* contrast control - magic */
+ { 0x34, 0x11 }, /* Array reference control - magic */
+ { REG_COM11, COM11_EXP | COM11_HZAUTO },
+ { 0xa4, 0x80 }, /* Auto FR: reduce FR by half and magic */
+
+ /* next group are all magic */
+ { 0x96, 0x00 }, { 0x97, 0x30 }, { 0x98, 0x20 }, { 0x99, 0x30 },
+ { 0x9a, 0x84 }, { 0x9b, 0x29 }, { 0x9c, 0x03 }, { 0x9d, 0x4c },
+ { 0x9e, 0x3f }, { 0x78, 0x04 },
+
+ /*
+ * Undocumented in prelimenary data sheet
+ * Looks like a multiplexor register, 0x79 sets sub address
+ * c8 is the access
+ */
+ /* none of this in driver 2 for vga. */
+ { 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 }, /* magic number? */
+
+ { 0xff, 0xff }, /* END MARKER */
+};
+
+static struct regval_list ov7670_fmt_yuv422[] = {
+ { REG_COM7, COM7_YUV, 0x05 },
+ { REG_RGB444, 0 }, /* No RGB444 please */
+ { REG_COM1, 0, 0x40 }, /* no ccir656 aec low 2 lsb to 0 */
+ { REG_COM15, 0x00, 0x30 },
+ { REG_COM9, 0x10, 0x70 }, /* AGC 4x gain ceiling */
+ /* set YUYV byte order and uv saturation off */
+ { REG_COM13, COM13_GAMMA | COM13_UVSAT },
+ { 0xff, 0xff },
+};
+
+static struct regval_list ov7670_fmt_rgb565[] = {
+ { REG_COM7, COM7_RGB, 0x05 },
+ { REG_RGB444, 0 }, /* No RGB444 */
+ { REG_COM1, 0x0, 0x40 }, /* No CCIR656 */
+ { REG_COM15, COM15_RGB565, 0x30 },
+ { REG_COM9, 0x30, 0x70 }, /* AGC 16x gain ceiling */
+ { REG_COM13, COM13_GAMMA | COM13_UVSAT },
+ { 0xff, 0xff },
+};
+
+#define SETFOURCC(type) .name = (#type), .fourcc = (V4L2_PIX_FMT_ ## type)
+static const struct soc_camera_data_format ov7670_soc_fmt_lists[] = {
+ {
+ SETFOURCC(YUYV),
+ .depth = 16,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ }, {
+ SETFOURCC(RGB565),
+ .depth = 16,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ },
+};
+
+static const struct ov7670_color_format ov7670_cfmts[] = {
+ {
+ SETFOURCC(YUYV),
+ .regs = ov7670_fmt_yuv422,
+ .cmatrix = { 128, -128, 0, -34, -94, 128 },
+ }, {
+ SETFOURCC(RGB565),
+ .regs = ov7670_fmt_rgb565,
+ .cmatrix = { 179, -179, 0, -61, -176, 228 },
+ },
+};
+
+#define OV7670_SIN_STEP 5
+static const int ov7670_sin_table[] = {
+ 0, 87, 173, 258, 342, 422,
+ 499, 573, 642, 707, 766, 819,
+ 866, 906, 939, 965, 984, 996,
+ 1000
+};
+
+static int ov7670_sine(int theta)
+{
+ int chs = 1;
+ int sine;
+
+ if (theta < 0) {
+ theta = -theta;
+ chs = -1;
+ }
+ if (theta <= 90)
+ sine = ov7670_sin_table[theta/OV7670_SIN_STEP];
+ else {
+ theta -= 90;
+ sine = 1000 - ov7670_sin_table[theta/OV7670_SIN_STEP];
+ }
+ return (sine * chs);
+}
+
+static int ov7670_cosine(int theta)
+{
+ theta = 90 - theta;
+ if (theta > 180)
+ theta -= 360;
+ else if (theta < -180)
+ theta += 360;
+
+ return ov7670_sine(theta);
+}
+
+static void ov7670_calc_cmatrix(struct ov7670_soc_priv *priv,
+ int matrix[CMATRIX_LEN])
+{
+ int i;
+
+ /*
+ * Apply the current saturation setting first.
+ */
+ for (i = 0; i < CMATRIX_LEN; i++)
+ matrix[i] = (priv->fmt->cmatrix[i]*priv->sat) >> 7;
+ /*
+ * Then, if need be, rotate the hue value.
+ */
+ if (priv->hue != 0) {
+ int sinth, costh, tmpmatrix[CMATRIX_LEN];
+
+ memcpy(tmpmatrix, matrix, CMATRIX_LEN*sizeof(int));
+ sinth = ov7670_sine(priv->hue);
+ costh = ov7670_cosine(priv->hue);
+
+ matrix[0] = (matrix[3]*sinth + matrix[0]*costh)/1000;
+ matrix[1] = (matrix[4]*sinth + matrix[1]*costh)/1000;
+ matrix[2] = (matrix[5]*sinth + matrix[2]*costh)/1000;
+ matrix[3] = (matrix[3]*costh - matrix[0]*sinth)/1000;
+ matrix[4] = (matrix[4]*costh - matrix[1]*sinth)/1000;
+ matrix[5] = (matrix[5]*costh - matrix[2]*sinth)/1000;
+ }
+}
+
+#define OV_VERSION(pid, ver) ((pid << 8) | (ver & 0xFF))
+#define OV7670_VER OV_VERSION(0x76, 0x73)
+
+static struct ov7670_soc_priv *to_ov7670_soc(const struct i2c_client *client)
+{
+ return container_of(i2c_get_clientdata(client),
+ struct ov7670_soc_priv, subdev);
+}
+
+/*
+ * A quirk in smbus implementation (SCCB) requires a stop before a repeated
+ * start. This necesitates this function. Without this I was getting
+ * all reads succeeding on second try. Can anyone else verify this isn't
+ * a board specific quirk?
+ */
+static s32 ov7670_smbus_read_byte_data(struct i2c_client *client, u8 command)
+{
+ /* lifted from i2c_smbus_xfer_emulated */
+ unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
+ unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
+
+ struct i2c_msg msg[2] = { {
+ client->addr,
+ client->flags,
+ 1,
+ msgbuf0
+ }, {
+ client->addr,
+ client->flags | I2C_M_RD,
+ 1,
+ msgbuf1
+ },
+ };
+ int status;
+
+ msgbuf0[0] = command;
+ /* send the register address to read */
+ status = i2c_transfer(client->adapter, msg, 1);
+ if (status < 0)
+ return status;
+ status = i2c_transfer(client->adapter, &msg[1], 1);
+ if (status < 0)
+ return status;
+
+ return msgbuf1[0];
+}
+
+static int ov7670_write_array(struct i2c_client *c,
+ const struct regval_list *vals)
+{
+ int ret, val;
+ while (vals->reg_num != 0xff || vals->value != 0xff) {
+ if (vals->mask) {
+ val = ov7670_smbus_read_byte_data(c,
+ vals->reg_num);
+ ret = i2c_smbus_write_byte_data(c,
+ vals->reg_num,
+ (val & ~vals->mask) |
+ vals->value);
+ if (ret)
+ return ret;
+ vals++;
+ } else {
+ ret = i2c_smbus_write_byte_data(c,
+ vals->reg_num,
+ vals->value);
+ if (ret)
+ return ret;
+ vals++;
+ }
+ }
+
+ return 0;
+}
+
+static int ov7670_soc_video_probe(struct soc_camera_device *icd,
+ struct i2c_client *client)
+{
+ s32 pid, ver;
+ int ret;
+
+ icd->formats = ov7670_soc_fmt_lists;
+ icd->num_formats = ARRAY_SIZE(ov7670_soc_fmt_lists);
+
+ /* Sanity check? */
+ i2c_smbus_write_byte_data(client, REG_COM7, COM7_RESET);
+ mdelay(1);
+
+ pid = ov7670_smbus_read_byte_data(client, REG_PID);
+ ver = ov7670_smbus_read_byte_data(client, REG_VER);
+
+ if (OV_VERSION(pid, ver) != OV7670_VER) {
+ dev_err(&icd->dev,
+ "Product ID error %x:%x\n", pid, ver);
+ return -ENODEV;
+ }
+
+ dev_info(&icd->dev,
+ "ov7670 Product ID %0x:%0x Manufacturer ID %x:%x\n",
+ pid,
+ ver,
+ ov7670_smbus_read_byte_data(client, REG_MIDH),
+ ov7670_smbus_read_byte_data(client, REG_MIDL));
+ /* Write a default set of registers so we have a consistent state */
+ ret = ov7670_write_array(client, ov7670_default_regs);
+ return ret;
+}
+
+static void ov7670_soc_video_remove(struct soc_camera_device *icd)
+{
+ icd->ops = NULL;
+}
+
+static int ov7670_store_cmatrix(struct i2c_client *c, const int *matrix)
+{
+ int signbits, i, ret;
+
+ signbits = ov7670_smbus_read_byte_data(c, REG_CMATRIX_SIGN);
+ if (signbits < 0)
+ return -EIO;
+
+ signbits &= 0xc0;
+
+ for (i = 0; i < 6; i++) {
+ u8 raw;
+ if (matrix[i] < 0) {
+ signbits |= (1 << i);
+ if (matrix[i] < -255)
+ raw = 0xff;
+ else
+ raw = (-1 * matrix[i]) & 0xff;
+ } else {
+ if (matrix[i] > 255)
+ raw = 0xff;
+ else
+ raw = matrix[i] & 0xff;
+ }
+ ret = i2c_smbus_write_byte_data(c, REG_CMATRIX_BASE + i, raw);
+ if (ret)
+ return -EIO;
+ }
+
+ return i2c_smbus_write_byte_data(c, REG_CMATRIX_SIGN, signbits);
+}
+
+static int ov7670_soc_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct i2c_client *client = sd->priv;
+ struct ov7670_soc_priv *priv = to_ov7670_soc(client);
+ int data, ret;
+
+ if (!enable) {
+ data = ov7670_smbus_read_byte_data(client, REG_COM2);
+ return i2c_smbus_write_byte_data(client,
+ REG_COM2,
+ data | COM2_SSLEEP);
+ }
+
+ if (!priv->win || !priv->fmt) {
+ dev_err(&client->dev, "norm or win select error\n");
+ return -EPERM;
+ }
+
+ data = ov7670_smbus_read_byte_data(client, REG_COM2);
+
+ return ret = i2c_smbus_write_byte_data(client,
+ REG_COM2,
+ data & ~COM2_SSLEEP);
+}
+
+static int ov7670_soc_g_chip_ident(struct v4l2_subdev *sd,
+ struct v4l2_dbg_chip_ident *id)
+{
+ id->ident = V4L2_IDENT_OV7670;
+ id->revision = 0;
+
+ return 0;
+}
+
+static unsigned long ov7670_soc_query_bus_param(struct soc_camera_device *icd)
+{
+ struct soc_camera_link *icl = to_soc_camera_link(icd);
+
+ unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | SOCAM_MASTER |
+ SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_HIGH |
+ SOCAM_DATAWIDTH_8 | SOCAM_DATA_ACTIVE_HIGH;
+
+ return soc_camera_apply_sensor_flags(icl, flags);
+}
+
+/* This device only supports one bus option */
+static int ov7670_soc_set_bus_param(struct soc_camera_device *icd,
+ unsigned long flags)
+{
+ return 0;
+}
+
+
+static const struct ov7670_soc_win_size *ov7670_soc_select_win(u32 width,
+ u32 height)
+{
+ const struct ov7670_soc_win_size *wsize;
+
+ /* relies on size order */
+ for (wsize = ov7670_soc_win_sizes;
+ wsize < ov7670_soc_win_sizes + ARRAY_SIZE(ov7670_soc_win_sizes);
+ wsize++)
+ if (width >= wsize->width && height >= wsize->height)
+ break;
+ if (wsize >= ov7670_soc_win_sizes + ARRAY_SIZE(ov7670_soc_win_sizes))
+ wsize--;
+
+ return wsize;
+}
+
+static int ov7670_soc_set_params(struct i2c_client *client,
+ u32 width, u32 height, u32 pixfmt)
+{
+ struct ov7670_soc_priv *priv = to_ov7670_soc(client);
+ int ret = -EINVAL;
+ int i;
+ int data = 0;
+ int matrix[CMATRIX_LEN];
+
+ priv->fmt = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(ov7670_cfmts); i++)
+ if (pixfmt == ov7670_cfmts[i].fourcc) {
+ priv->fmt = ov7670_cfmts + i;
+ ret = 0;
+ break;
+ }
+ if (!priv->fmt)
+ goto error_ret;
+
+ priv->win = ov7670_soc_select_win(width, height);
+
+ if (priv->fmt->fourcc == V4L2_PIX_FMT_RGB565) {
+ data = ov7670_smbus_read_byte_data(client,
+ REG_CLKRC);
+ if (data < 0) {
+ ret = data;
+ goto error_ret;
+ }
+ }
+ if (priv->fmt->regs) {
+ ret = ov7670_write_array(client, priv->fmt->regs);
+ if (ret)
+ goto error_ret;
+ }
+
+ ov7670_calc_cmatrix(priv, matrix);
+ ret = ov7670_store_cmatrix(client, matrix);
+ if (ret)
+ goto error_ret;
+ priv->win = ov7670_soc_select_win(width, height);
+
+ /*
+ * set size format
+ **/
+ ret = ov7670_write_array(client, priv->win->regs);
+ if (ret)
+ goto error_ret;
+
+ if (priv->fmt->fourcc == V4L2_PIX_FMT_RGB565)
+ ret = i2c_smbus_write_byte_data(client,
+ REG_CLKRC,
+ data);
+
+error_ret:
+ return ret;
+}
+
+static int ov7670_soc_s_fmt(struct v4l2_subdev *sd, struct v4l2_format *f)
+{
+ struct i2c_client *client = sd->priv;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ return ov7670_soc_set_params(client, pix->width, pix->height,
+ pix->pixelformat);
+}
+
+static int ov7670_soc_try_fmt(struct v4l2_subdev *sd,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ const struct ov7670_soc_win_size *win;
+
+ win = ov7670_soc_select_win(pix->width, pix->height);
+ pix->width = win->width;
+ pix->height = win->height;
+ pix->field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static int ov7670_soc_g_ctrl(struct v4l2_subdev *sd,
+ struct v4l2_control *ctrl)
+{
+ int data, data2, data3;
+ struct i2c_client *client = sd->priv;
+ struct ov7670_soc_priv *priv = to_ov7670_soc(client);
+
+ switch (ctrl->id) {
+ case V4L2_CID_CONTRAST:
+ data = ov7670_smbus_read_byte_data(client, REG_CONTRAST);
+ if (data < 0)
+ return data;
+ ctrl->value = data;
+ break;
+
+ case V4L2_CID_EXPOSURE_AUTO:
+ data = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data < 0)
+ return data;
+ ctrl->value = !!(data & COM8_AEC);
+ break;
+
+ case V4L2_CID_EXPOSURE_ABSOLUTE:
+ data = ov7670_smbus_read_byte_data(client, REG_AECHH);
+ if (data < 0)
+ return data;
+ data2 = ov7670_smbus_read_byte_data(client, REG_AECH);
+ if (data2 < 0)
+ return data2;
+ data3 = ov7670_smbus_read_byte_data(client, REG_COM1);
+ if (data3 < 0)
+ return data3;
+ ctrl->value = ((data & 0x1F) << 10) |
+ (data2 << 2) |
+ (data3 & 0x03);
+ break;
+
+ case V4L2_CID_AUTO_WHITE_BALANCE:
+ data = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data < 0)
+ return data;
+ ctrl->value = !!(data & COM8_AWB);
+ break;
+
+ case V4L2_CID_AUTOGAIN:
+ data = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data < 0)
+ return data;
+ ctrl->value = !!(data & COM8_AGC);
+ break;
+
+ case V4L2_CID_AUDIO_VOLUME:
+ data = ov7670_smbus_read_byte_data(client, REG_GAIN);
+ if (data < 0)
+ return data;
+ data2 = ov7670_smbus_read_byte_data(client, REG_VREF);
+ if (data2 < 0)
+ return data2;
+ ctrl->value = ((data2 & 0xC0) << 2) | data;
+ break;
+
+ case V4L2_CID_VFLIP:
+ data = ov7670_smbus_read_byte_data(client, REG_MVFP);
+ if (data < 0)
+ return data;
+ ctrl->value = !!(data & MVFP_FLIP);
+ break;
+
+ case V4L2_CID_HFLIP:
+ data = ov7670_smbus_read_byte_data(client, REG_MVFP);
+ if (data < 0)
+ return data;
+ ctrl->value = !!(data & MVFP_MIRROR);
+ break;
+
+ case V4L2_CID_BRIGHTNESS:
+ data = ov7670_smbus_read_byte_data(client, REG_CLKRC);
+ if (data < 0)
+ return data;
+ ctrl->value = data & 0x1F;
+ break;
+
+ case V4L2_CID_HUE:
+ ctrl->value = priv->hue;
+ break;
+
+ case V4L2_CID_SATURATION:
+ ctrl->value = priv->sat;
+ break;
+ };
+
+ return 0;
+}
+
+static int ov7670_tweak_hue_or_sat(struct i2c_client *client,
+ struct ov7670_soc_priv *priv)
+{
+ int matrix[6];
+
+ ov7670_calc_cmatrix(priv, matrix);
+
+ return ov7670_store_cmatrix(client, matrix);
+}
+
+static int ov7670_soc_set_crop(struct soc_camera_device *icd,
+ struct v4l2_rect *rect)
+{
+ struct i2c_client *client = to_i2c_client(to_soc_camera_control(icd));
+ struct ov7670_soc_priv *priv = to_ov7670_soc(client);
+
+ if (!priv->fmt)
+ return -EINVAL;
+
+ return ov7670_soc_set_params(client, rect->width, rect->height,
+ priv->fmt->fourcc);
+}
+
+/*
+ * Note that the brightness parameter is used for clock scaling. What would
+ * be more approriate?
+ */
+static int ov7670_soc_s_ctrl(struct v4l2_subdev *sd,
+ struct v4l2_control *ctrl)
+{
+ int data, ret = 0, data2;
+ struct i2c_client *client = sd->priv;
+ struct ov7670_soc_priv *priv = to_ov7670_soc(client);
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE_AUTO:
+ data = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ data = ctrl->value ? data | COM8_AEC : data & ~COM8_AEC;
+ ret = i2c_smbus_write_byte_data(client, REG_COM8,
+ data);
+ break;
+
+ case V4L2_CID_EXPOSURE_ABSOLUTE:
+ data2 = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data2 < 0) {
+ ret = data2;
+ break;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, REG_COM8,
+ data2 & ~(COM8_AGC | COM8_AEC));
+ if (ret)
+ break;
+ data = ov7670_smbus_read_byte_data(client, REG_AECHH);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ ret = i2c_smbus_write_byte_data(client, REG_AECHH,
+ (data & ~0x1F) |
+ ((ctrl->value >> 10) & 0x1F));
+ if (ret)
+ break;
+
+ ret = i2c_smbus_write_byte_data(client, REG_AECH,
+ (ctrl->value >> 2) & 0xFF);
+ if (ret)
+ break;
+
+ data = ov7670_smbus_read_byte_data(client, REG_COM1);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ ret = i2c_smbus_write_byte_data(client, REG_COM1,
+ (data & ~0x03) |
+ (ctrl->value & 0x03));
+ ret = i2c_smbus_write_byte_data(client, REG_COM8, data2);
+ if (ret)
+ break;
+ break;
+
+ case V4L2_CID_AUTO_WHITE_BALANCE:
+ data = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ data = ctrl->value ? data | COM8_AWB : data & ~COM8_AWB;
+ ret = i2c_smbus_write_byte_data(client, REG_COM8,
+ data);
+ break;
+
+ case V4L2_CID_AUTOGAIN:
+ data = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ data = ctrl->value ? data | COM8_AGC : data & ~COM8_AGC;
+ ret = i2c_smbus_write_byte_data(client, REG_COM8,
+ data);
+ break;
+
+ case V4L2_CID_CONTRAST:
+ ret = i2c_smbus_write_byte_data(client, REG_CONTRAST,
+ ctrl->value);
+ break;
+
+ case V4L2_CID_AUDIO_VOLUME:
+ /* need to disable the AGC and AEC before writing this */
+ data2 = ov7670_smbus_read_byte_data(client, REG_COM8);
+ if (data2 < 0) {
+ ret = data2;
+ break;
+ }
+ ret = i2c_smbus_write_byte_data(client, REG_COM8,
+ data2 & ~(COM8_AGC | COM8_AEC));
+ if (ret)
+ break;
+ ret = i2c_smbus_write_byte_data(client, REG_GAIN,
+ ctrl->value & 0xFF);
+ if (ret)
+ break;
+ data = ov7670_smbus_read_byte_data(client, REG_VREF);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ ret = i2c_smbus_write_byte_data(client, REG_VREF,
+ ((data & ~0xC0) |
+ ((ctrl->value >> 2) & 0xC0)));
+ ret = i2c_smbus_write_byte_data(client, REG_COM8, data2);
+ break;
+
+ case V4L2_CID_VFLIP:
+ data = ov7670_smbus_read_byte_data(client, REG_MVFP);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ data &= 127;
+ data = ctrl->value ? data | MVFP_FLIP : data & ~MVFP_FLIP;
+ ret = i2c_smbus_write_byte_data(client, REG_MVFP,
+ data);
+ break;
+
+ case V4L2_CID_HFLIP:
+ data = ov7670_smbus_read_byte_data(client, REG_MVFP);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ ret = i2c_smbus_write_byte_data(client, REG_MVFP,
+ ctrl->value ? data | MVFP_MIRROR
+ : data & ~MVFP_MIRROR);
+ break;
+
+ case V4L2_CID_BRIGHTNESS:
+ data = ov7670_smbus_read_byte_data(client, REG_CLKRC);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, REG_CLKRC,
+ ctrl->value |
+ (data & 0x80));
+ break;
+
+ case V4L2_CID_GAIN: /* doesn't seem to write successfully */
+ /* first disable auto stuff */
+ ret = i2c_smbus_write_byte_data(client,
+ REG_COM8,
+ COM8_FASTAEC
+ | COM8_AECSTEP
+ | COM8_BFILT);
+ if (ret)
+ break;
+ ret = i2c_smbus_write_byte_data(client, REG_GAIN,
+ ctrl->value & 0xFF);
+ if (ret)
+ break;
+ data = ov7670_smbus_read_byte_data(client, REG_VREF);
+ if (data < 0) {
+ ret = data;
+ break;
+ }
+ ret = i2c_smbus_write_byte_data(client, REG_VREF,
+ (data & ~0xC0) |
+ ((ctrl->value & 0x300) << 6));
+ if (ret)
+ break;
+ /* reenable auto stuff */
+ ret = i2c_smbus_write_byte_data(client, REG_COM8,
+ COM8_FASTAEC |
+ COM8_AECSTEP |
+ COM8_BFILT |
+ COM8_AGC|COM8_AEC);
+ break;
+
+ case V4L2_CID_HUE:
+ /* update locale storage */
+ if (ctrl->value < -180 || ctrl->value > 180) {
+ ret = -EINVAL;
+ break;
+ }
+ priv->hue = ctrl->value;
+ ov7670_tweak_hue_or_sat(client, priv);
+ break;
+
+ case V4L2_CID_SATURATION:
+ /* update locale storage */
+ if (ctrl->value < 0 || ctrl->value > 256) {
+ ret = -EINVAL;
+ break;
+ }
+ priv->sat = ctrl->value;
+ ov7670_tweak_hue_or_sat(client, priv);
+ break;
+
+ case V4L2_CID_RED_BALANCE:
+ ret = i2c_smbus_write_byte_data(client, REG_RED,
+ ctrl->value);
+ break;
+
+ case V4L2_CID_BLUE_BALANCE:
+ ret = i2c_smbus_write_byte_data(client, REG_BLUE,
+ ctrl->value);
+ break;
+ }
+
+ return ret;
+}
+
+static struct soc_camera_ops ov7670_soc_ops = {
+ .set_crop = ov7670_soc_set_crop,
+ .set_bus_param = ov7670_soc_set_bus_param,
+ .query_bus_param = ov7670_soc_query_bus_param,
+ .controls = ov7670_soc_controls,
+ .num_controls = ARRAY_SIZE(ov7670_soc_controls),
+};
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ov7670_soc_s_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct i2c_client *client = sd->priv;
+
+ if (reg->reg > 0xff ||
+ reg->val > 0xff)
+ return -EINVAL;
+
+ return i2c_smbus_write_byte_data(client, reg->reg, reg->val);
+}
+
+static int ov7670_soc_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct i2c_client *client = sd->priv;
+ int ret;
+
+ reg->size = 1;
+ if (reg->reg > 0xff)
+ return -EINVAL;
+ ret = ov7670_smbus_read_byte_data(client, reg->reg);
+ if (ret < 0)
+ return ret;
+
+ reg->val = (__u64)ret;
+
+ return 0;
+}
+#endif /* CONFIG_VIDEO_ADV_DEBUG */
+
+static struct v4l2_subdev_core_ops ov7670_soc_subdev_core_ops = {
+ .g_ctrl = ov7670_soc_g_ctrl,
+ .s_ctrl = ov7670_soc_s_ctrl,
+ .g_chip_ident = ov7670_soc_g_chip_ident,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = ov7670_soc_g_register,
+ .s_register = ov7670_soc_s_register,
+#endif
+};
+
+static struct v4l2_subdev_video_ops ov7670_soc_subdev_video_ops = {
+ .s_fmt = ov7670_soc_s_fmt,
+ .try_fmt = ov7670_soc_try_fmt,
+ .s_stream = ov7670_soc_s_stream,
+};
+
+static struct v4l2_subdev_ops ov7670_soc_subdev_ops = {
+ .core = &ov7670_soc_subdev_core_ops,
+ .video = &ov7670_soc_subdev_video_ops,
+};
+
+
+static int ov7670_soc_probe(struct i2c_client *client,
+ const struct i2c_device_id *did)
+{
+ struct ov7670_soc_priv *priv;
+ struct soc_camera_device *icd = client->dev.platform_data;
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct ov7670_soc_camera_info *info;
+ struct soc_camera_link *icl;
+ int ret;
+
+ if (!icd) {
+ dev_err(&client->dev, "OV7670_SOC: missing soc-camera data!\n");
+ return -EINVAL;
+ }
+
+ icl = to_soc_camera_link(icd);
+ if (!icl)
+ return -EINVAL;
+
+ info = container_of(icl, struct ov7670_soc_camera_info, link);
+
+ gpio_request(info->gpio_reset, "ov7670 soc reset");
+ gpio_request(info->gpio_pwr, "ov7670 soc power");
+
+ /* reset high for normal mode */
+ gpio_direction_output(info->gpio_reset, 1);
+ /* power down normal mode. */
+ gpio_direction_output(info->gpio_pwr, 0);
+ /* perform a hard reset as per tinyos code */
+ gpio_set_value(info->gpio_pwr, 1);
+ gpio_set_value(info->gpio_reset, 1);
+ mdelay(2);
+ gpio_set_value(info->gpio_pwr, 0);
+ gpio_set_value(info->gpio_reset, 0);
+ mdelay(2);
+ gpio_set_value(info->gpio_reset, 1);
+ mdelay(5);
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(&adapter->dev,
+ "I2C-Adapter doesn't support "
+ "I2C_FUNC_SMBUS_BYTE_DATA\n");
+ return -EIO;
+ }
+
+ priv = kzalloc(sizeof *priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->sat = 128;
+ priv->info = info;
+
+ v4l2_i2c_subdev_init(&priv->subdev, client, &ov7670_soc_subdev_ops);
+
+ icd->ops = &ov7670_soc_ops;
+ icd->width_max = MAX_WIDTH;
+ icd->height_max = MAX_HEIGHT;
+
+ ret = ov7670_soc_video_probe(icd, client);
+ if (ret) {
+ dev_err(&client->dev, "failure registering device\n");
+ i2c_set_clientdata(client, NULL);
+ kfree(priv);
+ }
+
+ return ret;
+}
+
+static int ov7670_soc_remove(struct i2c_client *client)
+{
+ struct ov7670_soc_priv *priv = i2c_get_clientdata(client);
+ struct soc_camera_deivce *icd = client->dev.platform_data;
+
+ ov7670_soc_video_remove(icd);
+ i2c_set_clientdata(client, NULL);
+ kfree(priv);
+
+ return 0;
+}
+static const struct i2c_device_id ov7670_soc_id[] = {
+ { "ov7670_soc", 0x15},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, ov7670_soc_id);
+
+static struct i2c_driver ov7670_soc_i2c_driver = {
+ .driver = {
+ .name = "ov7670_soc",
+ },
+ .probe = ov7670_soc_probe,
+ .remove = ov7670_soc_remove,
+ .id_table = ov7670_soc_id,
+};
+
+static int __init ov7670_module_init(void)
+{
+ return i2c_add_driver(&ov7670_soc_i2c_driver);
+}
+
+static void __exit ov7670_module_exit(void)
+{
+ return i2c_del_driver(&ov7670_soc_i2c_driver);
+}
+
+module_init(ov7670_module_init);
+module_exit(ov7670_module_exit);
+
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,21 @@
+/* ov7670_soc Camera
+ *
+ * Copyright (C) 2009 Jonathan Cameron <jic23@cam.ac.uk>
+ *
+ * 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 __OV7670_SOC_H__
+#define __OV7670_SOC_H__
+
+#include <media/soc_camera.h>
+
+struct ov7670_soc_camera_info {
+ int gpio_pwr;
+ int gpio_reset;
+ struct soc_camera_link link;
+};
+
+#endif