From 111f33564e19b2b5f70e3df9a8f92c08c1c91fd9 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 4 Nov 2006 09:26:00 -0300 Subject: [PATCH] V4L/DVB (4798): OmniVision OV7670 driver This patch adds a V4L2 driver for the OmniVision OV7670 camera. Signed-off-by: Jonathan Corbet Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Kconfig | 8 + drivers/media/video/Makefile | 1 + drivers/media/video/ov7670.c | 1002 ++++++++++++++++++++++++++++++++++ include/linux/i2c-id.h | 1 + include/media/v4l2-common.h | 3 + 5 files changed, 1015 insertions(+) create mode 100644 drivers/media/video/ov7670.c diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 4ea1d0ebf5..c95735cd68 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -184,6 +184,14 @@ config VIDEO_KS0127 To compile this driver as a module, choose M here: the module will be called ks0127. +config VIDEO_OV7670 + tristate "OmniVision OV7670 sensor support" + depends on I2C && VIDEO_V4L2 + ---help--- + This is a Video4Linux2 sensor-level driver for the OmniVision + OV7670 VGA camera. It currently only works with the M88ALP01 + controller. + config VIDEO_SAA7110 tristate "Philips SAA7110 video decoder" depends on VIDEO_V4L1 && I2C diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 8ff787a4cf..2adb56d01e 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o +obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_USB_DABUSB) += dabusb.o obj-$(CONFIG_USB_OV511) += ov511.o diff --git a/drivers/media/video/ov7670.c b/drivers/media/video/ov7670.c new file mode 100644 index 0000000000..382aa83b73 --- /dev/null +++ b/drivers/media/video/ov7670.c @@ -0,0 +1,1002 @@ +/* + * A V4L2 driver for OmniVision OV7670 cameras. + * + * Copyright 2006 One Laptop Per Child Association, Inc. Written + * by Jonathan Corbet with substantial inspiration from Mark + * McClelland's ovcamchip code. + * + * This file may be distributed under the terms of the GNU General + * Public License, version 2. + */ +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_AUTHOR("Jonathan Corbet 0) + *value = (unsigned char) ret; + return ret; +} + + +static int ov7670_write(struct i2c_client *c, unsigned char reg, + unsigned char value) +{ + return i2c_smbus_write_byte_data(c, reg, value); +} + + +/* + * Write a list of register settings; ff/ff stops the process. + */ +static int ov7670_write_array(struct i2c_client *c, struct regval_list *vals) +{ + while (vals->reg_num != 0xff || vals->value != 0xff) { + int ret = ov7670_write(c, vals->reg_num, vals->value); + if (ret < 0) + return ret; + vals++; + } + return 0; +} + + +/* + * Stuff that knows about the sensor. + */ +static void ov7670_reset(struct i2c_client *client) +{ + ov7670_write(client, REG_COM7, COM7_RESET); + msleep(1); +} + + +static int ov7670_init(struct i2c_client *client) +{ + return ov7670_write_array(client, ov7670_default_regs); +} + + + +static int ov7670_detect(struct i2c_client *client) +{ + unsigned char v; + int ret; + + ret = ov7670_init(client); + if (ret < 0) + return ret; + ret = ov7670_read(client, REG_MIDH, &v); + if (ret < 0) + return ret; + if (v != 0x7f) /* OV manuf. id. */ + return -ENODEV; + ret = ov7670_read(client, REG_MIDL, &v); + if (ret < 0) + return ret; + if (v != 0xa2) + return -ENODEV; + /* + * OK, we know we have an OmniVision chip...but which one? + */ + ret = ov7670_read(client, REG_PID, &v); + if (ret < 0) + return ret; + if (v != 0x76) /* PID + VER = 0x76 / 0x73 */ + return -ENODEV; + ret = ov7670_read(client, REG_VER, &v); + if (ret < 0) + return ret; + if (v != 0x73) /* PID + VER = 0x76 / 0x73 */ + return -ENODEV; + return 0; +} + + + + + +static struct ov7670_format_struct { + __u8 *desc; + __u32 pixelformat; + struct regval_list *regs; +} ov7670_formats[] = { + { + .desc = "YUYV 4:2:2", + .pixelformat = V4L2_PIX_FMT_YUYV, + .regs = ov7670_fmt_yuv422, + }, + { + .desc = "RGB 444", + .pixelformat = V4L2_PIX_FMT_RGB444, + .regs = ov7670_fmt_rgb444, + }, + { + .desc = "RGB 565", + .pixelformat = V4L2_PIX_FMT_RGB565, + .regs = ov7670_fmt_rgb565, + }, + /* + * Pretend we do RGB32. This is here on the assumption that the + * upper layer will reformat RGB444 appropriately. + * + * The entire purpose for this thing's existence is to enable easy + * display of RGB444 for debugging purposes. It will come out soon. + */ + { + .desc = "RGB32 (faked)", + .pixelformat = V4L2_PIX_FMT_RGB32, + .regs = ov7670_fmt_rgb444, + }, +}; +#define N_OV7670_FMTS (sizeof(ov7670_formats)/sizeof(ov7670_formats[0])) + +/* + * All formats we support are 2 bytes/pixel. + */ +#define BYTES_PER_PIXEL 2 + +/* + * Then there is the issue of window sizes. Try to capture the info here. + */ +static struct ov7670_win_size { + int width; + int height; + unsigned char com7_bit; + int hstart; /* Start/stop values for the camera. Note */ + int hstop; /* that they do not always make complete */ + int vstart; /* sense to humans, but evidently the sensor */ + int vstop; /* will do the right thing... */ +/* h/vref stuff */ +} ov7670_win_sizes[] = { + /* VGA */ + { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .com7_bit = COM7_FMT_VGA, + .hstart = 158, /* These values from */ + .hstop = 14, /* Omnivision */ + .vstart = 10, + .vstop = 490, + }, + /* CIF */ + { + .width = CIF_WIDTH, + .height = CIF_HEIGHT, + .com7_bit = COM7_FMT_CIF, + .hstart = 170, /* Empirically determined */ + .hstop = 90, + .vstart = 14, + .vstop = 494, + }, + /* QVGA */ + { + .width = QVGA_WIDTH, + .height = QVGA_HEIGHT, + .com7_bit = COM7_FMT_QVGA, + .hstart = 164, /* Empirically determined */ + .hstop = 20, + .vstart = 14, + .vstop = 494, + }, +}; + +#define N_WIN_SIZES (sizeof(ov7670_win_sizes)/sizeof(ov7670_win_sizes[0])) + + +/* + * Store a set of start/stop values into the camera. + */ +static int ov7670_set_hw(struct i2c_client *client, int hstart, int hstop, + int vstart, int vstop) +{ + int ret; + unsigned char v; +/* + * Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of + * hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is + * a mystery "edge offset" value in the top two bits of href. + */ + ret = ov7670_write(client, REG_HSTART, (hstart >> 3) & 0xff); + ret += ov7670_write(client, REG_HSTOP, (hstop >> 3) & 0xff); + ret += ov7670_read(client, REG_HREF, &v); + v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7); + msleep(10); + ret += ov7670_write(client, REG_HREF, v); +/* + * Vertical: similar arrangement, but only 10 bits. + */ + ret += ov7670_write(client, REG_VSTART, (vstart >> 2) & 0xff); + ret += ov7670_write(client, REG_VSTOP, (vstop >> 2) & 0xff); + ret += ov7670_read(client, REG_VREF, &v); + v = (v & 0xf0) | ((vstop & 0x3) << 2) | (vstart & 0x3); + msleep(10); + ret += ov7670_write(client, REG_VREF, v); + return ret; +} + + +static int ov7670_enum_fmt(struct i2c_client *c, struct v4l2_fmtdesc *fmt) +{ + struct ov7670_format_struct *ofmt; + + if (fmt->index >= N_OV7670_FMTS) + return -EINVAL; + + ofmt = ov7670_formats + fmt->index; + fmt->flags = 0; + strcpy(fmt->description, ofmt->desc); + fmt->pixelformat = ofmt->pixelformat; + return 0; +} + + +static int ov7670_try_fmt(struct i2c_client *c, struct v4l2_format *fmt, + struct ov7670_format_struct **ret_fmt, + struct ov7670_win_size **ret_wsize) +{ + int index; + struct ov7670_win_size *wsize; + struct v4l2_pix_format *pix = &fmt->fmt.pix; + + for (index = 0; index < N_OV7670_FMTS; index++) + if (ov7670_formats[index].pixelformat == pix->pixelformat) + break; + if (index >= N_OV7670_FMTS) + return -EINVAL; + if (ret_fmt != NULL) + *ret_fmt = ov7670_formats + index; + /* + * Fields: the OV devices claim to be progressive. + */ + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_NONE; + else if (pix->field != V4L2_FIELD_NONE) + return -EINVAL; + /* + * Round requested image size down to the nearest + * we support, but not below the smallest. + */ + for (wsize = ov7670_win_sizes; wsize < ov7670_win_sizes + N_WIN_SIZES; + wsize++) + if (pix->width >= wsize->width && pix->height >= wsize->height) + break; + if (wsize > ov7670_win_sizes + N_WIN_SIZES) + wsize--; /* Take the smallest one */ + if (ret_wsize != NULL) + *ret_wsize = wsize; + /* + * Note the size we'll actually handle. + */ + pix->width = wsize->width; + pix->height = wsize->height; + pix->bytesperline = pix->width*BYTES_PER_PIXEL; + if (pix->pixelformat == V4L2_PIX_FMT_RGB32) + pix->bytesperline *= 2; + pix->sizeimage = pix->height*pix->bytesperline; + return 0; + +} + +/* + * Set a format. + */ +static int ov7670_s_fmt(struct i2c_client *c, struct v4l2_format *fmt) +{ + int ret; + struct ov7670_format_struct *ovfmt; + struct ov7670_win_size *wsize; + unsigned char com7; + + ret = ov7670_try_fmt(c, fmt, &ovfmt, &wsize); + if (ret) + return ret; + /* + * COM7 is a pain in the ass, it doesn't like to be read then + * quickly written afterward. But we have everything we need + * to set it absolutely here, as long as the format-specific + * register sets list it first. + */ + com7 = ovfmt->regs[0].value; + com7 |= wsize->com7_bit; + ov7670_write(c, REG_COM7, com7); + /* + * Now write the rest of the array. Also store start/stops + */ + ov7670_write_array(c, ovfmt->regs + 1); + ov7670_set_hw(c, wsize->hstart, wsize->hstop, wsize->vstart, + wsize->vstop); + return 0; +} + +/* + * Code for dealing with controls. + */ + +/* + * Some weird registers seem to store values in a sign/magnitude format! + */ +static unsigned char ov7670_sm_to_abs(unsigned char v) +{ + if ((v & 0x80) == 0) + return v + 128; + else + return 128 - (v & 0x7f); +} + + +static unsigned char ov7670_abs_to_sm(unsigned char v) +{ + if (v > 127) + return v & 0x7f; + else + return (128 - v) | 0x80; +} + +static int ov7670_t_brightness(struct i2c_client *client, unsigned char value) +{ + unsigned char com8; + int ret; + + ov7670_read(client, REG_COM8, &com8); + com8 &= ~COM8_AEC; + ov7670_write(client, REG_COM8, com8); + value = ov7670_abs_to_sm(value); + ret = ov7670_write(client, REG_BRIGHT, value); + return ret; +} + +static int ov7670_q_brightness(struct i2c_client *client, unsigned char *value) +{ + int ret; + ret = ov7670_read(client, REG_BRIGHT, value); + *value = ov7670_sm_to_abs(*value); + return ret; +} + +static int ov7670_t_contrast(struct i2c_client *client, unsigned char value) +{ + return ov7670_write(client, REG_CONTRAS, value); +} + +static int ov7670_q_contrast(struct i2c_client *client, unsigned char *value) +{ + return ov7670_read(client, REG_CONTRAS, value); +} + +static int ov7670_q_hflip(struct i2c_client *client, unsigned char *value) +{ + int ret; + unsigned char v; + + ret = ov7670_read(client, REG_MVFP, &v); + *value = (v & MVFP_MIRROR) == MVFP_MIRROR; + return ret; +} + + +static int ov7670_t_hflip(struct i2c_client *client, unsigned char value) +{ + unsigned char v; + int ret; + + ret = ov7670_read(client, REG_MVFP, &v); + if (value) + v |= MVFP_MIRROR; + else + v &= ~MVFP_MIRROR; + msleep(10); /* FIXME */ + ret += ov7670_write(client, REG_MVFP, v); + return ret; +} + + + +static int ov7670_q_vflip(struct i2c_client *client, unsigned char *value) +{ + int ret; + unsigned char v; + + ret = ov7670_read(client, REG_MVFP, &v); + *value = (v & MVFP_FLIP) == MVFP_FLIP; + return ret; +} + + +static int ov7670_t_vflip(struct i2c_client *client, unsigned char value) +{ + unsigned char v; + int ret; + + ret = ov7670_read(client, REG_MVFP, &v); + if (value) + v |= MVFP_FLIP; + else + v &= ~MVFP_FLIP; + msleep(10); /* FIXME */ + ret += ov7670_write(client, REG_MVFP, v); + return ret; +} + + +static struct ov7670_control { + struct v4l2_queryctrl qc; + int (*query)(struct i2c_client *c, unsigned char *value); + int (*tweak)(struct i2c_client *c, unsigned char value); +} ov7670_controls[] = +{ + { + .qc = { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0x80, + .flags = V4L2_CTRL_FLAG_SLIDER + }, + .tweak = ov7670_t_brightness, + .query = ov7670_q_brightness, + }, + { + .qc = { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 0x40, /* XXX ov7670 spec */ + .flags = V4L2_CTRL_FLAG_SLIDER + }, + .tweak = ov7670_t_contrast, + .query = ov7670_q_contrast, + }, + { + .qc = { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + .tweak = ov7670_t_vflip, + .query = ov7670_q_vflip, + }, + { + .qc = { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + .tweak = ov7670_t_hflip, + .query = ov7670_q_hflip, + }, +}; +#define N_CONTROLS (sizeof(ov7670_controls)/sizeof(ov7670_controls[0])) + +static struct ov7670_control *ov7670_find_control(__u32 id) +{ + int i; + + for (i = 0; i < N_CONTROLS; i++) + if (ov7670_controls[i].qc.id == id) + return ov7670_controls + i; + return NULL; +} + + +static int ov7670_queryctrl(struct i2c_client *client, + struct v4l2_queryctrl *qc) +{ + struct ov7670_control *ctrl = ov7670_find_control(qc->id); + + if (ctrl == NULL) + return -EINVAL; + *qc = ctrl->qc; + return 0; +} + +static int ov7670_g_ctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct ov7670_control *octrl = ov7670_find_control(ctrl->id); + int ret; + unsigned char v; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->query(client, &v); + if (ret >= 0) { + ctrl->value = v; + return 0; + } + return ret; +} + +static int ov7670_s_ctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct ov7670_control *octrl = ov7670_find_control(ctrl->id); + + if (octrl == NULL) + return -EINVAL; + return octrl->tweak(client, ctrl->value); +} + + + + + + + +/* + * Basic i2c stuff. + */ +static struct i2c_driver ov7670_driver; + +static int ov7670_attach(struct i2c_adapter *adapter) +{ + int ret; + struct i2c_client *client; + + printk(KERN_ERR "ov7670 attach, id = %d\n", adapter->id); + /* + * For now: only deal with adapters we recognize. + */ + if (adapter->id != I2C_HW_SMBUS_CAFE) + return -ENODEV; + + printk(KERN_ERR "ov7670 accepting\n"); + client = kzalloc(sizeof (struct i2c_client), GFP_KERNEL); + if (! client) + return -ENOMEM; + client->adapter = adapter; + client->addr = OV7670_I2C_ADDR; + client->driver = &ov7670_driver, + strcpy(client->name, "OV7670"); + /* Do we need clientdata? */ + + /* + * Make sure it's an ov7670 + */ + ret = ov7670_detect(client); + printk(KERN_ERR "detect result is %d\n", ret); + if (ret) + goto out_free; + i2c_attach_client(client); + return 0; + + out_free: + kfree(client); + return ret; +} + + +static int ov7670_detach(struct i2c_client *client) +{ + i2c_detach_client(client); + kfree(client); + return 0; +} + + +static int ov7670_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + switch (cmd) { + case VIDIOC_INT_G_CHIP_IDENT: + * (enum v4l2_chip_ident *) arg = V4L2_IDENT_OV7670; + return 0; + + case VIDIOC_INT_RESET: + ov7670_reset(client); + return 0; + + case VIDIOC_INT_INIT: + return ov7670_init(client); + + case VIDIOC_ENUM_FMT: + return ov7670_enum_fmt(client, (struct v4l2_fmtdesc *) arg); + case VIDIOC_TRY_FMT: + return ov7670_try_fmt(client, (struct v4l2_format *) arg, NULL, NULL); + case VIDIOC_S_FMT: + return ov7670_s_fmt(client, (struct v4l2_format *) arg); + case VIDIOC_QUERYCTRL: + return ov7670_queryctrl(client, (struct v4l2_queryctrl *) arg); + case VIDIOC_S_CTRL: + return ov7670_s_ctrl(client, (struct v4l2_control *) arg); + case VIDIOC_G_CTRL: + return ov7670_g_ctrl(client, (struct v4l2_control *) arg); + /* Todo: + g/s_parm + initialization + */ + } + return -EINVAL; +} + + + +static struct i2c_driver ov7670_driver = { + .driver = { + .name = "ov7670", + }, + .id = I2C_DRIVERID_OV7670, + .class = I2C_CLASS_CAM_DIGITAL, + .attach_adapter = ov7670_attach, + .detach_client = ov7670_detach, + .command = ov7670_command, +}; + + +/* + * Module initialization + */ +static int __init ov7670_mod_init(void) +{ + printk(KERN_NOTICE "OmniVision ov7670 sensor driver, at your service\n"); + return i2c_add_driver(&ov7670_driver); +} + +static void __exit ov7670_mod_exit(void) +{ + i2c_del_driver(&ov7670_driver); +} + +module_init(ov7670_mod_init); +module_exit(ov7670_mod_exit); diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index a32db163cb..0f4cf34b6f 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -159,6 +159,7 @@ #define I2C_DRIVERID_ASB100 1043 #define I2C_DRIVERID_FSCHER 1046 #define I2C_DRIVERID_W83L785TS 1047 +#define I2C_DRIVERID_OV7670 1048 /* Omnivision 7670 camera */ /* * ---- Adapter types ---------------------------------------------------- diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h index 8263ea00ca..91b19921f9 100644 --- a/include/media/v4l2-common.h +++ b/include/media/v4l2-common.h @@ -144,6 +144,9 @@ enum v4l2_chip_ident { V4L2_IDENT_CX25841 = 241, V4L2_IDENT_CX25842 = 242, V4L2_IDENT_CX25843 = 243, + + /* OmniVision sensors - range 250-299 */ + V4L2_IDENT_OV7670 = 250, }; /* audio ioctls */ -- 2.39.5