diff -Naur linux-2.4.0-test5-pre4-orig/Documentation/usb/ov511.txt linux/Documentation/usb/ov511.txt --- linux-2.4.0-test5-pre4-orig/Documentation/usb/ov511.txt Sun Jul 23 04:20:06 2000 +++ linux/Documentation/usb/ov511.txt Sun Jul 23 05:38:41 2000 @@ -6,14 +6,14 @@ Homepage: http://alpha.dyndns.org/ov511 NEW IN THIS VERSION: - o Sensor detection fixes - o More efficient/reliable buffer allocation - o Many minor fixes + o OV6620 sensor support (not working correctly yet) + o 160x120 support (not working either) + o Experimentation with compression has begun INTRODUCTION: This is a driver for the OV511, a USB-only chip used in many "webcam" devices. -Any camera using the OV511/OV511+ and the OV7610/20/20AE CCD should work.It +Any camera using the OV511/OV511+ and the OV7610/20/20AE CCD should work. It supports streaming and capture of color or monochrome video via the Video4Linux API. Most V4L apps are compatible with it, but a few videoconferencing programs do not work yet. The following resolutions are supported: 640x480, 448x336, @@ -208,7 +208,8 @@ o fix_rgb_offset: Sometimes works, but other times causes errors with xawtv and corrupted frames. If you have a very fast CPU, you can try it. o Snapshot mode (only works with some read() based apps; see below for more) - o read() support + o 160x120 support + o OV6620 sensor support TODO: o Fix the noise / grainy image problem. @@ -216,7 +217,6 @@ frame rate quite a bit. OmniVision wouldn't tell me how the algorithm works, so we can't really work on that yet. Please kindly inform OmniVision that you would like them to release their specifications to the Linux community. - o Get 160x120 working o YUV422 (and other color modes) o Get snapshot mode working with mmap(). o Fix fixFrameRGBoffset(). It is not stable yet with streaming video. @@ -224,11 +224,10 @@ and ov511_set_picture()) o Get autoadjust disable working o V4L2 support (Probably not until it goes into the kernel) - o Fix I2C initialization. Some people are reporting problems with reading the - 7610 registers. This could be due to timing differences, an excessive I2C - clock rate, or a problem with ov511_i2c_read(). + o Creative WebCam III has problems initializing its sensor. This should be + fixed now, but if you still have problems let me know. o Get rid of the memory management functions (put them in videodev.c??) - o Setting of contrast and brightness not working with 7620 + o Setting of contrast and brightness not working with 7620/7620AE/6620 o Driver/camera state save/restore for when USB supports suspend/resume o Unstable on SMP systems diff -Naur linux-2.4.0-test5-pre4-orig/drivers/usb/ov511.c linux/drivers/usb/ov511.c --- linux-2.4.0-test5-pre4-orig/drivers/usb/ov511.c Sun Jul 23 04:20:19 2000 +++ linux/drivers/usb/ov511.c Sun Jul 23 05:29:55 2000 @@ -30,7 +30,7 @@ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -static const char version[] = "1.20"; +static const char version[] = "1.21"; #define __NO_VERSION__ @@ -107,6 +107,13 @@ /* Prevent apps from timing out if frame is not done in time */ static int retry_sync = 0; +/* Enable compression. This is for experimentation only; compressed images + * still cannot be decoded yet. */ +static int compress = 0; + +/* Display test pattern - doesn't work yet either */ +static int testpat = 0; + MODULE_PARM(autoadjust, "i"); MODULE_PARM(debug, "i"); MODULE_PARM(fix_rgb_offset, "i"); @@ -118,6 +125,8 @@ MODULE_PARM(buf_timeout, "i"); MODULE_PARM(cams, "i"); MODULE_PARM(retry_sync, "i"); +MODULE_PARM(compress, "i"); +MODULE_PARM(testpat, "i"); MODULE_AUTHOR("Mark McClelland & Bret Wallach & Orion Sky Lawlor & Kevin Moore & Charl P. Botha & Claudio Matsuoka "); MODULE_DESCRIPTION("OV511 USB Camera Driver"); @@ -861,7 +870,9 @@ { 384, 288, 0, 0x2f, 0x25, 0x00, 0x00, 0x03, 0x04, 0x04, 0x1e }, { 384, 288, 1, 0x2f, 0x25, 0x00, 0x00, 0x03, 0x04, 0x04, 0x1e }, { 448, 336, 0, 0x37, 0x29, 0x00, 0x00, 0x03, 0x04, 0x04, 0x1e }, - { 448, 336, 1 ,0x37, 0x29, 0x00, 0x00, 0x03, 0x04, 0x04, 0x1e }, + { 448, 336, 1, 0x37, 0x29, 0x00, 0x00, 0x03, 0x04, 0x04, 0x1e }, + { 160, 120, 0, 0x13, 0x0e, 0x01, 0x01, 0x03, 0x04, 0x04, 0x1e }, + { 160, 120, 1, 0x13, 0x0e, 0x01, 0x01, 0x03, 0x04, 0x04, 0x1e }, { 0, 0 } }; @@ -883,7 +894,7 @@ ov511_reg_write(dev, 0x16, 0x00); if (ov511->sensor == SEN_OV7610 || ov511->sensor == SEN_OV7620AE) { - /* these aren't valid on the OV7620 */ + /* these aren't valid on the OV6620/OV7620 */ ov511_i2c_write(dev, 0x0e, 0x44); } ov511_i2c_write(dev, 0x13, autoadjust ? 0x21 : 0x20); @@ -895,7 +906,7 @@ ov511_reg_write(dev, 0x16, 0x01); if (ov511->sensor == SEN_OV7610 || ov511->sensor == SEN_OV7620AE) { - /* not valid on the OV7620 */ + /* not valid on the OV6620/OV7620 */ ov511_i2c_write(dev, 0x0e, 0x04); } ov511_i2c_write(dev, 0x13, autoadjust ? 0x01 : 0x00); @@ -913,6 +924,7 @@ hwebase = 0x3a; break; case SEN_OV7620: + case SEN_OV6620: hwsbase = 0x2c; hwebase = 0x2d; break; @@ -969,17 +981,23 @@ #ifdef OV511_GBR422 ov511_i2c_write(dev, 0x12, mlist[i].common_A | 0x08); #else - ov511_i2c_write(dev, 0x12, mlist[i].common_A); + ov511_i2c_write(dev, 0x12, mlist[i].common_A | (testpat?0x02:0x00)); #endif ov511_i2c_write(dev, 0x14, mlist[i].common_C); - /* 7620 doesn't have register 0x35, so play it safe */ - if (ov511->sensor != SEN_OV7620) + /* 7620/6620 don't have register 0x35, so play it safe */ + if (ov511->sensor == SEN_OV7610 || + ov511->sensor == SEN_OV7620AE) ov511_i2c_write(dev, 0x35, mlist[i].common_L); break; } + if (compress) { + ov511_reg_write(dev, 0x78, 0x03); // Turn on Y compression + ov511_reg_write(dev, 0x79, 0x00); // Disable LUTs + } + if (ov511_restart(ov511->dev) < 0) return -EIO; @@ -1092,6 +1110,9 @@ #undef OV511_DUMPPIX +/* #define this and OV511_DUMPPIX to disable parsing of UV data */ +#undef OV511_FORCE_MONO + #ifdef OV511_GBR422 static void ov511_parse_data_rgb24(unsigned char *pIn0, unsigned char *pOut0, @@ -1222,6 +1243,8 @@ } } #else + +#ifndef OV511_FORCE_MONO /* Just dump pix data straight out for debug */ int i, j; @@ -1234,6 +1257,28 @@ } pOut0 += (iWidth - WDIV) * 3; } +#else + /* This converts the Y data to "black-and-white" RGB data */ + /* Useful for experimenting with compression */ + int k, l, m; + unsigned char *pIn, *pOut, *pOut1; + + pIn = pIn0 + 128; + pOut = pOut0 + iOutY; + for (k = 0; k < 4; k++) { + pOut1 = pOut; + for (l = 0; l < 8; l++) { + for (m = 0; m < 8; m++) { + *pOut1++ = *pIn; + *pOut1++ = *pIn; + *pOut1++ = *pIn++; + } + pOut1 += (iWidth - 8) * 3; + } + pOut += 8 * 3; + } +#endif + #endif } #endif @@ -1621,6 +1666,7 @@ iY & 1, frame->width); break; case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: ov511_parse_data_yuv422(pData, pOut, iOutY, iOutUV, iY & 1, frame->width); break; @@ -1662,7 +1708,7 @@ return; if (!ov511->streaming) { - PDEBUG(2, "hmmm... not streaming, but got interrupt"); + PDEBUG(4, "hmmm... not streaming, but got interrupt"); return; } @@ -2114,6 +2160,7 @@ if (p.palette != VIDEO_PALETTE_GREY && p.palette != VIDEO_PALETTE_RGB24 && p.palette != VIDEO_PALETTE_YUV422 && + p.palette != VIDEO_PALETTE_YUYV && p.palette != VIDEO_PALETTE_YUV422P) return -EINVAL; @@ -2256,6 +2303,7 @@ if (vm.format != VIDEO_PALETTE_RGB24 && vm.format != VIDEO_PALETTE_YUV422 && + vm.format != VIDEO_PALETTE_YUYV && vm.format != VIDEO_PALETTE_YUV422P && vm.format != VIDEO_PALETTE_GREY) return -EINVAL; @@ -2380,14 +2428,14 @@ return 0; } case VIDIOCKEY: - return 0; + return 0; case VIDIOCCAPTURE: return -EINVAL; case VIDIOCSFBUF: return -EINVAL; case VIDIOCGTUNER: case VIDIOCSTUNER: - return -EINVAL; + return -EINVAL; case VIDIOCGFREQ: case VIDIOCSFREQ: return -EINVAL; @@ -2587,12 +2635,9 @@ { OV511_I2C_BUS, 0x16, 0x06 }, { OV511_I2C_BUS, 0x28, 0x24 }, { OV511_I2C_BUS, 0x2b, 0xac }, - { OV511_I2C_BUS, 0x05, 0x00 }, - { OV511_I2C_BUS, 0x06, 0x00 }, { OV511_I2C_BUS, 0x12, 0x00 }, { OV511_I2C_BUS, 0x38, 0x81 }, { OV511_I2C_BUS, 0x28, 0x24 }, /* 0c */ - { OV511_I2C_BUS, 0x05, 0x00 }, { OV511_I2C_BUS, 0x0f, 0x85 }, /* lg's setting */ { OV511_I2C_BUS, 0x15, 0x01 }, { OV511_I2C_BUS, 0x20, 0x1c }, @@ -2600,7 +2645,6 @@ { OV511_I2C_BUS, 0x24, 0x10 }, { OV511_I2C_BUS, 0x25, 0x8a }, { OV511_I2C_BUS, 0x27, 0xc2 }, - { OV511_I2C_BUS, 0x29, 0x03 }, /* 91 */ { OV511_I2C_BUS, 0x2a, 0x04 }, { OV511_I2C_BUS, 0x2c, 0xfe }, { OV511_I2C_BUS, 0x30, 0x71 }, @@ -2622,14 +2666,12 @@ { OV511_I2C_BUS, 0x2b, 0xac }, { OV511_I2C_BUS, 0x12, 0x00 }, { OV511_I2C_BUS, 0x28, 0x24 }, - { OV511_I2C_BUS, 0x05, 0x00 }, { OV511_I2C_BUS, 0x0f, 0x85 }, /* lg's setting */ { OV511_I2C_BUS, 0x15, 0x01 }, { OV511_I2C_BUS, 0x23, 0x00 }, { OV511_I2C_BUS, 0x24, 0x10 }, { OV511_I2C_BUS, 0x25, 0x8a }, { OV511_I2C_BUS, 0x27, 0xe2 }, - { OV511_I2C_BUS, 0x29, 0x03 }, { OV511_I2C_BUS, 0x2a, 0x00 }, { OV511_I2C_BUS, 0x2c, 0xfe }, { OV511_I2C_BUS, 0x30, 0x71 }, @@ -2646,24 +2688,13 @@ PDEBUG (4, "starting configuration"); - if(ov511_reg_write(dev, OV511_REG_I2C_SLAVE_ID_WRITE, - OV7610_I2C_WRITE_ID) < 0) - return -1; - - if(ov511_reg_write(dev, OV511_REG_I2C_SLAVE_ID_READ, - OV7610_I2C_READ_ID) < 0) - return -1; - - if (ov511_reset(dev, OV511_RESET_NOREGS) < 0) - return -1; /* Reset the 76xx */ if (ov511_i2c_write(dev, 0x12, 0x80) < 0) return -1; -#if 1 /* Maybe this will fix detection problems? MM */ + /* Maybe this will fix detection problems? MM */ /* Wait for it to initialize */ schedule_timeout (1 + 150 * HZ / 1000); -#endif for (i = 0, success = 0; i < i2c_detect_tries && !success; i++) { if ((ov511_i2c_read(dev, OV7610_REG_ID_HIGH) == 0x7F) && @@ -2724,6 +2755,126 @@ return -1; } + /* Set sensor-specific vars */ + ov511->maxwidth = 640; + ov511->maxheight = 480; + + if (aperture < 0) { /* go with the default */ + if (ov511_i2c_write(dev, 0x26, 0xa2) < 0) return -1; + } else if (aperture <= 0xf) { /* user overrode default */ + if (ov511_i2c_write(dev, 0x26, (aperture << 4) + 2) < 0) + return -1; + } else { + err("Invalid setting for aperture; legal value: 0 - 15"); + return -1; + } + + if (autoadjust) { + if (ov511_i2c_write(dev, 0x13, 0x01) < 0) return -1; + if (ov511_i2c_write(dev, 0x2d, + ov511->sensor==SEN_OV7620?0x91:0x93) < 0) return -1; + } else { + if (ov511_i2c_write(dev, 0x13, 0x00) < 0) return -1; + if (ov511_i2c_write(dev, 0x2d, + ov511->sensor==SEN_OV7620?0x81:0x83) < 0) return -1; + ov511_i2c_write(dev, 0x28, ov511_i2c_read(dev, 0x28) | 8); + } + + return 0; +} + +static int ov6xx0_configure(struct usb_ov511 *ov511) +{ + struct usb_device *dev = ov511->dev; + int i, success, rc; + + static struct ov511_regvals aRegvalsNorm6x20[] = { + { OV511_I2C_BUS, 0x12, 0x80 }, /* reset */ + { OV511_I2C_BUS, 0x11, 0x01 }, + { OV511_I2C_BUS, 0x12, 0x24 }, /* ? */ + { OV511_I2C_BUS, 0x03, 0xd0 }, + { OV511_I2C_BUS, 0x05, 0x7f }, + { OV511_I2C_BUS, 0x07, 0xa8 }, + { OV511_I2C_BUS, 0x0c, 0x24 }, + { OV511_I2C_BUS, 0x0d, 0x24 }, + { OV511_I2C_BUS, 0x10, 0xff }, /* ? */ + { OV511_I2C_BUS, 0x14, 0x04 }, + { OV511_I2C_BUS, 0x16, 0x06 }, /* ? */ + { OV511_I2C_BUS, 0x19, 0x04 }, + { OV511_I2C_BUS, 0x1a, 0x93 }, + { OV511_I2C_BUS, 0x20, 0x28 }, + { OV511_I2C_BUS, 0x27, 0xa2 }, + { OV511_I2C_BUS, 0x28, 0x24 }, + { OV511_I2C_BUS, 0x2a, 0x04 }, /* 84? */ + { OV511_I2C_BUS, 0x2b, 0xac }, /* a8? */ + { OV511_I2C_BUS, 0x2d, 0x95 }, + { OV511_I2C_BUS, 0x33, 0x28 }, + { OV511_I2C_BUS, 0x34, 0xc7 }, + { OV511_I2C_BUS, 0x38, 0x8b }, + { OV511_I2C_BUS, 0x3c, 0x5c }, + { OV511_I2C_BUS, 0x3d, 0x80 }, + { OV511_I2C_BUS, 0x4d, 0xd2 }, + { OV511_I2C_BUS, 0x4e, 0xc1 }, + { OV511_I2C_BUS, 0x4f, 0x04 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + PDEBUG (4, "starting sensor configuration"); + + /* Reset the 6xx0 */ + if (ov511_i2c_write(dev, 0x12, 0x80) < 0) return -1; + + /* Wait for it to initialize */ + schedule_timeout (1 + 150 * HZ / 1000); + + for (i = 0, success = 0; i < i2c_detect_tries && !success; i++) { + if ((ov511_i2c_read(dev, OV7610_REG_ID_HIGH) == 0x7F) && + (ov511_i2c_read(dev, OV7610_REG_ID_LOW) == 0xA2)) { + success = 1; + continue; + } + + /* Reset the 6xx0 */ + if (ov511_i2c_write(dev, 0x12, 0x80) < 0) return -1; + /* Wait for it to initialize */ + schedule_timeout (1 + 150 * HZ / 1000); + /* Dummy read to sync I2C */ + if (ov511_i2c_read(dev, 0x00) < 0) return -1; + } + + if (success) { + PDEBUG(1, "I2C synced in %d attempt(s)", i); + } else { + err("Failed to read sensor ID. You might not have an OV6xx0,"); + err("or it may be not responding. Report this to"); + err("mmcclelland@delphi.com"); + return -1; + } + + /* Detect sensor if user didn't use override param */ + if (sensor == 0) { + rc = ov511_i2c_read(dev, OV7610_REG_COM_I); + + if (rc < 0) { + err("Error detecting sensor type"); + return -1; + } else { + info("Sensor is an OV6xx0 (version %d)", rc & 3); + ov511->sensor = SEN_OV6620; + } + } else { /* sensor != 0; user overrode detection */ + ov511->sensor = sensor; + info("Sensor set to type %d", ov511->sensor); + } + + /* Set sensor-specific vars */ + ov511->maxwidth = 352; + ov511->maxheight = 288; + + PDEBUG(4, "Writing 6x20 registers"); + if (ov511_write_regvals(dev, aRegvalsNorm6x20)) + return -1; + if (aperture < 0) { /* go with the default */ if (ov511_i2c_write(dev, 0x26, 0xa2) < 0) return -1; } else if (aperture <= 0xf) { /* user overrode default */ @@ -2769,10 +2920,10 @@ { OV511_REG_BUS, OV511_REG_DRAM_ENABLE_FLOW_CONTROL, 0x01 }, { OV511_REG_BUS, OV511_REG_SYSTEM_SNAPSHOT, 0x02 }, { OV511_REG_BUS, OV511_REG_SYSTEM_SNAPSHOT, 0x00 }, - { OV511_REG_BUS, OV511_REG_FIFO_BITMASK, 0x1f }, /* 0f */ - { OV511_REG_BUS, OV511_OMNICE_PREDICTION_HORIZ_Y, 0x3f }, - { OV511_REG_BUS, OV511_OMNICE_PREDICTION_HORIZ_UV, 0x3f }, - { OV511_REG_BUS, OV511_OMNICE_PREDICTION_VERT_Y, 0x01 }, + { OV511_REG_BUS, OV511_REG_FIFO_BITMASK, 0x1f }, + { OV511_REG_BUS, OV511_OMNICE_PREDICTION_HORIZ_Y, 0x08 }, + { OV511_REG_BUS, OV511_OMNICE_PREDICTION_HORIZ_UV, 0x01 }, + { OV511_REG_BUS, OV511_OMNICE_PREDICTION_VERT_Y, 0x08 }, { OV511_REG_BUS, OV511_OMNICE_PREDICTION_VERT_UV, 0x01 }, { OV511_REG_BUS, OV511_OMNICE_QUANTIZATION_HORIZ_Y, 0x01 }, { OV511_REG_BUS, OV511_OMNICE_QUANTIZATION_HORIZ_UV, 0x01 }, @@ -2814,13 +2965,50 @@ ov511->frame[i].segsize = GET_SEGSIZE(ov511->frame[i].format); } - /* Initialize to DEFAULT_WIDTH, DEFAULT_HEIGHT, YUV4:2:0 */ + /* Test for 76xx */ - if(ov76xx_configure(ov511) < 0) { - err("failed to configure OV76xx"); - goto error; - } + if (ov511_reg_write(dev, OV511_REG_I2C_SLAVE_ID_WRITE, + OV7610_I2C_WRITE_ID) < 0) + goto error; + if (ov511_reg_write(dev, OV511_REG_I2C_SLAVE_ID_READ, + OV7610_I2C_READ_ID) < 0) + goto error; + + if (ov511_reset(dev, OV511_RESET_NOREGS) < 0) + goto error; + + if (ov511_i2c_write(dev, 0x12, 0x80) < 0) { + /* Test for 6xx0 */ + + if (ov511_reg_write(dev, OV511_REG_I2C_SLAVE_ID_WRITE, + OV6xx0_I2C_WRITE_ID) < 0) + goto error; + + if (ov511_reg_write(dev, OV511_REG_I2C_SLAVE_ID_READ, + OV6xx0_I2C_READ_ID) < 0) + goto error; + + if (ov511_reset(dev, OV511_RESET_NOREGS) < 0) + goto error; + + if (ov511_i2c_write(dev, 0x12, 0x80) < 0) { + err("Can't determine sensor slave IDs"); + goto error; + } + + if(ov6xx0_configure(ov511) < 0) { + err("failed to configure OV6xx0"); + goto error; + } + } else { + if(ov76xx_configure(ov511) < 0) { + err("failed to configure OV76xx"); + goto error; + } + } + + /* Initialize to DEFAULT_WIDTH, DEFAULT_HEIGHT, YUV4:2:0 */ if (ov511_mode_init_regs(ov511, DEFAULT_WIDTH, DEFAULT_HEIGHT, VIDEO_PALETTE_RGB24, 0) < 0) goto error; @@ -2857,10 +3045,12 @@ interface = &dev->actconfig->interface[ifnum].altsetting[0]; /* Is it an OV511/OV511+? */ - if (dev->descriptor.idVendor != 0x05a9) + if (dev->descriptor.idVendor != 0x05a9 + && dev->descriptor.idVendor != 0x0813) return NULL; if (dev->descriptor.idProduct != 0x0511 - && dev->descriptor.idProduct != 0xA511) + && dev->descriptor.idProduct != 0xA511 + && dev->descriptor.idProduct != 0x0002) return NULL; /* Checking vendor/product should be enough, but what the hell */ @@ -2891,6 +3081,15 @@ info("USB OV511+ camera found"); ov511->bridge = BRG_OV511PLUS; break; + case 0x0002: + if (dev->descriptor.idVendor != 0x0813); + goto error; + info("Intel Play Me2Cam (OV511+) found"); + ov511->bridge = BRG_OV511PLUS; + break; + default: + err("Unknown product ID"); + goto error; } ov511->customid = ov511_reg_read(dev, OV511_REG_SYSTEM_CUSTOM_ID); diff -Naur linux-2.4.0-test5-pre4-orig/drivers/usb/ov511.h linux/drivers/usb/ov511.h --- linux-2.4.0-test5-pre4-orig/drivers/usb/ov511.h Fri Jul 14 03:54:14 2000 +++ linux/drivers/usb/ov511.h Sun Jul 23 05:10:39 2000 @@ -183,6 +183,9 @@ // FIXME - these can vary between specific models #define OV7610_I2C_WRITE_ID 0x42 #define OV7610_I2C_READ_ID 0x43 +#define OV6xx0_I2C_WRITE_ID 0xC0 +#define OV6xx0_I2C_READ_ID 0xC1 + #define OV511_I2C_CLOCK_PRESCALER 0x03 /* Prototypes */ @@ -203,6 +206,7 @@ SEN_OV7610, SEN_OV7620, SEN_OV7620AE, + SEN_OV6620, }; enum { @@ -284,6 +288,10 @@ int customid; int desc; unsigned char iface; + + /* Determined by sensor type */ + int maxwidth; + int maxheight; int brightness; int colour;