diff options
Diffstat (limited to 'drivers/media/video/usbvideo')
-rw-r--r-- | drivers/media/video/usbvideo/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/video/usbvideo/ibmcam.c | 3932 | ||||
-rw-r--r-- | drivers/media/video/usbvideo/konicawc.c | 978 | ||||
-rw-r--r-- | drivers/media/video/usbvideo/ultracam.c | 679 | ||||
-rw-r--r-- | drivers/media/video/usbvideo/usbvideo.c | 2190 | ||||
-rw-r--r-- | drivers/media/video/usbvideo/usbvideo.h | 394 | ||||
-rw-r--r-- | drivers/media/video/usbvideo/vicam.c | 1411 |
7 files changed, 9588 insertions, 0 deletions
diff --git a/drivers/media/video/usbvideo/Makefile b/drivers/media/video/usbvideo/Makefile new file mode 100644 index 000000000000..ed410a5ee8c9 --- /dev/null +++ b/drivers/media/video/usbvideo/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_USB_IBMCAM) += ibmcam.o usbvideo.o ultracam.o +obj-$(CONFIG_USB_KONICAWC) += konicawc.o usbvideo.o +obj-$(CONFIG_USB_VICAM) += vicam.o usbvideo.o + diff --git a/drivers/media/video/usbvideo/ibmcam.c b/drivers/media/video/usbvideo/ibmcam.c new file mode 100644 index 000000000000..a42c22294124 --- /dev/null +++ b/drivers/media/video/usbvideo/ibmcam.c @@ -0,0 +1,3932 @@ +/* + * USB IBM C-It Video Camera driver + * + * Supports Xirlink C-It Video Camera, IBM PC Camera, + * IBM NetCamera and Veo Stingray. + * + * This driver is based on earlier work of: + * + * (C) Copyright 1999 Johannes Erdfelt + * (C) Copyright 1999 Randy Dunlap + * + * 5/24/00 Removed optional (and unnecessary) locking of the driver while + * the device remains plugged in. Corrected race conditions in ibmcam_open + * and ibmcam_probe() routines using this as a guideline: + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/init.h> + +#include "usbvideo.h" + +#define IBMCAM_VENDOR_ID 0x0545 +#define IBMCAM_PRODUCT_ID 0x8080 +#define NETCAM_PRODUCT_ID 0x8002 /* IBM NetCamera, close to model 2 */ +#define VEO_800C_PRODUCT_ID 0x800C /* Veo Stingray, repackaged Model 2 */ +#define VEO_800D_PRODUCT_ID 0x800D /* Veo Stingray, repackaged Model 4 */ + +#define MAX_IBMCAM 4 /* How many devices we allow to connect */ +#define USES_IBMCAM_PUTPIXEL 0 /* 0=Fast/oops 1=Slow/secure */ + +/* Header signatures */ + +/* Model 1 header: 00 FF 00 xx */ +#define HDRSIG_MODEL1_128x96 0x06 /* U Y V Y ... */ +#define HDRSIG_MODEL1_176x144 0x0e /* U Y V Y ... */ +#define HDRSIG_MODEL1_352x288 0x00 /* V Y U Y ... */ + +#define IBMCAM_MODEL_1 1 /* XVP-501, 3 interfaces, rev. 0.02 */ +#define IBMCAM_MODEL_2 2 /* KSX-X9903, 2 interfaces, rev. 3.0a */ +#define IBMCAM_MODEL_3 3 /* KSX-X9902, 2 interfaces, rev. 3.01 */ +#define IBMCAM_MODEL_4 4 /* IBM NetCamera, 0545/8002/3.0a */ + +/* Video sizes supported */ +#define VIDEOSIZE_128x96 VIDEOSIZE(128, 96) +#define VIDEOSIZE_176x144 VIDEOSIZE(176,144) +#define VIDEOSIZE_352x288 VIDEOSIZE(352,288) +#define VIDEOSIZE_320x240 VIDEOSIZE(320,240) +#define VIDEOSIZE_352x240 VIDEOSIZE(352,240) +#define VIDEOSIZE_640x480 VIDEOSIZE(640,480) +#define VIDEOSIZE_160x120 VIDEOSIZE(160,120) + +/* Video sizes supported */ +enum { + SIZE_128x96 = 0, + SIZE_160x120, + SIZE_176x144, + SIZE_320x240, + SIZE_352x240, + SIZE_352x288, + SIZE_640x480, + /* Add/remove/rearrange items before this line */ + SIZE_LastItem +}; + +/* + * This structure lives in uvd->user field. + */ +typedef struct { + int initialized; /* Had we already sent init sequence? */ + int camera_model; /* What type of IBM camera we got? */ + int has_hdr; +} ibmcam_t; +#define IBMCAM_T(uvd) ((ibmcam_t *)((uvd)->user_data)) + +static struct usbvideo *cams; + +static int debug; + +static int flags; /* = FLAGS_DISPLAY_HINTS | FLAGS_OVERLAY_STATS; */ + +static const int min_canvasWidth = 8; +static const int min_canvasHeight = 4; + +static int lighting = 1; /* Medium */ + +#define SHARPNESS_MIN 0 +#define SHARPNESS_MAX 6 +static int sharpness = 4; /* Low noise, good details */ + +#define FRAMERATE_MIN 0 +#define FRAMERATE_MAX 6 +static int framerate = -1; + +static int size = SIZE_352x288; + +/* + * Here we define several initialization variables. They may + * be used to automatically set color, hue, brightness and + * contrast to desired values. This is particularly useful in + * case of webcams (which have no controls and no on-screen + * output) and also when a client V4L software is used that + * does not have some of those controls. In any case it's + * good to have startup values as options. + * + * These values are all in [0..255] range. This simplifies + * operation. Note that actual values of V4L variables may + * be scaled up (as much as << 8). User can see that only + * on overlay output, however, or through a V4L client. + */ +static int init_brightness = 128; +static int init_contrast = 192; +static int init_color = 128; +static int init_hue = 128; +static int hue_correction = 128; + +/* Settings for camera model 2 */ +static int init_model2_rg2 = -1; +static int init_model2_sat = -1; +static int init_model2_yb = -1; + +/* 01.01.08 - Added for RCA video in support -LO */ +/* Settings for camera model 3 */ +static int init_model3_input = 0; + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level: 0-9 (default=0)"); +module_param(flags, int, 0); +MODULE_PARM_DESC(flags, "Bitfield: 0=VIDIOCSYNC, 1=B/W, 2=show hints, 3=show stats, 4=test pattern, 5=separate frames, 6=clean frames"); +module_param(framerate, int, 0); +MODULE_PARM_DESC(framerate, "Framerate setting: 0=slowest, 6=fastest (default=2)"); +module_param(lighting, int, 0); +MODULE_PARM_DESC(lighting, "Photosensitivity: 0=bright, 1=medium (default), 2=low light"); +module_param(sharpness, int, 0); +MODULE_PARM_DESC(sharpness, "Model1 noise reduction: 0=smooth, 6=sharp (default=4)"); +module_param(size, int, 0); +MODULE_PARM_DESC(size, "Image size: 0=128x96 1=160x120 2=176x144 3=320x240 4=352x240 5=352x288 6=640x480 (default=5)"); +module_param(init_brightness, int, 0); +MODULE_PARM_DESC(init_brightness, "Brightness preconfiguration: 0-255 (default=128)"); +module_param(init_contrast, int, 0); +MODULE_PARM_DESC(init_contrast, "Contrast preconfiguration: 0-255 (default=192)"); +module_param(init_color, int, 0); +MODULE_PARM_DESC(init_color, "Color preconfiguration: 0-255 (default=128)"); +module_param(init_hue, int, 0); +MODULE_PARM_DESC(init_hue, "Hue preconfiguration: 0-255 (default=128)"); +module_param(hue_correction, int, 0); +MODULE_PARM_DESC(hue_correction, "YUV colorspace regulation: 0-255 (default=128)"); + +module_param(init_model2_rg2, int, 0); +MODULE_PARM_DESC(init_model2_rg2, "Model2 preconfiguration: 0-255 (default=47)"); +module_param(init_model2_sat, int, 0); +MODULE_PARM_DESC(init_model2_sat, "Model2 preconfiguration: 0-255 (default=52)"); +module_param(init_model2_yb, int, 0); +MODULE_PARM_DESC(init_model2_yb, "Model2 preconfiguration: 0-255 (default=160)"); + +/* 01.01.08 - Added for RCA video in support -LO */ +module_param(init_model3_input, int, 0); +MODULE_PARM_DESC(init_model3_input, "Model3 input: 0=CCD 1=RCA"); + +MODULE_AUTHOR ("Dmitri"); +MODULE_DESCRIPTION ("IBM/Xirlink C-it USB Camera Driver for Linux (c) 2000"); +MODULE_LICENSE("GPL"); + +/* Still mysterious i2c commands */ +static const unsigned short unknown_88 = 0x0088; +static const unsigned short unknown_89 = 0x0089; +static const unsigned short bright_3x[3] = { 0x0031, 0x0032, 0x0033 }; +static const unsigned short contrast_14 = 0x0014; +static const unsigned short light_27 = 0x0027; +static const unsigned short sharp_13 = 0x0013; + +/* i2c commands for Model 2 cameras */ +static const unsigned short mod2_brightness = 0x001a; /* $5b .. $ee; default=$5a */ +static const unsigned short mod2_set_framerate = 0x001c; /* 0 (fast).. $1F (slow) */ +static const unsigned short mod2_color_balance_rg2 = 0x001e; /* 0 (red) .. $7F (green) */ +static const unsigned short mod2_saturation = 0x0020; /* 0 (b/w) - $7F (full color) */ +static const unsigned short mod2_color_balance_yb = 0x0022; /* 0..$7F, $50 is about right */ +static const unsigned short mod2_hue = 0x0024; /* 0..$7F, $70 is about right */ +static const unsigned short mod2_sensitivity = 0x0028; /* 0 (min) .. $1F (max) */ + +struct struct_initData { + unsigned char req; + unsigned short value; + unsigned short index; +}; + +/* + * ibmcam_size_to_videosize() + * + * This procedure converts module option 'size' into the actual + * videosize_t that defines the image size in pixels. We need + * simplified 'size' because user wants a simple enumerated list + * of choices, not an infinite set of possibilities. + */ +static videosize_t ibmcam_size_to_videosize(int size) +{ + videosize_t vs = VIDEOSIZE_352x288; + RESTRICT_TO_RANGE(size, 0, (SIZE_LastItem-1)); + switch (size) { + case SIZE_128x96: + vs = VIDEOSIZE_128x96; + break; + case SIZE_160x120: + vs = VIDEOSIZE_160x120; + break; + case SIZE_176x144: + vs = VIDEOSIZE_176x144; + break; + case SIZE_320x240: + vs = VIDEOSIZE_320x240; + break; + case SIZE_352x240: + vs = VIDEOSIZE_352x240; + break; + case SIZE_352x288: + vs = VIDEOSIZE_352x288; + break; + case SIZE_640x480: + vs = VIDEOSIZE_640x480; + break; + default: + err("size=%d. is not valid", size); + break; + } + return vs; +} + +/* + * ibmcam_find_header() + * + * Locate one of supported header markers in the queue. + * Once found, remove all preceding bytes AND the marker (4 bytes) + * from the data pump queue. Whatever follows must be video lines. + * + * History: + * 1/21/00 Created. + */ +static enum ParseState ibmcam_find_header(struct uvd *uvd) /* FIXME: Add frame here */ +{ + struct usbvideo_frame *frame; + ibmcam_t *icam; + + if ((uvd->curframe) < 0 || (uvd->curframe >= USBVIDEO_NUMFRAMES)) { + err("ibmcam_find_header: Illegal frame %d.", uvd->curframe); + return scan_EndParse; + } + icam = IBMCAM_T(uvd); + assert(icam != NULL); + frame = &uvd->frame[uvd->curframe]; + icam->has_hdr = 0; + switch (icam->camera_model) { + case IBMCAM_MODEL_1: + { + const int marker_len = 4; + while (RingQueue_GetLength(&uvd->dp) >= marker_len) { + if ((RING_QUEUE_PEEK(&uvd->dp, 0) == 0x00) && + (RING_QUEUE_PEEK(&uvd->dp, 1) == 0xFF) && + (RING_QUEUE_PEEK(&uvd->dp, 2) == 0x00)) + { +#if 0 /* This code helps to detect new frame markers */ + info("Header sig: 00 FF 00 %02X", RING_QUEUE_PEEK(&uvd->dp, 3)); +#endif + frame->header = RING_QUEUE_PEEK(&uvd->dp, 3); + if ((frame->header == HDRSIG_MODEL1_128x96) || + (frame->header == HDRSIG_MODEL1_176x144) || + (frame->header == HDRSIG_MODEL1_352x288)) + { +#if 0 + info("Header found."); +#endif + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, marker_len); + icam->has_hdr = 1; + break; + } + } + /* If we are still here then this doesn't look like a header */ + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, 1); + } + break; + } + case IBMCAM_MODEL_2: +case IBMCAM_MODEL_4: + { + int marker_len = 0; + switch (uvd->videosize) { + case VIDEOSIZE_176x144: + marker_len = 10; + break; + default: + marker_len = 2; + break; + } + while (RingQueue_GetLength(&uvd->dp) >= marker_len) { + if ((RING_QUEUE_PEEK(&uvd->dp, 0) == 0x00) && + (RING_QUEUE_PEEK(&uvd->dp, 1) == 0xFF)) + { +#if 0 + info("Header found."); +#endif + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, marker_len); + icam->has_hdr = 1; + frame->header = HDRSIG_MODEL1_176x144; + break; + } + /* If we are still here then this doesn't look like a header */ + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, 1); + } + break; + } + case IBMCAM_MODEL_3: + { /* + * Headers: (one precedes every frame). nc=no compression, + * bq=best quality bf=best frame rate. + * + * 176x144: 00 FF 02 { 0A=nc CA=bq EA=bf } + * 320x240: 00 FF 02 { 08=nc 28=bq 68=bf } + * 640x480: 00 FF 03 { 08=nc 28=bq 68=bf } + * + * Bytes '00 FF' seem to indicate header. Other two bytes + * encode the frame type. This is a set of bit fields that + * encode image size, compression type etc. These fields + * do NOT contain frame number because all frames carry + * the same header. + */ + const int marker_len = 4; + while (RingQueue_GetLength(&uvd->dp) >= marker_len) { + if ((RING_QUEUE_PEEK(&uvd->dp, 0) == 0x00) && + (RING_QUEUE_PEEK(&uvd->dp, 1) == 0xFF) && + (RING_QUEUE_PEEK(&uvd->dp, 2) != 0xFF)) + { + /* + * Combine 2 bytes of frame type into one + * easy to use value + */ + unsigned long byte3, byte4; + + byte3 = RING_QUEUE_PEEK(&uvd->dp, 2); + byte4 = RING_QUEUE_PEEK(&uvd->dp, 3); + frame->header = (byte3 << 8) | byte4; +#if 0 + info("Header found."); +#endif + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, marker_len); + icam->has_hdr = 1; + break; + } + /* If we are still here then this doesn't look like a header */ + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, 1); + } + break; + } + default: + break; + } + if (!icam->has_hdr) { + if (uvd->debug > 2) + info("Skipping frame, no header"); + return scan_EndParse; + } + + /* Header found */ + icam->has_hdr = 1; + uvd->stats.header_count++; + frame->scanstate = ScanState_Lines; + frame->curline = 0; + + if (flags & FLAGS_FORCE_TESTPATTERN) { + usbvideo_TestPattern(uvd, 1, 1); + return scan_NextFrame; + } + return scan_Continue; +} + +/* + * ibmcam_parse_lines() + * + * Parse one line (interlaced) from the buffer, put + * decoded RGB value into the current frame buffer + * and add the written number of bytes (RGB) to + * the *pcopylen. + * + * History: + * 21-Jan-2000 Created. + * 12-Oct-2000 Reworked to reflect interlaced nature of the data. + */ +static enum ParseState ibmcam_parse_lines( + struct uvd *uvd, + struct usbvideo_frame *frame, + long *pcopylen) +{ + unsigned char *f; + ibmcam_t *icam; + unsigned int len, scanLength, scanHeight, order_uv, order_yc; + int v4l_linesize; /* V4L line offset */ + const int hue_corr = (uvd->vpic.hue - 0x8000) >> 10; /* -32..+31 */ + const int hue2_corr = (hue_correction - 128) / 4; /* -32..+31 */ + const int ccm = 128; /* Color correction median - see below */ + int y, u, v, i, frame_done=0, color_corr; + static unsigned char lineBuffer[640*3]; + unsigned const char *chromaLine, *lumaLine; + + assert(uvd != NULL); + assert(frame != NULL); + icam = IBMCAM_T(uvd); + assert(icam != NULL); + color_corr = (uvd->vpic.colour - 0x8000) >> 8; /* -128..+127 = -ccm..+(ccm-1)*/ + RESTRICT_TO_RANGE(color_corr, -ccm, ccm+1); + + v4l_linesize = VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL; + + if (IBMCAM_T(uvd)->camera_model == IBMCAM_MODEL_4) { + /* Model 4 frame markers do not carry image size identification */ + switch (uvd->videosize) { + case VIDEOSIZE_128x96: + case VIDEOSIZE_160x120: + case VIDEOSIZE_176x144: + scanLength = VIDEOSIZE_X(uvd->videosize); + scanHeight = VIDEOSIZE_Y(uvd->videosize); + break; + default: + err("ibmcam_parse_lines: Wrong mode."); + return scan_Out; + } + order_yc = 1; /* order_yc: true=Yc false=cY ('c'=either U or V) */ + order_uv = 1; /* Always true in this algorithm */ + } else { + switch (frame->header) { + case HDRSIG_MODEL1_128x96: + scanLength = 128; + scanHeight = 96; + order_uv = 1; /* U Y V Y ... */ + break; + case HDRSIG_MODEL1_176x144: + scanLength = 176; + scanHeight = 144; + order_uv = 1; /* U Y V Y ... */ + break; + case HDRSIG_MODEL1_352x288: + scanLength = 352; + scanHeight = 288; + order_uv = 0; /* Y V Y V ... */ + break; + default: + err("Unknown header signature 00 FF 00 %02lX", frame->header); + return scan_NextFrame; + } + /* order_yc: true=Yc false=cY ('c'=either U or V) */ + order_yc = (IBMCAM_T(uvd)->camera_model == IBMCAM_MODEL_2); + } + + len = scanLength * 3; + assert(len <= sizeof(lineBuffer)); + + /* + * Lines are organized this way: + * + * I420: + * ~~~~ + * <scanLength-> + * ___________________________________ + * |-----Y-----|---UVUVUV...UVUV-----| \ + * |-----------+---------------------| \ + * |<-- 176 -->|<------ 176*2 ------>| Total 72. lines (interlaced) + * |... ... | ... | / + * |<-- 352 -->|<------ 352*2 ------>| Total 144. lines (interlaced) + * |___________|_____________________| / + * \ \ + * lumaLine chromaLine + */ + + /* Make sure there's enough data for the entire line */ + if (RingQueue_GetLength(&uvd->dp) < len) + return scan_Out; + + /* Suck one line out of the ring queue */ + RingQueue_Dequeue(&uvd->dp, lineBuffer, len); + + /* + * Make sure that our writing into output buffer + * will not exceed the buffer. Mind that we may write + * not into current output scanline but in several after + * it as well (if we enlarge image vertically.) + */ + if ((frame->curline + 2) >= VIDEOSIZE_Y(frame->request)) + return scan_NextFrame; + + /* + * Now we are sure that entire line (representing all 'scanLength' + * pixels from the camera) is available in the buffer. We + * start copying the line left-aligned to the V4L buffer. + * If the camera line is shorter then we should pad the V4L + * buffer with something (black) to complete the line. + */ + assert(frame->data != NULL); + f = frame->data + (v4l_linesize * frame->curline); + + /* + * To obtain chrominance data from the 'chromaLine' use this: + * v = chromaLine[0]; // 0-1:[0], 2-3:[4], 4-5:[8]... + * u = chromaLine[2]; // 0-1:[2], 2-3:[6], 4-5:[10]... + * + * Indices must be calculated this way: + * v_index = (i >> 1) << 2; + * u_index = (i >> 1) << 2 + 2; + * + * where 'i' is the column number [0..VIDEOSIZE_X(frame->request)-1] + */ + lumaLine = lineBuffer; + chromaLine = lineBuffer + scanLength; + for (i = 0; i < VIDEOSIZE_X(frame->request); i++) + { + unsigned char rv, gv, bv; /* RGB components */ + + /* Check for various visual debugging hints (colorized pixels) */ + if ((flags & FLAGS_DISPLAY_HINTS) && (icam->has_hdr)) { + /* + * This is bad and should not happen. This means that + * we somehow overshoot the line and encountered new + * frame! Obviously our camera/V4L frame size is out + * of whack. This cyan dot will help you to figure + * out where exactly the new frame arrived. + */ + if (icam->has_hdr == 1) { + bv = 0; /* Yellow marker */ + gv = 0xFF; + rv = 0xFF; + } else { + bv = 0xFF; /* Cyan marker */ + gv = 0xFF; + rv = 0; + } + icam->has_hdr = 0; + goto make_pixel; + } + + /* + * Check if we are still in range. We may be out of range if our + * V4L canvas is wider or taller than the camera "native" image. + * Then we quickly fill the remainder of the line with zeros to + * make black color and quit the horizontal scanning loop. + */ + if (((frame->curline + 2) >= scanHeight) || (i >= scanLength)) { + const int j = i * V4L_BYTES_PER_PIXEL; +#if USES_IBMCAM_PUTPIXEL + /* Refresh 'f' because we don't use it much with PUTPIXEL */ + f = frame->data + (v4l_linesize * frame->curline) + j; +#endif + memset(f, 0, v4l_linesize - j); + break; + } + + y = lumaLine[i]; + if (flags & FLAGS_MONOCHROME) /* Use monochrome for debugging */ + rv = gv = bv = y; + else { + int off_0, off_2; + + off_0 = (i >> 1) << 2; + off_2 = off_0 + 2; + + if (order_yc) { + off_0++; + off_2++; + } + if (!order_uv) { + off_0 += 2; + off_2 -= 2; + } + u = chromaLine[off_0] + hue_corr; + v = chromaLine[off_2] + hue2_corr; + + /* Apply color correction */ + if (color_corr != 0) { + /* Magnify up to 2 times, reduce down to zero saturation */ + u = 128 + ((ccm + color_corr) * (u - 128)) / ccm; + v = 128 + ((ccm + color_corr) * (v - 128)) / ccm; + } + YUV_TO_RGB_BY_THE_BOOK(y, u, v, rv, gv, bv); + } + + make_pixel: + /* + * The purpose of creating the pixel here, in one, + * dedicated place is that we may need to make the + * pixel wider and taller than it actually is. This + * may be used if camera generates small frames for + * sake of frame rate (or any other reason.) + * + * The output data consists of B, G, R bytes + * (in this order). + */ +#if USES_IBMCAM_PUTPIXEL + RGB24_PUTPIXEL(frame, i, frame->curline, rv, gv, bv); +#else + *f++ = bv; + *f++ = gv; + *f++ = rv; +#endif + /* + * Typically we do not decide within a legitimate frame + * that we want to end the frame. However debugging code + * may detect marker of new frame within the data. Then + * this condition activates. The 'data' pointer is already + * pointing at the new marker, so we'd better leave it as is. + */ + if (frame_done) + break; /* End scanning of lines */ + } + /* + * Account for number of bytes that we wrote into output V4L frame. + * We do it here, after we are done with the scanline, because we + * may fill more than one output scanline if we do vertical + * enlargement. + */ + frame->curline += 2; + if (pcopylen != NULL) + *pcopylen += 2 * v4l_linesize; + frame->deinterlace = Deinterlace_FillOddLines; + + if (frame_done || (frame->curline >= VIDEOSIZE_Y(frame->request))) + return scan_NextFrame; + else + return scan_Continue; +} + +/* + * ibmcam_model2_320x240_parse_lines() + * + * This procedure deals with a weird RGB format that is produced by IBM + * camera model 2 in modes 320x240 and above; 'x' below is 159 or 175, + * depending on horizontal size of the picture: + * + * <--- 160 or 176 pairs of RA,RB bytes -----> + * *-----------------------------------------* \ + * | RA0 | RB0 | RA1 | RB1 | ... | RAx | RBx | \ This is pair of horizontal lines, + * |-----+-----+-----+-----+ ... +-----+-----| *- or one interlaced line, total + * | B0 | G0 | B1 | G1 | ... | Bx | Gx | / 120 or 144 such pairs which yield + * |=====+=====+=====+=====+ ... +=====+=====| / 240 or 288 lines after deinterlacing. + * + * Each group of FOUR bytes (RAi, RBi, Bi, Gi) where i=0..frame_width/2-1 + * defines ONE pixel. Therefore this format yields 176x144 "decoded" + * resolution at best. I do not know why camera sends such format - the + * previous model (1) just used interlaced I420 and everyone was happy. + * + * I do not know what is the difference between RAi and RBi bytes. Both + * seemingly represent R component, but slightly vary in value (so that + * the picture looks a bit colored if one or another is used). I use + * them both as R component in attempt to at least partially recover the + * lost resolution. + */ +static enum ParseState ibmcam_model2_320x240_parse_lines( + struct uvd *uvd, + struct usbvideo_frame *frame, + long *pcopylen) +{ + unsigned char *f, *la, *lb; + unsigned int len; + int v4l_linesize; /* V4L line offset */ + int i, j, frame_done=0, color_corr; + int scanLength, scanHeight; + static unsigned char lineBuffer[352*2]; + + switch (uvd->videosize) { + case VIDEOSIZE_320x240: + case VIDEOSIZE_352x240: + case VIDEOSIZE_352x288: + scanLength = VIDEOSIZE_X(uvd->videosize); + scanHeight = VIDEOSIZE_Y(uvd->videosize); + break; + default: + err("ibmcam_model2_320x240_parse_lines: Wrong mode."); + return scan_Out; + } + + color_corr = (uvd->vpic.colour) >> 8; /* 0..+255 */ + v4l_linesize = VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL; + + len = scanLength * 2; /* See explanation above */ + assert(len <= sizeof(lineBuffer)); + + /* Make sure there's enough data for the entire line */ + if (RingQueue_GetLength(&uvd->dp) < len) + return scan_Out; + + /* Suck one line out of the ring queue */ + RingQueue_Dequeue(&uvd->dp, lineBuffer, len); + + /* + * Make sure that our writing into output buffer + * will not exceed the buffer. Mind that we may write + * not into current output scanline but in several after + * it as well (if we enlarge image vertically.) + */ + if ((frame->curline + 2) >= VIDEOSIZE_Y(frame->request)) + return scan_NextFrame; + + la = lineBuffer; + lb = lineBuffer + scanLength; + + /* + * Now we are sure that entire line (representing all + * VIDEOSIZE_X(frame->request) + * pixels from the camera) is available in the scratch buffer. We + * start copying the line left-aligned to the V4L buffer (which + * might be larger - not smaller, hopefully). If the camera + * line is shorter then we should pad the V4L buffer with something + * (black in this case) to complete the line. + */ + f = frame->data + (v4l_linesize * frame->curline); + + /* Fill the 2-line strip */ + for (i = 0; i < VIDEOSIZE_X(frame->request); i++) { + int y, rv, gv, bv; /* RGB components */ + + j = i & (~1); + + /* Check for various visual debugging hints (colorized pixels) */ + if ((flags & FLAGS_DISPLAY_HINTS) && (IBMCAM_T(uvd)->has_hdr)) { + if (IBMCAM_T(uvd)->has_hdr == 1) { + bv = 0; /* Yellow marker */ + gv = 0xFF; + rv = 0xFF; + } else { + bv = 0xFF; /* Cyan marker */ + gv = 0xFF; + rv = 0; + } + IBMCAM_T(uvd)->has_hdr = 0; + goto make_pixel; + } + + /* + * Check if we are still in range. We may be out of range if our + * V4L canvas is wider or taller than the camera "native" image. + * Then we quickly fill the remainder of the line with zeros to + * make black color and quit the horizontal scanning loop. + */ + if (((frame->curline + 2) >= scanHeight) || (i >= scanLength)) { + const int j = i * V4L_BYTES_PER_PIXEL; +#if USES_IBMCAM_PUTPIXEL + /* Refresh 'f' because we don't use it much with PUTPIXEL */ + f = frame->data + (v4l_linesize * frame->curline) + j; +#endif + memset(f, 0, v4l_linesize - j); + break; + } + + /* + * Here I use RA and RB components, one per physical pixel. + * This causes fine vertical grid on the picture but may improve + * horizontal resolution. If you prefer replicating, use this: + * rv = la[j + 0]; ... or ... rv = la[j + 1]; + * then the pixel will be replicated. + */ + rv = la[i]; + gv = lb[j + 1]; + bv = lb[j + 0]; + + y = (rv + gv + bv) / 3; /* Brightness (badly calculated) */ + + if (flags & FLAGS_MONOCHROME) /* Use monochrome for debugging */ + rv = gv = bv = y; + else if (color_corr != 128) { + + /* Calculate difference between color and brightness */ + rv -= y; + gv -= y; + bv -= y; + + /* Scale differences */ + rv = (rv * color_corr) / 128; + gv = (gv * color_corr) / 128; + bv = (bv * color_corr) / 128; + + /* Reapply brightness */ + rv += y; + gv += y; + bv += y; + + /* Watch for overflows */ + RESTRICT_TO_RANGE(rv, 0, 255); + RESTRICT_TO_RANGE(gv, 0, 255); + RESTRICT_TO_RANGE(bv, 0, 255); + } + + make_pixel: + RGB24_PUTPIXEL(frame, i, frame->curline, rv, gv, bv); + } + /* + * Account for number of bytes that we wrote into output V4L frame. + * We do it here, after we are done with the scanline, because we + * may fill more than one output scanline if we do vertical + * enlargement. + */ + frame->curline += 2; + *pcopylen += v4l_linesize * 2; + frame->deinterlace = Deinterlace_FillOddLines; + + if (frame_done || (frame->curline >= VIDEOSIZE_Y(frame->request))) + return scan_NextFrame; + else + return scan_Continue; +} + +static enum ParseState ibmcam_model3_parse_lines( + struct uvd *uvd, + struct usbvideo_frame *frame, + long *pcopylen) +{ + unsigned char *data; + const unsigned char *color; + unsigned int len; + int v4l_linesize; /* V4L line offset */ + const int hue_corr = (uvd->vpic.hue - 0x8000) >> 10; /* -32..+31 */ + const int hue2_corr = (hue_correction - 128) / 4; /* -32..+31 */ + const int ccm = 128; /* Color correction median - see below */ + int i, u, v, rw, data_w=0, data_h=0, color_corr; + static unsigned char lineBuffer[640*3]; + + color_corr = (uvd->vpic.colour - 0x8000) >> 8; /* -128..+127 = -ccm..+(ccm-1)*/ + RESTRICT_TO_RANGE(color_corr, -ccm, ccm+1); + + v4l_linesize = VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL; + + /* The header tells us what sort of data is in this frame */ + switch (frame->header) { + /* + * Uncompressed modes (that are easy to decode). + */ + case 0x0308: + data_w = 640; + data_h = 480; + break; + case 0x0208: + data_w = 320; + data_h = 240; + break; + case 0x020A: + data_w = 160; + data_h = 120; + break; + /* + * Compressed modes (ViCE - that I don't know how to decode). + */ + case 0x0328: /* 640x480, best quality compression */ + case 0x0368: /* 640x480, best frame rate compression */ + case 0x0228: /* 320x240, best quality compression */ + case 0x0268: /* 320x240, best frame rate compression */ + case 0x02CA: /* 160x120, best quality compression */ + case 0x02EA: /* 160x120, best frame rate compression */ + /* Do nothing with this - not supported */ + err("Unsupported mode $%04lx", frame->header); + return scan_NextFrame; + default: + /* Catch unknown headers, may help in learning new headers */ + err("Strange frame->header=$%08lx", frame->header); + return scan_NextFrame; + } + + /* + * Make sure that our writing into output buffer + * will not exceed the buffer. Note that we may write + * not into current output scanline but in several after + * it as well (if we enlarge image vertically.) + */ + if ((frame->curline + 1) >= data_h) { + if (uvd->debug >= 3) + info("Reached line %d. (frame is done)", frame->curline); + return scan_NextFrame; + } + + /* Make sure there's enough data for the entire line */ + len = 3 * data_w; /* <y-data> <uv-data> */ + assert(len <= sizeof(lineBuffer)); + + /* Make sure there's enough data for the entire line */ + if (RingQueue_GetLength(&uvd->dp) < len) + return scan_Out; + + /* Suck one line out of the ring queue */ + RingQueue_Dequeue(&uvd->dp, lineBuffer, len); + + data = lineBuffer; + color = data + data_w; /* Point to where color planes begin */ + + /* Bottom-to-top scanning */ + rw = (int)VIDEOSIZE_Y(frame->request) - (int)(frame->curline) - 1; + RESTRICT_TO_RANGE(rw, 0, VIDEOSIZE_Y(frame->request)-1); + + for (i = 0; i < VIDEOSIZE_X(frame->request); i++) { + int y, rv, gv, bv; /* RGB components */ + + if (i < data_w) { + y = data[i]; /* Luminosity is the first line */ + + /* Apply static color correction */ + u = color[i*2] + hue_corr; + v = color[i*2 + 1] + hue2_corr; + + /* Apply color correction */ + if (color_corr != 0) { + /* Magnify up to 2 times, reduce down to zero saturation */ + u = 128 + ((ccm + color_corr) * (u - 128)) / ccm; + v = 128 + ((ccm + color_corr) * (v - 128)) / ccm; + } + } else + y = 0, u = v = 128; + + YUV_TO_RGB_BY_THE_BOOK(y, u, v, rv, gv, bv); + RGB24_PUTPIXEL(frame, i, rw, rv, gv, bv); /* Done by deinterlacing now */ + } + frame->deinterlace = Deinterlace_FillEvenLines; + + /* + * Account for number of bytes that we wrote into output V4L frame. + * We do it here, after we are done with the scanline, because we + * may fill more than one output scanline if we do vertical + * enlargement. + */ + frame->curline += 2; + *pcopylen += 2 * v4l_linesize; + + if (frame->curline >= VIDEOSIZE_Y(frame->request)) { + if (uvd->debug >= 3) { + info("All requested lines (%ld.) done.", + VIDEOSIZE_Y(frame->request)); + } + return scan_NextFrame; + } else + return scan_Continue; +} + +/* + * ibmcam_model4_128x96_parse_lines() + * + * This decoder is for one strange data format that is produced by Model 4 + * camera only in 128x96 mode. This is RGB format and here is its description. + * First of all, this is non-interlaced stream, meaning that all scan lines + * are present in the datastream. There are 96 consecutive blocks of data + * that describe all 96 lines of the image. Each block is 5*128 bytes long + * and carries R, G, B components. The format of the block is shown in the + * code below. First 128*2 bytes are interleaved R and G components. Then + * we have a gap (junk data) 64 bytes long. Then follow B and something + * else, also interleaved (this makes another 128*2 bytes). After that + * probably another 64 bytes of junk follow. + * + * History: + * 10-Feb-2001 Created. + */ +static enum ParseState ibmcam_model4_128x96_parse_lines( + struct uvd *uvd, + struct usbvideo_frame *frame, + long *pcopylen) +{ + const unsigned char *data_rv, *data_gv, *data_bv; + unsigned int len; + int i, v4l_linesize; /* V4L line offset */ + const int data_w=128, data_h=96; + static unsigned char lineBuffer[128*5]; + + v4l_linesize = VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL; + + /* + * Make sure that our writing into output buffer + * will not exceed the buffer. Note that we may write + * not into current output scanline but in several after + * it as well (if we enlarge image vertically.) + */ + if ((frame->curline + 1) >= data_h) { + if (uvd->debug >= 3) + info("Reached line %d. (frame is done)", frame->curline); + return scan_NextFrame; + } + + /* + * RGRGRG .... RGRG_____________B?B?B? ... B?B?____________ + * <---- 128*2 ---><---- 64 ---><--- 128*2 ---><--- 64 ---> + */ + + /* Make sure there's enough data for the entire line */ + len = 5 * data_w; + assert(len <= sizeof(lineBuffer)); + + /* Make sure there's enough data for the entire line */ + if (RingQueue_GetLength(&uvd->dp) < len) + return scan_Out; + + /* Suck one line out of the ring queue */ + RingQueue_Dequeue(&uvd->dp, lineBuffer, len); + + data_rv = lineBuffer; + data_gv = lineBuffer + 1; + data_bv = lineBuffer + data_w*2 + data_w/2; + for (i = 0; i < VIDEOSIZE_X(frame->request); i++) { + int rv, gv, bv; /* RGB components */ + if (i < data_w) { + const int j = i * 2; + gv = data_rv[j]; + rv = data_gv[j]; + bv = data_bv[j]; + if (flags & FLAGS_MONOCHROME) { + unsigned long y; + y = rv + gv + bv; + y /= 3; + if (y > 0xFF) + y = 0xFF; + rv = gv = bv = (unsigned char) y; + } + } else { + rv = gv = bv = 0; + } + RGB24_PUTPIXEL(frame, i, frame->curline, rv, gv, bv); + } + frame->deinterlace = Deinterlace_None; + frame->curline++; + *pcopylen += v4l_linesize; + + if (frame->curline >= VIDEOSIZE_Y(frame->request)) { + if (uvd->debug >= 3) { + info("All requested lines (%ld.) done.", + VIDEOSIZE_Y(frame->request)); + } + return scan_NextFrame; + } else + return scan_Continue; +} + +/* + * ibmcam_ProcessIsocData() + * + * Generic routine to parse the ring queue data. It employs either + * ibmcam_find_header() or ibmcam_parse_lines() to do most + * of work. + * + * History: + * 1/21/00 Created. + */ +static void ibmcam_ProcessIsocData(struct uvd *uvd, + struct usbvideo_frame *frame) +{ + enum ParseState newstate; + long copylen = 0; + int mod = IBMCAM_T(uvd)->camera_model; + + while (1) { + newstate = scan_Out; + if (RingQueue_GetLength(&uvd->dp) > 0) { + if (frame->scanstate == ScanState_Scanning) { + newstate = ibmcam_find_header(uvd); + } else if (frame->scanstate == ScanState_Lines) { + if ((mod == IBMCAM_MODEL_2) && + ((uvd->videosize == VIDEOSIZE_352x288) || + (uvd->videosize == VIDEOSIZE_320x240) || + (uvd->videosize == VIDEOSIZE_352x240))) + { + newstate = ibmcam_model2_320x240_parse_lines( + uvd, frame, ©len); + } else if (mod == IBMCAM_MODEL_4) { + /* + * Model 4 cameras (IBM NetCamera) use Model 2 decoder (RGB) + * for 320x240 and above; 160x120 and 176x144 uses Model 1 + * decoder (YUV), and 128x96 mode uses ??? + */ + if ((uvd->videosize == VIDEOSIZE_352x288) || + (uvd->videosize == VIDEOSIZE_320x240) || + (uvd->videosize == VIDEOSIZE_352x240)) + { + newstate = ibmcam_model2_320x240_parse_lines(uvd, frame, ©len); + } else if (uvd->videosize == VIDEOSIZE_128x96) { + newstate = ibmcam_model4_128x96_parse_lines(uvd, frame, ©len); + } else { + newstate = ibmcam_parse_lines(uvd, frame, ©len); + } + } else if (mod == IBMCAM_MODEL_3) { + newstate = ibmcam_model3_parse_lines(uvd, frame, ©len); + } else { + newstate = ibmcam_parse_lines(uvd, frame, ©len); + } + } + } + if (newstate == scan_Continue) + continue; + else if ((newstate == scan_NextFrame) || (newstate == scan_Out)) + break; + else + return; /* scan_EndParse */ + } + + if (newstate == scan_NextFrame) { + frame->frameState = FrameState_Done; + uvd->curframe = -1; + uvd->stats.frame_num++; + if ((mod == IBMCAM_MODEL_2) || (mod == IBMCAM_MODEL_4)) { + /* Need software contrast adjustment for those cameras */ + frame->flags |= USBVIDEO_FRAME_FLAG_SOFTWARE_CONTRAST; + } + } + + /* Update the frame's uncompressed length. */ + frame->seqRead_Length += copylen; + +#if 0 + { + static unsigned char j=0; + memset(frame->data, j++, uvd->max_frame_size); + frame->frameState = FrameState_Ready; + } +#endif +} + +/* + * ibmcam_veio() + * + * History: + * 1/27/00 Added check for dev == NULL; this happens if camera is unplugged. + */ +static int ibmcam_veio( + struct uvd *uvd, + unsigned char req, + unsigned short value, + unsigned short index) +{ + static const char proc[] = "ibmcam_veio"; + unsigned char cp[8] /* = { 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef } */; + int i; + + if (!CAMERA_IS_OPERATIONAL(uvd)) + return 0; + + if (req == 1) { + i = usb_control_msg( + uvd->dev, + usb_rcvctrlpipe(uvd->dev, 0), + req, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, + value, + index, + cp, + sizeof(cp), + 1000); +#if 0 + info("USB => %02x%02x%02x%02x%02x%02x%02x%02x " + "(req=$%02x val=$%04x ind=$%04x)", + cp[0],cp[1],cp[2],cp[3],cp[4],cp[5],cp[6],cp[7], + req, value, index); +#endif + } else { + i = usb_control_msg( + uvd->dev, + usb_sndctrlpipe(uvd->dev, 0), + req, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, + value, + index, + NULL, + 0, + 1000); + } + if (i < 0) { + err("%s: ERROR=%d. Camera stopped; Reconnect or reload driver.", + proc, i); + uvd->last_error = i; + } + return i; +} + +/* + * ibmcam_calculate_fps() + * + * This procedure roughly calculates the real frame rate based + * on FPS code (framerate=NNN option). Actual FPS differs + * slightly depending on lighting conditions, so that actual frame + * rate is determined by the camera. Since I don't know how to ask + * the camera what FPS is now I have to use the FPS code instead. + * + * The FPS code is in range [0..6], 0 is slowest, 6 is fastest. + * Corresponding real FPS should be in range [3..30] frames per second. + * The conversion formula is obvious: + * + * real_fps = 3 + (fps_code * 4.5) + * + * History: + * 1/18/00 Created. + */ +static int ibmcam_calculate_fps(struct uvd *uvd) +{ + return 3 + framerate*4 + framerate/2; +} + +/* + * ibmcam_send_FF_04_02() + * + * This procedure sends magic 3-command prefix to the camera. + * The purpose of this prefix is not known. + * + * History: + * 1/2/00 Created. + */ +static void ibmcam_send_FF_04_02(struct uvd *uvd) +{ + ibmcam_veio(uvd, 0, 0x00FF, 0x0127); + ibmcam_veio(uvd, 0, 0x0004, 0x0124); + ibmcam_veio(uvd, 0, 0x0002, 0x0124); +} + +static void ibmcam_send_00_04_06(struct uvd *uvd) +{ + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x0004, 0x0124); + ibmcam_veio(uvd, 0, 0x0006, 0x0124); +} + +static void ibmcam_send_x_00(struct uvd *uvd, unsigned short x) +{ + ibmcam_veio(uvd, 0, x, 0x0127); + ibmcam_veio(uvd, 0, 0x0000, 0x0124); +} + +static void ibmcam_send_x_00_05(struct uvd *uvd, unsigned short x) +{ + ibmcam_send_x_00(uvd, x); + ibmcam_veio(uvd, 0, 0x0005, 0x0124); +} + +static void ibmcam_send_x_00_05_02(struct uvd *uvd, unsigned short x) +{ + ibmcam_veio(uvd, 0, x, 0x0127); + ibmcam_veio(uvd, 0, 0x0000, 0x0124); + ibmcam_veio(uvd, 0, 0x0005, 0x0124); + ibmcam_veio(uvd, 0, 0x0002, 0x0124); +} + +static void ibmcam_send_x_01_00_05(struct uvd *uvd, unsigned short x) +{ + ibmcam_veio(uvd, 0, x, 0x0127); + ibmcam_veio(uvd, 0, 0x0001, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0124); + ibmcam_veio(uvd, 0, 0x0005, 0x0124); +} + +static void ibmcam_send_x_00_05_02_01(struct uvd *uvd, unsigned short x) +{ + ibmcam_veio(uvd, 0, x, 0x0127); + ibmcam_veio(uvd, 0, 0x0000, 0x0124); + ibmcam_veio(uvd, 0, 0x0005, 0x0124); + ibmcam_veio(uvd, 0, 0x0002, 0x0124); + ibmcam_veio(uvd, 0, 0x0001, 0x0124); +} + +static void ibmcam_send_x_00_05_02_08_01(struct uvd *uvd, unsigned short x) +{ + ibmcam_veio(uvd, 0, x, 0x0127); + ibmcam_veio(uvd, 0, 0x0000, 0x0124); + ibmcam_veio(uvd, 0, 0x0005, 0x0124); + ibmcam_veio(uvd, 0, 0x0002, 0x0124); + ibmcam_veio(uvd, 0, 0x0008, 0x0124); + ibmcam_veio(uvd, 0, 0x0001, 0x0124); +} + +static void ibmcam_Packet_Format1(struct uvd *uvd, unsigned char fkey, unsigned char val) +{ + ibmcam_send_x_01_00_05(uvd, unknown_88); + ibmcam_send_x_00_05(uvd, fkey); + ibmcam_send_x_00_05_02_08_01(uvd, val); + ibmcam_send_x_00_05(uvd, unknown_88); + ibmcam_send_x_00_05_02_01(uvd, fkey); + ibmcam_send_x_00_05(uvd, unknown_89); + ibmcam_send_x_00(uvd, fkey); + ibmcam_send_00_04_06(uvd); + ibmcam_veio(uvd, 1, 0x0000, 0x0126); + ibmcam_send_FF_04_02(uvd); +} + +static void ibmcam_PacketFormat2(struct uvd *uvd, unsigned char fkey, unsigned char val) +{ + ibmcam_send_x_01_00_05 (uvd, unknown_88); + ibmcam_send_x_00_05 (uvd, fkey); + ibmcam_send_x_00_05_02 (uvd, val); +} + +static void ibmcam_model2_Packet2(struct uvd *uvd) +{ + ibmcam_veio(uvd, 0, 0x00ff, 0x012d); + ibmcam_veio(uvd, 0, 0xfea3, 0x0124); +} + +static void ibmcam_model2_Packet1(struct uvd *uvd, unsigned short v1, unsigned short v2) +{ + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x00ff, 0x012e); + ibmcam_veio(uvd, 0, v1, 0x012f); + ibmcam_veio(uvd, 0, 0x00ff, 0x0130); + ibmcam_veio(uvd, 0, 0xc719, 0x0124); + ibmcam_veio(uvd, 0, v2, 0x0127); + + ibmcam_model2_Packet2(uvd); +} + +/* + * ibmcam_model3_Packet1() + * + * 00_0078_012d + * 00_0097_012f + * 00_d141_0124 + * 00_0096_0127 + * 00_fea8_0124 +*/ +static void ibmcam_model3_Packet1(struct uvd *uvd, unsigned short v1, unsigned short v2) +{ + ibmcam_veio(uvd, 0, 0x0078, 0x012d); + ibmcam_veio(uvd, 0, v1, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, v2, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); +} + +static void ibmcam_model4_BrightnessPacket(struct uvd *uvd, int i) +{ + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0026, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, i, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0038, 0x012d); + ibmcam_veio(uvd, 0, 0x0004, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); +} + +/* + * ibmcam_adjust_contrast() + * + * The contrast value changes from 0 (high contrast) to 15 (low contrast). + * This is in reverse to usual order of things (such as TV controls), so + * we reverse it again here. + * + * TODO: we probably don't need to send the setup 5 times... + * + * History: + * 1/2/00 Created. + */ +static void ibmcam_adjust_contrast(struct uvd *uvd) +{ + unsigned char a_contrast = uvd->vpic.contrast >> 12; + unsigned char new_contrast; + + if (a_contrast >= 16) + a_contrast = 15; + new_contrast = 15 - a_contrast; + if (new_contrast == uvd->vpic_old.contrast) + return; + uvd->vpic_old.contrast = new_contrast; + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + { + const int ntries = 5; + int i; + for (i=0; i < ntries; i++) { + ibmcam_Packet_Format1(uvd, contrast_14, new_contrast); + ibmcam_send_FF_04_02(uvd); + } + break; + } + case IBMCAM_MODEL_2: + case IBMCAM_MODEL_4: + /* Models 2, 4 do not have this control; implemented in software. */ + break; + case IBMCAM_MODEL_3: + { /* Preset hardware values */ + static const struct { + unsigned short cv1; + unsigned short cv2; + unsigned short cv3; + } cv[7] = { + { 0x05, 0x05, 0x0f }, /* Minimum */ + { 0x04, 0x04, 0x16 }, + { 0x02, 0x03, 0x16 }, + { 0x02, 0x08, 0x16 }, + { 0x01, 0x0c, 0x16 }, + { 0x01, 0x0e, 0x16 }, + { 0x01, 0x10, 0x16 } /* Maximum */ + }; + int i = a_contrast / 2; + RESTRICT_TO_RANGE(i, 0, 6); + ibmcam_veio(uvd, 0, 0x0000, 0x010c); /* Stop */ + ibmcam_model3_Packet1(uvd, 0x0067, cv[i].cv1); + ibmcam_model3_Packet1(uvd, 0x005b, cv[i].cv2); + ibmcam_model3_Packet1(uvd, 0x005c, cv[i].cv3); + ibmcam_veio(uvd, 0, 0x0001, 0x0114); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); /* Go! */ + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); + break; + } + default: + break; + } +} + +/* + * ibmcam_change_lighting_conditions() + * + * Camera model 1: + * We have 3 levels of lighting conditions: 0=Bright, 1=Medium, 2=Low. + * + * Camera model 2: + * We have 16 levels of lighting, 0 for bright light and up to 15 for + * low light. But values above 5 or so are useless because camera is + * not really capable to produce anything worth viewing at such light. + * This setting may be altered only in certain camera state. + * + * Low lighting forces slower FPS. Lighting is set as a module parameter. + * + * History: + * 1/5/00 Created. + * 2/20/00 Added support for Model 2 cameras. + */ +static void ibmcam_change_lighting_conditions(struct uvd *uvd) +{ + static const char proc[] = "ibmcam_change_lighting_conditions"; + + if (debug > 0) + info("%s: Set lighting to %hu.", proc, lighting); + + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + { + const int ntries = 5; + int i; + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, light_27, (unsigned short) lighting); + break; + } + case IBMCAM_MODEL_2: +#if 0 + /* + * This command apparently requires camera to be stopped. My + * experiments showed that it -is- possible to alter the lighting + * conditions setting "on the fly", but why bother? This setting does + * not work reliably in all cases, so I decided simply to leave the + * setting where Xirlink put it - in the camera setup phase. This code + * is commented out because it does not work at -any- moment, so its + * presence makes no sense. You may use it for experiments. + */ + ibmcam_veio(uvd, 0, 0x0000, 0x010c); /* Stop camera */ + ibmcam_model2_Packet1(uvd, mod2_sensitivity, lighting); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); /* Start camera */ +#endif + break; + case IBMCAM_MODEL_3: + case IBMCAM_MODEL_4: + default: + break; + } +} + +/* + * ibmcam_set_sharpness() + * + * Cameras model 1 have internal smoothing feature. It is controlled by value in + * range [0..6], where 0 is most smooth and 6 is most sharp (raw image, I guess). + * Recommended value is 4. Cameras model 2 do not have this feature at all. + */ +static void ibmcam_set_sharpness(struct uvd *uvd) +{ + static const char proc[] = "ibmcam_set_sharpness"; + + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + { + static const unsigned short sa[] = { 0x11, 0x13, 0x16, 0x18, 0x1a, 0x8, 0x0a }; + unsigned short i, sv; + + RESTRICT_TO_RANGE(sharpness, SHARPNESS_MIN, SHARPNESS_MAX); + if (debug > 0) + info("%s: Set sharpness to %hu.", proc, sharpness); + + sv = sa[sharpness - SHARPNESS_MIN]; + for (i=0; i < 2; i++) { + ibmcam_send_x_01_00_05 (uvd, unknown_88); + ibmcam_send_x_00_05 (uvd, sharp_13); + ibmcam_send_x_00_05_02 (uvd, sv); + } + break; + } + case IBMCAM_MODEL_2: + case IBMCAM_MODEL_4: + /* Models 2, 4 do not have this control */ + break; + case IBMCAM_MODEL_3: + { /* + * "Use a table of magic numbers. + * This setting doesn't really change much. + * But that's how Windows does it." + */ + static const struct { + unsigned short sv1; + unsigned short sv2; + unsigned short sv3; + unsigned short sv4; + } sv[7] = { + { 0x00, 0x00, 0x05, 0x14 }, /* Smoothest */ + { 0x01, 0x04, 0x05, 0x14 }, + { 0x02, 0x04, 0x05, 0x14 }, + { 0x03, 0x04, 0x05, 0x14 }, + { 0x03, 0x05, 0x05, 0x14 }, + { 0x03, 0x06, 0x05, 0x14 }, + { 0x03, 0x07, 0x05, 0x14 } /* Sharpest */ + }; + RESTRICT_TO_RANGE(sharpness, SHARPNESS_MIN, SHARPNESS_MAX); + RESTRICT_TO_RANGE(sharpness, 0, 6); + ibmcam_veio(uvd, 0, 0x0000, 0x010c); /* Stop */ + ibmcam_model3_Packet1(uvd, 0x0060, sv[sharpness].sv1); + ibmcam_model3_Packet1(uvd, 0x0061, sv[sharpness].sv2); + ibmcam_model3_Packet1(uvd, 0x0062, sv[sharpness].sv3); + ibmcam_model3_Packet1(uvd, 0x0063, sv[sharpness].sv4); + ibmcam_veio(uvd, 0, 0x0001, 0x0114); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); /* Go! */ + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); + ibmcam_veio(uvd, 0, 0x0001, 0x0113); + break; + } + default: + break; + } +} + +/* + * ibmcam_set_brightness() + * + * This procedure changes brightness of the picture. + */ +static void ibmcam_set_brightness(struct uvd *uvd) +{ + static const char proc[] = "ibmcam_set_brightness"; + static const unsigned short n = 1; + + if (debug > 0) + info("%s: Set brightness to %hu.", proc, uvd->vpic.brightness); + + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + { + unsigned short i, j, bv[3]; + bv[0] = bv[1] = bv[2] = uvd->vpic.brightness >> 10; + if (bv[0] == (uvd->vpic_old.brightness >> 10)) + return; + uvd->vpic_old.brightness = bv[0]; + for (j=0; j < 3; j++) + for (i=0; i < n; i++) + ibmcam_Packet_Format1(uvd, bright_3x[j], bv[j]); + break; + } + case IBMCAM_MODEL_2: + { + unsigned short i, j; + i = uvd->vpic.brightness >> 12; /* 0 .. 15 */ + j = 0x60 + i * ((0xee - 0x60) / 16); /* 0x60 .. 0xee or so */ + if (uvd->vpic_old.brightness == j) + break; + uvd->vpic_old.brightness = j; + ibmcam_model2_Packet1(uvd, mod2_brightness, j); + break; + } + case IBMCAM_MODEL_3: + { + /* Model 3: Brightness range 'i' in [0x0C..0x3F] */ + unsigned short i = + 0x0C + (uvd->vpic.brightness / (0xFFFF / (0x3F - 0x0C + 1))); + RESTRICT_TO_RANGE(i, 0x0C, 0x3F); + if (uvd->vpic_old.brightness == i) + break; + uvd->vpic_old.brightness = i; + ibmcam_veio(uvd, 0, 0x0000, 0x010c); /* Stop */ + ibmcam_model3_Packet1(uvd, 0x0036, i); + ibmcam_veio(uvd, 0, 0x0001, 0x0114); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); /* Go! */ + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); + ibmcam_veio(uvd, 0, 0x0001, 0x0113); + break; + } + case IBMCAM_MODEL_4: + { + /* Model 4: Brightness range 'i' in [0x04..0xb4] */ + unsigned short i = 0x04 + (uvd->vpic.brightness / (0xFFFF / (0xb4 - 0x04 + 1))); + RESTRICT_TO_RANGE(i, 0x04, 0xb4); + if (uvd->vpic_old.brightness == i) + break; + uvd->vpic_old.brightness = i; + ibmcam_model4_BrightnessPacket(uvd, i); + break; + } + default: + break; + } +} + +static void ibmcam_set_hue(struct uvd *uvd) +{ + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_2: + { + unsigned short hue = uvd->vpic.hue >> 9; /* 0 .. 7F */ + if (uvd->vpic_old.hue == hue) + return; + uvd->vpic_old.hue = hue; + ibmcam_model2_Packet1(uvd, mod2_hue, hue); + /* ibmcam_model2_Packet1(uvd, mod2_saturation, sat); */ + break; + } + case IBMCAM_MODEL_3: + { +#if 0 /* This seems not to work. No problem, will fix programmatically */ + unsigned short hue = 0x05 + (uvd->vpic.hue / (0xFFFF / (0x37 - 0x05 + 1))); + RESTRICT_TO_RANGE(hue, 0x05, 0x37); + if (uvd->vpic_old.hue == hue) + return; + uvd->vpic_old.hue = hue; + ibmcam_veio(uvd, 0, 0x0000, 0x010c); /* Stop */ + ibmcam_model3_Packet1(uvd, 0x007e, hue); + ibmcam_veio(uvd, 0, 0x0001, 0x0114); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); /* Go! */ + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); + ibmcam_veio(uvd, 0, 0x0001, 0x0113); +#endif + break; + } + case IBMCAM_MODEL_4: + { + unsigned short r_gain, g_gain, b_gain, hue; + + /* + * I am not sure r/g/b_gain variables exactly control gain + * of those channels. Most likely they subtly change some + * very internal image processing settings in the camera. + * In any case, here is what they do, and feel free to tweak: + * + * r_gain: seriously affects red gain + * g_gain: seriously affects green gain + * b_gain: seriously affects blue gain + * hue: changes average color from violet (0) to red (0xFF) + * + * These settings are preset for a decent white balance in + * 320x240, 352x288 modes. Low-res modes exhibit higher contrast + * and therefore may need different values here. + */ + hue = 20 + (uvd->vpic.hue >> 9); + switch (uvd->videosize) { + case VIDEOSIZE_128x96: + r_gain = 90; + g_gain = 166; + b_gain = 175; + break; + case VIDEOSIZE_160x120: + r_gain = 70; + g_gain = 166; + b_gain = 185; + break; + case VIDEOSIZE_176x144: + r_gain = 160; + g_gain = 175; + b_gain = 185; + break; + default: + r_gain = 120; + g_gain = 166; + b_gain = 175; + break; + } + RESTRICT_TO_RANGE(hue, 1, 0x7f); + + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x001e, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, g_gain, 0x0127); /* Green gain */ + ibmcam_veio(uvd, 0, r_gain, 0x012e); /* Red gain */ + ibmcam_veio(uvd, 0, b_gain, 0x0130); /* Blue gain */ + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, hue, 0x012d); /* Hue */ + ibmcam_veio(uvd, 0, 0xf545, 0x0124); + break; + } + default: + break; + } +} + +/* + * ibmcam_adjust_picture() + * + * This procedure gets called from V4L interface to update picture settings. + * Here we change brightness and contrast. + */ +static void ibmcam_adjust_picture(struct uvd *uvd) +{ + ibmcam_adjust_contrast(uvd); + ibmcam_set_brightness(uvd); + ibmcam_set_hue(uvd); +} + +static int ibmcam_model1_setup(struct uvd *uvd) +{ + const int ntries = 5; + int i; + + ibmcam_veio(uvd, 1, 0x00, 0x0128); + ibmcam_veio(uvd, 1, 0x00, 0x0100); + ibmcam_veio(uvd, 0, 0x01, 0x0100); /* LED On */ + ibmcam_veio(uvd, 1, 0x00, 0x0100); + ibmcam_veio(uvd, 0, 0x81, 0x0100); /* LED Off */ + ibmcam_veio(uvd, 1, 0x00, 0x0100); + ibmcam_veio(uvd, 0, 0x01, 0x0100); /* LED On */ + ibmcam_veio(uvd, 0, 0x01, 0x0108); + + ibmcam_veio(uvd, 0, 0x03, 0x0112); + ibmcam_veio(uvd, 1, 0x00, 0x0115); + ibmcam_veio(uvd, 0, 0x06, 0x0115); + ibmcam_veio(uvd, 1, 0x00, 0x0116); + ibmcam_veio(uvd, 0, 0x44, 0x0116); + ibmcam_veio(uvd, 1, 0x00, 0x0116); + ibmcam_veio(uvd, 0, 0x40, 0x0116); + ibmcam_veio(uvd, 1, 0x00, 0x0115); + ibmcam_veio(uvd, 0, 0x0e, 0x0115); + ibmcam_veio(uvd, 0, 0x19, 0x012c); + + ibmcam_Packet_Format1(uvd, 0x00, 0x1e); + ibmcam_Packet_Format1(uvd, 0x39, 0x0d); + ibmcam_Packet_Format1(uvd, 0x39, 0x09); + ibmcam_Packet_Format1(uvd, 0x3b, 0x00); + ibmcam_Packet_Format1(uvd, 0x28, 0x22); + ibmcam_Packet_Format1(uvd, light_27, 0); + ibmcam_Packet_Format1(uvd, 0x2b, 0x1f); + ibmcam_Packet_Format1(uvd, 0x39, 0x08); + + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x2c, 0x00); + + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x30, 0x14); + + ibmcam_PacketFormat2(uvd, 0x39, 0x02); + ibmcam_PacketFormat2(uvd, 0x01, 0xe1); + ibmcam_PacketFormat2(uvd, 0x02, 0xcd); + ibmcam_PacketFormat2(uvd, 0x03, 0xcd); + ibmcam_PacketFormat2(uvd, 0x04, 0xfa); + ibmcam_PacketFormat2(uvd, 0x3f, 0xff); + ibmcam_PacketFormat2(uvd, 0x39, 0x00); + + ibmcam_PacketFormat2(uvd, 0x39, 0x02); + ibmcam_PacketFormat2(uvd, 0x0a, 0x37); + ibmcam_PacketFormat2(uvd, 0x0b, 0xb8); + ibmcam_PacketFormat2(uvd, 0x0c, 0xf3); + ibmcam_PacketFormat2(uvd, 0x0d, 0xe3); + ibmcam_PacketFormat2(uvd, 0x0e, 0x0d); + ibmcam_PacketFormat2(uvd, 0x0f, 0xf2); + ibmcam_PacketFormat2(uvd, 0x10, 0xd5); + ibmcam_PacketFormat2(uvd, 0x11, 0xba); + ibmcam_PacketFormat2(uvd, 0x12, 0x53); + ibmcam_PacketFormat2(uvd, 0x3f, 0xff); + ibmcam_PacketFormat2(uvd, 0x39, 0x00); + + ibmcam_PacketFormat2(uvd, 0x39, 0x02); + ibmcam_PacketFormat2(uvd, 0x16, 0x00); + ibmcam_PacketFormat2(uvd, 0x17, 0x28); + ibmcam_PacketFormat2(uvd, 0x18, 0x7d); + ibmcam_PacketFormat2(uvd, 0x19, 0xbe); + ibmcam_PacketFormat2(uvd, 0x3f, 0xff); + ibmcam_PacketFormat2(uvd, 0x39, 0x00); + + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x00, 0x18); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x13, 0x18); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x14, 0x06); + + /* This is default brightness */ + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x31, 0x37); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x32, 0x46); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x33, 0x55); + + ibmcam_Packet_Format1(uvd, 0x2e, 0x04); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x2d, 0x04); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x29, 0x80); + ibmcam_Packet_Format1(uvd, 0x2c, 0x01); + ibmcam_Packet_Format1(uvd, 0x30, 0x17); + ibmcam_Packet_Format1(uvd, 0x39, 0x08); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x34, 0x00); + + ibmcam_veio(uvd, 0, 0x00, 0x0101); + ibmcam_veio(uvd, 0, 0x00, 0x010a); + + switch (uvd->videosize) { + case VIDEOSIZE_128x96: + ibmcam_veio(uvd, 0, 0x80, 0x0103); + ibmcam_veio(uvd, 0, 0x60, 0x0105); + ibmcam_veio(uvd, 0, 0x0c, 0x010b); + ibmcam_veio(uvd, 0, 0x04, 0x011b); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x0b, 0x011d); + ibmcam_veio(uvd, 0, 0x00, 0x011e); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x00, 0x0129); + break; + case VIDEOSIZE_176x144: + ibmcam_veio(uvd, 0, 0xb0, 0x0103); + ibmcam_veio(uvd, 0, 0x8f, 0x0105); + ibmcam_veio(uvd, 0, 0x06, 0x010b); + ibmcam_veio(uvd, 0, 0x04, 0x011b); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x0d, 0x011d); + ibmcam_veio(uvd, 0, 0x00, 0x011e); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x03, 0x0129); + break; + case VIDEOSIZE_352x288: + ibmcam_veio(uvd, 0, 0xb0, 0x0103); + ibmcam_veio(uvd, 0, 0x90, 0x0105); + ibmcam_veio(uvd, 0, 0x02, 0x010b); + ibmcam_veio(uvd, 0, 0x04, 0x011b); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x05, 0x011d); + ibmcam_veio(uvd, 0, 0x00, 0x011e); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x00, 0x0129); + break; + } + + ibmcam_veio(uvd, 0, 0xff, 0x012b); + + /* This is another brightness - don't know why */ + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x31, 0xc3); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x32, 0xd2); + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, 0x33, 0xe1); + + /* Default contrast */ + for (i=0; i < ntries; i++) + ibmcam_Packet_Format1(uvd, contrast_14, 0x0a); + + /* Default sharpness */ + for (i=0; i < 2; i++) + ibmcam_PacketFormat2(uvd, sharp_13, 0x1a); /* Level 4 FIXME */ + + /* Default lighting conditions */ + ibmcam_Packet_Format1(uvd, light_27, lighting); /* 0=Bright 2=Low */ + + /* Assorted init */ + + switch (uvd->videosize) { + case VIDEOSIZE_128x96: + ibmcam_Packet_Format1(uvd, 0x2b, 0x1e); + ibmcam_veio(uvd, 0, 0xc9, 0x0119); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x80, 0x0109); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x36, 0x0102); + ibmcam_veio(uvd, 0, 0x1a, 0x0104); + ibmcam_veio(uvd, 0, 0x04, 0x011a); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x2b, 0x011c); + ibmcam_veio(uvd, 0, 0x23, 0x012a); /* Same everywhere */ +#if 0 + ibmcam_veio(uvd, 0, 0x00, 0x0106); + ibmcam_veio(uvd, 0, 0x38, 0x0107); +#else + ibmcam_veio(uvd, 0, 0x02, 0x0106); + ibmcam_veio(uvd, 0, 0x2a, 0x0107); +#endif + break; + case VIDEOSIZE_176x144: + ibmcam_Packet_Format1(uvd, 0x2b, 0x1e); + ibmcam_veio(uvd, 0, 0xc9, 0x0119); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x80, 0x0109); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x04, 0x0102); + ibmcam_veio(uvd, 0, 0x02, 0x0104); + ibmcam_veio(uvd, 0, 0x04, 0x011a); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x2b, 0x011c); + ibmcam_veio(uvd, 0, 0x23, 0x012a); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x01, 0x0106); + ibmcam_veio(uvd, 0, 0xca, 0x0107); + break; + case VIDEOSIZE_352x288: + ibmcam_Packet_Format1(uvd, 0x2b, 0x1f); + ibmcam_veio(uvd, 0, 0xc9, 0x0119); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x80, 0x0109); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x08, 0x0102); + ibmcam_veio(uvd, 0, 0x01, 0x0104); + ibmcam_veio(uvd, 0, 0x04, 0x011a); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x2f, 0x011c); + ibmcam_veio(uvd, 0, 0x23, 0x012a); /* Same everywhere */ + ibmcam_veio(uvd, 0, 0x03, 0x0106); + ibmcam_veio(uvd, 0, 0xf6, 0x0107); + break; + } + return (CAMERA_IS_OPERATIONAL(uvd) ? 0 : -EFAULT); +} + +static int ibmcam_model2_setup(struct uvd *uvd) +{ + ibmcam_veio(uvd, 0, 0x0000, 0x0100); /* LED on */ + ibmcam_veio(uvd, 1, 0x0000, 0x0116); + ibmcam_veio(uvd, 0, 0x0060, 0x0116); + ibmcam_veio(uvd, 0, 0x0002, 0x0112); + ibmcam_veio(uvd, 0, 0x00bc, 0x012c); + ibmcam_veio(uvd, 0, 0x0008, 0x012b); + ibmcam_veio(uvd, 0, 0x0000, 0x0108); + ibmcam_veio(uvd, 0, 0x0001, 0x0133); + ibmcam_veio(uvd, 0, 0x0001, 0x0102); + switch (uvd->videosize) { + case VIDEOSIZE_176x144: + ibmcam_veio(uvd, 0, 0x002c, 0x0103); /* All except 320x240 */ + ibmcam_veio(uvd, 0, 0x0000, 0x0104); /* Same */ + ibmcam_veio(uvd, 0, 0x0024, 0x0105); /* 176x144, 352x288 */ + ibmcam_veio(uvd, 0, 0x00b9, 0x010a); /* Unique to this mode */ + ibmcam_veio(uvd, 0, 0x0038, 0x0119); /* Unique to this mode */ + ibmcam_veio(uvd, 0, 0x0003, 0x0106); /* Same */ + ibmcam_veio(uvd, 0, 0x0090, 0x0107); /* Unique to every mode*/ + break; + case VIDEOSIZE_320x240: + ibmcam_veio(uvd, 0, 0x0028, 0x0103); /* Unique to this mode */ + ibmcam_veio(uvd, 0, 0x0000, 0x0104); /* Same */ + ibmcam_veio(uvd, 0, 0x001e, 0x0105); /* 320x240, 352x240 */ + ibmcam_veio(uvd, 0, 0x0039, 0x010a); /* All except 176x144 */ + ibmcam_veio(uvd, 0, 0x0070, 0x0119); /* All except 176x144 */ + ibmcam_veio(uvd, 0, 0x0003, 0x0106); /* Same */ + ibmcam_veio(uvd, 0, 0x0098, 0x0107); /* Unique to every mode*/ + break; + case VIDEOSIZE_352x240: + ibmcam_veio(uvd, 0, 0x002c, 0x0103); /* All except 320x240 */ + ibmcam_veio(uvd, 0, 0x0000, 0x0104); /* Same */ + ibmcam_veio(uvd, 0, 0x001e, 0x0105); /* 320x240, 352x240 */ + ibmcam_veio(uvd, 0, 0x0039, 0x010a); /* All except 176x144 */ + ibmcam_veio(uvd, 0, 0x0070, 0x0119); /* All except 176x144 */ + ibmcam_veio(uvd, 0, 0x0003, 0x0106); /* Same */ + ibmcam_veio(uvd, 0, 0x00da, 0x0107); /* Unique to every mode*/ + break; + case VIDEOSIZE_352x288: + ibmcam_veio(uvd, 0, 0x002c, 0x0103); /* All except 320x240 */ + ibmcam_veio(uvd, 0, 0x0000, 0x0104); /* Same */ + ibmcam_veio(uvd, 0, 0x0024, 0x0105); /* 176x144, 352x288 */ + ibmcam_veio(uvd, 0, 0x0039, 0x010a); /* All except 176x144 */ + ibmcam_veio(uvd, 0, 0x0070, 0x0119); /* All except 176x144 */ + ibmcam_veio(uvd, 0, 0x0003, 0x0106); /* Same */ + ibmcam_veio(uvd, 0, 0x00fe, 0x0107); /* Unique to every mode*/ + break; + } + return (CAMERA_IS_OPERATIONAL(uvd) ? 0 : -EFAULT); +} + +/* + * ibmcam_model1_setup_after_video_if() + * + * This code adds finishing touches to the video data interface. + * Here we configure the frame rate and turn on the LED. + */ +static void ibmcam_model1_setup_after_video_if(struct uvd *uvd) +{ + unsigned short internal_frame_rate; + + RESTRICT_TO_RANGE(framerate, FRAMERATE_MIN, FRAMERATE_MAX); + internal_frame_rate = FRAMERATE_MAX - framerate; /* 0=Fast 6=Slow */ + ibmcam_veio(uvd, 0, 0x01, 0x0100); /* LED On */ + ibmcam_veio(uvd, 0, internal_frame_rate, 0x0111); + ibmcam_veio(uvd, 0, 0x01, 0x0114); + ibmcam_veio(uvd, 0, 0xc0, 0x010c); +} + +static void ibmcam_model2_setup_after_video_if(struct uvd *uvd) +{ + unsigned short setup_model2_rg2, setup_model2_sat, setup_model2_yb; + + ibmcam_veio(uvd, 0, 0x0000, 0x0100); /* LED on */ + + switch (uvd->videosize) { + case VIDEOSIZE_176x144: + ibmcam_veio(uvd, 0, 0x0050, 0x0111); + ibmcam_veio(uvd, 0, 0x00d0, 0x0111); + break; + case VIDEOSIZE_320x240: + case VIDEOSIZE_352x240: + case VIDEOSIZE_352x288: + ibmcam_veio(uvd, 0, 0x0040, 0x0111); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + break; + } + ibmcam_veio(uvd, 0, 0x009b, 0x010f); + ibmcam_veio(uvd, 0, 0x00bb, 0x010f); + + /* + * Hardware settings, may affect CMOS sensor; not user controls! + * ------------------------------------------------------------- + * 0x0004: no effect + * 0x0006: hardware effect + * 0x0008: no effect + * 0x000a: stops video stream, probably important h/w setting + * 0x000c: changes color in hardware manner (not user setting) + * 0x0012: changes number of colors (does not affect speed) + * 0x002a: no effect + * 0x002c: hardware setting (related to scan lines) + * 0x002e: stops video stream, probably important h/w setting + */ + ibmcam_model2_Packet1(uvd, 0x000a, 0x005c); + ibmcam_model2_Packet1(uvd, 0x0004, 0x0000); + ibmcam_model2_Packet1(uvd, 0x0006, 0x00fb); + ibmcam_model2_Packet1(uvd, 0x0008, 0x0000); + ibmcam_model2_Packet1(uvd, 0x000c, 0x0009); + ibmcam_model2_Packet1(uvd, 0x0012, 0x000a); + ibmcam_model2_Packet1(uvd, 0x002a, 0x0000); + ibmcam_model2_Packet1(uvd, 0x002c, 0x0000); + ibmcam_model2_Packet1(uvd, 0x002e, 0x0008); + + /* + * Function 0x0030 pops up all over the place. Apparently + * it is a hardware control register, with every bit assigned to + * do something. + */ + ibmcam_model2_Packet1(uvd, 0x0030, 0x0000); + + /* + * Magic control of CMOS sensor. Only lower values like + * 0-3 work, and picture shifts left or right. Don't change. + */ + switch (uvd->videosize) { + case VIDEOSIZE_176x144: + ibmcam_model2_Packet1(uvd, 0x0014, 0x0002); + ibmcam_model2_Packet1(uvd, 0x0016, 0x0002); /* Horizontal shift */ + ibmcam_model2_Packet1(uvd, 0x0018, 0x004a); /* Another hardware setting */ + break; + case VIDEOSIZE_320x240: + ibmcam_model2_Packet1(uvd, 0x0014, 0x0009); + ibmcam_model2_Packet1(uvd, 0x0016, 0x0005); /* Horizontal shift */ + ibmcam_model2_Packet1(uvd, 0x0018, 0x0044); /* Another hardware setting */ + break; + case VIDEOSIZE_352x240: + /* This mode doesn't work as Windows programs it; changed to work */ + ibmcam_model2_Packet1(uvd, 0x0014, 0x0009); /* Windows sets this to 8 */ + ibmcam_model2_Packet1(uvd, 0x0016, 0x0003); /* Horizontal shift */ + ibmcam_model2_Packet1(uvd, 0x0018, 0x0044); /* Windows sets this to 0x0045 */ + break; + case VIDEOSIZE_352x288: + ibmcam_model2_Packet1(uvd, 0x0014, 0x0003); + ibmcam_model2_Packet1(uvd, 0x0016, 0x0002); /* Horizontal shift */ + ibmcam_model2_Packet1(uvd, 0x0018, 0x004a); /* Another hardware setting */ + break; + } + + ibmcam_model2_Packet1(uvd, mod2_brightness, 0x005a); + + /* + * We have our own frame rate setting varying from 0 (slowest) to 6 (fastest). + * The camera model 2 allows frame rate in range [0..0x1F] where 0 is also the + * slowest setting. However for all practical reasons high settings make no + * sense because USB is not fast enough to support high FPS. Be aware that + * the picture datastream will be severely disrupted if you ask for + * frame rate faster than allowed for the video size - see below: + * + * Allowable ranges (obtained experimentally on OHCI, K6-3, 450 MHz): + * ----------------------------------------------------------------- + * 176x144: [6..31] + * 320x240: [8..31] + * 352x240: [10..31] + * 352x288: [16..31] I have to raise lower threshold for stability... + * + * As usual, slower FPS provides better sensitivity. + */ + { + short hw_fps=31, i_framerate; + + RESTRICT_TO_RANGE(framerate, FRAMERATE_MIN, FRAMERATE_MAX); + i_framerate = FRAMERATE_MAX - framerate + FRAMERATE_MIN; + switch (uvd->videosize) { + case VIDEOSIZE_176x144: + hw_fps = 6 + i_framerate*4; + break; + case VIDEOSIZE_320x240: + hw_fps = 8 + i_framerate*3; + break; + case VIDEOSIZE_352x240: + hw_fps = 10 + i_framerate*2; + break; + case VIDEOSIZE_352x288: + hw_fps = 28 + i_framerate/2; + break; + } + if (uvd->debug > 0) + info("Framerate (hardware): %hd.", hw_fps); + RESTRICT_TO_RANGE(hw_fps, 0, 31); + ibmcam_model2_Packet1(uvd, mod2_set_framerate, hw_fps); + } + + /* + * This setting does not visibly affect pictures; left it here + * because it was present in Windows USB data stream. This function + * does not allow arbitrary values and apparently is a bit mask, to + * be activated only at appropriate time. Don't change it randomly! + */ + switch (uvd->videosize) { + case VIDEOSIZE_176x144: + ibmcam_model2_Packet1(uvd, 0x0026, 0x00c2); + break; + case VIDEOSIZE_320x240: + ibmcam_model2_Packet1(uvd, 0x0026, 0x0044); + break; + case VIDEOSIZE_352x240: + ibmcam_model2_Packet1(uvd, 0x0026, 0x0046); + break; + case VIDEOSIZE_352x288: + ibmcam_model2_Packet1(uvd, 0x0026, 0x0048); + break; + } + + ibmcam_model2_Packet1(uvd, mod2_sensitivity, lighting); + + if (init_model2_rg2 >= 0) { + RESTRICT_TO_RANGE(init_model2_rg2, 0, 255); + setup_model2_rg2 = init_model2_rg2; + } else + setup_model2_rg2 = 0x002f; + + if (init_model2_sat >= 0) { + RESTRICT_TO_RANGE(init_model2_sat, 0, 255); + setup_model2_sat = init_model2_sat; + } else + setup_model2_sat = 0x0034; + + if (init_model2_yb >= 0) { + RESTRICT_TO_RANGE(init_model2_yb, 0, 255); + setup_model2_yb = init_model2_yb; + } else + setup_model2_yb = 0x00a0; + + ibmcam_model2_Packet1(uvd, mod2_color_balance_rg2, setup_model2_rg2); + ibmcam_model2_Packet1(uvd, mod2_saturation, setup_model2_sat); + ibmcam_model2_Packet1(uvd, mod2_color_balance_yb, setup_model2_yb); + ibmcam_model2_Packet1(uvd, mod2_hue, uvd->vpic.hue >> 9); /* 0 .. 7F */; + + /* Hardware control command */ + ibmcam_model2_Packet1(uvd, 0x0030, 0x0004); + + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); /* Go camera, go! */ + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); +} + +static void ibmcam_model4_setup_after_video_if(struct uvd *uvd) +{ + switch (uvd->videosize) { + case VIDEOSIZE_128x96: + ibmcam_veio(uvd, 0, 0x0000, 0x0100); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + ibmcam_veio(uvd, 0, 0x00bc, 0x012c); + ibmcam_veio(uvd, 0, 0x0080, 0x012b); + ibmcam_veio(uvd, 0, 0x0000, 0x0108); + ibmcam_veio(uvd, 0, 0x0001, 0x0133); + ibmcam_veio(uvd, 0, 0x009b, 0x010f); + ibmcam_veio(uvd, 0, 0x00bb, 0x010f); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x000a, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x005c, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0004, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00fb, 0x012e); + ibmcam_veio(uvd, 0, 0x0000, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x000c, 0x0127); + ibmcam_veio(uvd, 0, 0x0009, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0012, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0008, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x002a, 0x012d); + ibmcam_veio(uvd, 0, 0x0000, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0034, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x0070, 0x0119); + ibmcam_veio(uvd, 0, 0x00d2, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x005e, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x00d0, 0x0111); + ibmcam_veio(uvd, 0, 0x0039, 0x010a); + ibmcam_veio(uvd, 0, 0x0001, 0x0102); + ibmcam_veio(uvd, 0, 0x0028, 0x0103); + ibmcam_veio(uvd, 0, 0x0000, 0x0104); + ibmcam_veio(uvd, 0, 0x001e, 0x0105); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0016, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x000a, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0014, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012e); + ibmcam_veio(uvd, 0, 0x001a, 0x0130); + ibmcam_veio(uvd, 0, 0x8a0a, 0x0124); + ibmcam_veio(uvd, 0, 0x005a, 0x012d); + ibmcam_veio(uvd, 0, 0x9545, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x0127); + ibmcam_veio(uvd, 0, 0x0018, 0x012e); + ibmcam_veio(uvd, 0, 0x0043, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x001c, 0x0127); + ibmcam_veio(uvd, 0, 0x00eb, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0032, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0036, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x001e, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0017, 0x0127); + ibmcam_veio(uvd, 0, 0x0013, 0x012e); + ibmcam_veio(uvd, 0, 0x0031, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x0017, 0x012d); + ibmcam_veio(uvd, 0, 0x0078, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0004, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + break; + case VIDEOSIZE_160x120: + ibmcam_veio(uvd, 0, 0x0000, 0x0100); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + ibmcam_veio(uvd, 0, 0x00bc, 0x012c); + ibmcam_veio(uvd, 0, 0x0080, 0x012b); + ibmcam_veio(uvd, 0, 0x0000, 0x0108); + ibmcam_veio(uvd, 0, 0x0001, 0x0133); + ibmcam_veio(uvd, 0, 0x009b, 0x010f); + ibmcam_veio(uvd, 0, 0x00bb, 0x010f); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x000a, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x005c, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0004, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00fb, 0x012e); + ibmcam_veio(uvd, 0, 0x0000, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x000c, 0x0127); + ibmcam_veio(uvd, 0, 0x0009, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0012, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0008, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x002a, 0x012d); + ibmcam_veio(uvd, 0, 0x0000, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0034, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x0038, 0x0119); + ibmcam_veio(uvd, 0, 0x00d8, 0x0107); + ibmcam_veio(uvd, 0, 0x0002, 0x0106); + ibmcam_veio(uvd, 0, 0x00d0, 0x0111); + ibmcam_veio(uvd, 0, 0x00b9, 0x010a); + ibmcam_veio(uvd, 0, 0x0001, 0x0102); + ibmcam_veio(uvd, 0, 0x0028, 0x0103); + ibmcam_veio(uvd, 0, 0x0000, 0x0104); + ibmcam_veio(uvd, 0, 0x001e, 0x0105); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0016, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x000b, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0014, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012e); + ibmcam_veio(uvd, 0, 0x001a, 0x0130); + ibmcam_veio(uvd, 0, 0x8a0a, 0x0124); + ibmcam_veio(uvd, 0, 0x005a, 0x012d); + ibmcam_veio(uvd, 0, 0x9545, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x0127); + ibmcam_veio(uvd, 0, 0x0018, 0x012e); + ibmcam_veio(uvd, 0, 0x0043, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x001c, 0x0127); + ibmcam_veio(uvd, 0, 0x00c7, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0032, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0025, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0036, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x001e, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0048, 0x0127); + ibmcam_veio(uvd, 0, 0x0035, 0x012e); + ibmcam_veio(uvd, 0, 0x00d0, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x0048, 0x012d); + ibmcam_veio(uvd, 0, 0x0090, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x0001, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0004, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + break; + case VIDEOSIZE_176x144: + ibmcam_veio(uvd, 0, 0x0000, 0x0100); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + ibmcam_veio(uvd, 0, 0x00bc, 0x012c); + ibmcam_veio(uvd, 0, 0x0080, 0x012b); + ibmcam_veio(uvd, 0, 0x0000, 0x0108); + ibmcam_veio(uvd, 0, 0x0001, 0x0133); + ibmcam_veio(uvd, 0, 0x009b, 0x010f); + ibmcam_veio(uvd, 0, 0x00bb, 0x010f); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x000a, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x005c, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0004, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00fb, 0x012e); + ibmcam_veio(uvd, 0, 0x0000, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x000c, 0x0127); + ibmcam_veio(uvd, 0, 0x0009, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0012, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0008, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x002a, 0x012d); + ibmcam_veio(uvd, 0, 0x0000, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0034, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x0038, 0x0119); + ibmcam_veio(uvd, 0, 0x00d6, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x0018, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x00d0, 0x0111); + ibmcam_veio(uvd, 0, 0x00b9, 0x010a); + ibmcam_veio(uvd, 0, 0x0001, 0x0102); + ibmcam_veio(uvd, 0, 0x002c, 0x0103); + ibmcam_veio(uvd, 0, 0x0000, 0x0104); + ibmcam_veio(uvd, 0, 0x0024, 0x0105); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0016, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0007, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0014, 0x012d); + ibmcam_veio(uvd, 0, 0x0001, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012e); + ibmcam_veio(uvd, 0, 0x001a, 0x0130); + ibmcam_veio(uvd, 0, 0x8a0a, 0x0124); + ibmcam_veio(uvd, 0, 0x005e, 0x012d); + ibmcam_veio(uvd, 0, 0x9545, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x0127); + ibmcam_veio(uvd, 0, 0x0018, 0x012e); + ibmcam_veio(uvd, 0, 0x0049, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x001c, 0x0127); + ibmcam_veio(uvd, 0, 0x00c7, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0032, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0028, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0036, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x001e, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0010, 0x0127); + ibmcam_veio(uvd, 0, 0x0013, 0x012e); + ibmcam_veio(uvd, 0, 0x002a, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x0010, 0x012d); + ibmcam_veio(uvd, 0, 0x006d, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x0001, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0004, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + break; + case VIDEOSIZE_320x240: + ibmcam_veio(uvd, 0, 0x0000, 0x0100); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + ibmcam_veio(uvd, 0, 0x00bc, 0x012c); + ibmcam_veio(uvd, 0, 0x0080, 0x012b); + ibmcam_veio(uvd, 0, 0x0000, 0x0108); + ibmcam_veio(uvd, 0, 0x0001, 0x0133); + ibmcam_veio(uvd, 0, 0x009b, 0x010f); + ibmcam_veio(uvd, 0, 0x00bb, 0x010f); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x000a, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x005c, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0004, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00fb, 0x012e); + ibmcam_veio(uvd, 0, 0x0000, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x000c, 0x0127); + ibmcam_veio(uvd, 0, 0x0009, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0012, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0008, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x002a, 0x012d); + ibmcam_veio(uvd, 0, 0x0000, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0034, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x0070, 0x0119); + ibmcam_veio(uvd, 0, 0x00d2, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x005e, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x00d0, 0x0111); + ibmcam_veio(uvd, 0, 0x0039, 0x010a); + ibmcam_veio(uvd, 0, 0x0001, 0x0102); + ibmcam_veio(uvd, 0, 0x0028, 0x0103); + ibmcam_veio(uvd, 0, 0x0000, 0x0104); + ibmcam_veio(uvd, 0, 0x001e, 0x0105); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0016, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x000a, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0014, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012e); + ibmcam_veio(uvd, 0, 0x001a, 0x0130); + ibmcam_veio(uvd, 0, 0x8a0a, 0x0124); + ibmcam_veio(uvd, 0, 0x005a, 0x012d); + ibmcam_veio(uvd, 0, 0x9545, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x0127); + ibmcam_veio(uvd, 0, 0x0018, 0x012e); + ibmcam_veio(uvd, 0, 0x0043, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x001c, 0x0127); + ibmcam_veio(uvd, 0, 0x00eb, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0032, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0036, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x001e, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0017, 0x0127); + ibmcam_veio(uvd, 0, 0x0013, 0x012e); + ibmcam_veio(uvd, 0, 0x0031, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x0017, 0x012d); + ibmcam_veio(uvd, 0, 0x0078, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0004, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + break; + case VIDEOSIZE_352x288: + ibmcam_veio(uvd, 0, 0x0000, 0x0100); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + ibmcam_veio(uvd, 0, 0x00bc, 0x012c); + ibmcam_veio(uvd, 0, 0x0080, 0x012b); + ibmcam_veio(uvd, 0, 0x0000, 0x0108); + ibmcam_veio(uvd, 0, 0x0001, 0x0133); + ibmcam_veio(uvd, 0, 0x009b, 0x010f); + ibmcam_veio(uvd, 0, 0x00bb, 0x010f); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x000a, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x005c, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0004, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00fb, 0x012e); + ibmcam_veio(uvd, 0, 0x0000, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x000c, 0x0127); + ibmcam_veio(uvd, 0, 0x0009, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0012, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0008, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x002a, 0x012d); + ibmcam_veio(uvd, 0, 0x0000, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0034, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x0070, 0x0119); + ibmcam_veio(uvd, 0, 0x00f2, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x008c, 0x0107); + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x00c0, 0x0111); + ibmcam_veio(uvd, 0, 0x0039, 0x010a); + ibmcam_veio(uvd, 0, 0x0001, 0x0102); + ibmcam_veio(uvd, 0, 0x002c, 0x0103); + ibmcam_veio(uvd, 0, 0x0000, 0x0104); + ibmcam_veio(uvd, 0, 0x0024, 0x0105); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0016, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0006, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0014, 0x012d); + ibmcam_veio(uvd, 0, 0x0002, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012e); + ibmcam_veio(uvd, 0, 0x001a, 0x0130); + ibmcam_veio(uvd, 0, 0x8a0a, 0x0124); + ibmcam_veio(uvd, 0, 0x005e, 0x012d); + ibmcam_veio(uvd, 0, 0x9545, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x0127); + ibmcam_veio(uvd, 0, 0x0018, 0x012e); + ibmcam_veio(uvd, 0, 0x0049, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012f); + ibmcam_veio(uvd, 0, 0xd055, 0x0124); + ibmcam_veio(uvd, 0, 0x001c, 0x0127); + ibmcam_veio(uvd, 0, 0x00cf, 0x012e); + ibmcam_veio(uvd, 0, 0xaa28, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0032, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0x00aa, 0x0130); + ibmcam_veio(uvd, 0, 0x82a8, 0x0124); + ibmcam_veio(uvd, 0, 0x0036, 0x012d); + ibmcam_veio(uvd, 0, 0x0008, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0xfffa, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x001e, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0010, 0x0127); + ibmcam_veio(uvd, 0, 0x0013, 0x012e); + ibmcam_veio(uvd, 0, 0x0025, 0x0130); + ibmcam_veio(uvd, 0, 0x8a28, 0x0124); + ibmcam_veio(uvd, 0, 0x0010, 0x012d); + ibmcam_veio(uvd, 0, 0x0048, 0x012f); + ibmcam_veio(uvd, 0, 0xd145, 0x0124); + ibmcam_veio(uvd, 0, 0x0000, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00aa, 0x012d); + ibmcam_veio(uvd, 0, 0x0038, 0x012f); + ibmcam_veio(uvd, 0, 0xd141, 0x0124); + ibmcam_veio(uvd, 0, 0x0004, 0x0127); + ibmcam_veio(uvd, 0, 0xfea8, 0x0124); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + break; + } + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); +} + +static void ibmcam_model3_setup_after_video_if(struct uvd *uvd) +{ + int i; + /* + * 01.01.08 - Added for RCA video in support -LO + * This struct is used to init the Model3 cam to use the RCA video in port + * instead of the CCD sensor. + */ + static const struct struct_initData initData[] = { + {0, 0x0000, 0x010c}, + {0, 0x0006, 0x012c}, + {0, 0x0078, 0x012d}, + {0, 0x0046, 0x012f}, + {0, 0xd141, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfea8, 0x0124}, + {1, 0x0000, 0x0116}, + {0, 0x0064, 0x0116}, + {1, 0x0000, 0x0115}, + {0, 0x0003, 0x0115}, + {0, 0x0008, 0x0123}, + {0, 0x0000, 0x0117}, + {0, 0x0000, 0x0112}, + {0, 0x0080, 0x0100}, + {0, 0x0000, 0x0100}, + {1, 0x0000, 0x0116}, + {0, 0x0060, 0x0116}, + {0, 0x0002, 0x0112}, + {0, 0x0000, 0x0123}, + {0, 0x0001, 0x0117}, + {0, 0x0040, 0x0108}, + {0, 0x0019, 0x012c}, + {0, 0x0040, 0x0116}, + {0, 0x000a, 0x0115}, + {0, 0x000b, 0x0115}, + {0, 0x0078, 0x012d}, + {0, 0x0046, 0x012f}, + {0, 0xd141, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfea8, 0x0124}, + {0, 0x0064, 0x0116}, + {0, 0x0000, 0x0115}, + {0, 0x0001, 0x0115}, + {0, 0xffff, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00aa, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xffff, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00f2, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x000f, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xffff, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00f8, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00fc, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xffff, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00f9, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x003c, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xffff, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0027, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0019, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0021, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0006, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0045, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x002a, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x000e, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x002b, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00f4, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x002c, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0004, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x002d, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0014, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x002e, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0003, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x002f, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0003, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0014, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0040, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0040, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0053, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0x0000, 0x0101}, + {0, 0x00a0, 0x0103}, + {0, 0x0078, 0x0105}, + {0, 0x0000, 0x010a}, + {0, 0x0024, 0x010b}, + {0, 0x0028, 0x0119}, + {0, 0x0088, 0x011b}, + {0, 0x0002, 0x011d}, + {0, 0x0003, 0x011e}, + {0, 0x0000, 0x0129}, + {0, 0x00fc, 0x012b}, + {0, 0x0008, 0x0102}, + {0, 0x0000, 0x0104}, + {0, 0x0008, 0x011a}, + {0, 0x0028, 0x011c}, + {0, 0x0021, 0x012a}, + {0, 0x0000, 0x0118}, + {0, 0x0000, 0x0132}, + {0, 0x0000, 0x0109}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0031, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0040, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0040, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x00dc, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0032, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0020, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0001, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0040, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0040, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0037, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0030, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0xfff9, 0x0124}, + {0, 0x0086, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0038, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0008, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0x0000, 0x0127}, + {0, 0xfff8, 0x0124}, + {0, 0xfffd, 0x0124}, + {0, 0xfffa, 0x0124}, + {0, 0x0003, 0x0106}, + {0, 0x0062, 0x0107}, + {0, 0x0003, 0x0111}, + }; +#define NUM_INIT_DATA + + unsigned short compression = 0; /* 0=none, 7=best frame rate */ + int f_rate; /* 0=Fastest 7=slowest */ + + if (IBMCAM_T(uvd)->initialized) + return; + + /* Internal frame rate is controlled by f_rate value */ + f_rate = 7 - framerate; + RESTRICT_TO_RANGE(f_rate, 0, 7); + + ibmcam_veio(uvd, 0, 0x0000, 0x0100); + ibmcam_veio(uvd, 1, 0x0000, 0x0116); + ibmcam_veio(uvd, 0, 0x0060, 0x0116); + ibmcam_veio(uvd, 0, 0x0002, 0x0112); + ibmcam_veio(uvd, 0, 0x0000, 0x0123); + ibmcam_veio(uvd, 0, 0x0001, 0x0117); + ibmcam_veio(uvd, 0, 0x0040, 0x0108); + ibmcam_veio(uvd, 0, 0x0019, 0x012c); + ibmcam_veio(uvd, 0, 0x0060, 0x0116); + ibmcam_veio(uvd, 0, 0x0002, 0x0115); + ibmcam_veio(uvd, 0, 0x0003, 0x0115); + ibmcam_veio(uvd, 1, 0x0000, 0x0115); + ibmcam_veio(uvd, 0, 0x000b, 0x0115); + ibmcam_model3_Packet1(uvd, 0x000a, 0x0040); + ibmcam_model3_Packet1(uvd, 0x000b, 0x00f6); + ibmcam_model3_Packet1(uvd, 0x000c, 0x0002); + ibmcam_model3_Packet1(uvd, 0x000d, 0x0020); + ibmcam_model3_Packet1(uvd, 0x000e, 0x0033); + ibmcam_model3_Packet1(uvd, 0x000f, 0x0007); + ibmcam_model3_Packet1(uvd, 0x0010, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0011, 0x0070); + ibmcam_model3_Packet1(uvd, 0x0012, 0x0030); + ibmcam_model3_Packet1(uvd, 0x0013, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0014, 0x0001); + ibmcam_model3_Packet1(uvd, 0x0015, 0x0001); + ibmcam_model3_Packet1(uvd, 0x0016, 0x0001); + ibmcam_model3_Packet1(uvd, 0x0017, 0x0001); + ibmcam_model3_Packet1(uvd, 0x0018, 0x0000); + ibmcam_model3_Packet1(uvd, 0x001e, 0x00c3); + ibmcam_model3_Packet1(uvd, 0x0020, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0028, 0x0010); + ibmcam_model3_Packet1(uvd, 0x0029, 0x0054); + ibmcam_model3_Packet1(uvd, 0x002a, 0x0013); + ibmcam_model3_Packet1(uvd, 0x002b, 0x0007); + ibmcam_model3_Packet1(uvd, 0x002d, 0x0028); + ibmcam_model3_Packet1(uvd, 0x002e, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0031, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0032, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0033, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0034, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0035, 0x0038); + ibmcam_model3_Packet1(uvd, 0x003a, 0x0001); + ibmcam_model3_Packet1(uvd, 0x003c, 0x001e); + ibmcam_model3_Packet1(uvd, 0x003f, 0x000a); + ibmcam_model3_Packet1(uvd, 0x0041, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0046, 0x003f); + ibmcam_model3_Packet1(uvd, 0x0047, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0050, 0x0005); + ibmcam_model3_Packet1(uvd, 0x0052, 0x001a); + ibmcam_model3_Packet1(uvd, 0x0053, 0x0003); + ibmcam_model3_Packet1(uvd, 0x005a, 0x006b); + ibmcam_model3_Packet1(uvd, 0x005d, 0x001e); + ibmcam_model3_Packet1(uvd, 0x005e, 0x0030); + ibmcam_model3_Packet1(uvd, 0x005f, 0x0041); + ibmcam_model3_Packet1(uvd, 0x0064, 0x0008); + ibmcam_model3_Packet1(uvd, 0x0065, 0x0015); + ibmcam_model3_Packet1(uvd, 0x0068, 0x000f); + ibmcam_model3_Packet1(uvd, 0x0079, 0x0000); + ibmcam_model3_Packet1(uvd, 0x007a, 0x0000); + ibmcam_model3_Packet1(uvd, 0x007c, 0x003f); + ibmcam_model3_Packet1(uvd, 0x0082, 0x000f); + ibmcam_model3_Packet1(uvd, 0x0085, 0x0000); + ibmcam_model3_Packet1(uvd, 0x0099, 0x0000); + ibmcam_model3_Packet1(uvd, 0x009b, 0x0023); + ibmcam_model3_Packet1(uvd, 0x009c, 0x0022); + ibmcam_model3_Packet1(uvd, 0x009d, 0x0096); + ibmcam_model3_Packet1(uvd, 0x009e, 0x0096); + ibmcam_model3_Packet1(uvd, 0x009f, 0x000a); + + switch (uvd->videosize) { + case VIDEOSIZE_160x120: + ibmcam_veio(uvd, 0, 0x0000, 0x0101); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x00a0, 0x0103); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x0078, 0x0105); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x0000, 0x010a); /* Same */ + ibmcam_veio(uvd, 0, 0x0024, 0x010b); /* Differs everywhere */ + ibmcam_veio(uvd, 0, 0x00a9, 0x0119); + ibmcam_veio(uvd, 0, 0x0016, 0x011b); + ibmcam_veio(uvd, 0, 0x0002, 0x011d); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x0003, 0x011e); /* Same on 176x144, 640x480 */ + ibmcam_veio(uvd, 0, 0x0000, 0x0129); /* Same */ + ibmcam_veio(uvd, 0, 0x00fc, 0x012b); /* Same */ + ibmcam_veio(uvd, 0, 0x0018, 0x0102); + ibmcam_veio(uvd, 0, 0x0004, 0x0104); + ibmcam_veio(uvd, 0, 0x0004, 0x011a); + ibmcam_veio(uvd, 0, 0x0028, 0x011c); + ibmcam_veio(uvd, 0, 0x0022, 0x012a); /* Same */ + ibmcam_veio(uvd, 0, 0x0000, 0x0118); + ibmcam_veio(uvd, 0, 0x0000, 0x0132); + ibmcam_model3_Packet1(uvd, 0x0021, 0x0001); /* Same */ + ibmcam_veio(uvd, 0, compression, 0x0109); + break; + case VIDEOSIZE_320x240: + ibmcam_veio(uvd, 0, 0x0000, 0x0101); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x00a0, 0x0103); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x0078, 0x0105); /* Same on 176x144, 320x240 */ + ibmcam_veio(uvd, 0, 0x0000, 0x010a); /* Same */ + ibmcam_veio(uvd, 0, 0x0028, 0x010b); /* Differs everywhere */ + ibmcam_veio(uvd, 0, 0x0002, 0x011d); /* Same */ + ibmcam_veio(uvd, 0, 0x0000, 0x011e); + ibmcam_veio(uvd, 0, 0x0000, 0x0129); /* Same */ + ibmcam_veio(uvd, 0, 0x00fc, 0x012b); /* Same */ + /* 4 commands from 160x120 skipped */ + ibmcam_veio(uvd, 0, 0x0022, 0x012a); /* Same */ + ibmcam_model3_Packet1(uvd, 0x0021, 0x0001); /* Same */ + ibmcam_veio(uvd, 0, compression, 0x0109); + ibmcam_veio(uvd, 0, 0x00d9, 0x0119); + ibmcam_veio(uvd, 0, 0x0006, 0x011b); + ibmcam_veio(uvd, 0, 0x0021, 0x0102); /* Same on 320x240, 640x480 */ + ibmcam_veio(uvd, 0, 0x0010, 0x0104); + ibmcam_veio(uvd, 0, 0x0004, 0x011a); + ibmcam_veio(uvd, 0, 0x003f, 0x011c); + ibmcam_veio(uvd, 0, 0x001c, 0x0118); + ibmcam_veio(uvd, 0, 0x0000, 0x0132); + break; + case VIDEOSIZE_640x480: + ibmcam_veio(uvd, 0, 0x00f0, 0x0105); + ibmcam_veio(uvd, 0, 0x0000, 0x010a); /* Same */ + ibmcam_veio(uvd, 0, 0x0038, 0x010b); /* Differs everywhere */ + ibmcam_veio(uvd, 0, 0x00d9, 0x0119); /* Same on 320x240, 640x480 */ + ibmcam_veio(uvd, 0, 0x0006, 0x011b); /* Same on 320x240, 640x480 */ + ibmcam_veio(uvd, 0, 0x0004, 0x011d); /* NC */ + ibmcam_veio(uvd, 0, 0x0003, 0x011e); /* Same on 176x144, 640x480 */ + ibmcam_veio(uvd, 0, 0x0000, 0x0129); /* Same */ + ibmcam_veio(uvd, 0, 0x00fc, 0x012b); /* Same */ + ibmcam_veio(uvd, 0, 0x0021, 0x0102); /* Same on 320x240, 640x480 */ + ibmcam_veio(uvd, 0, 0x0016, 0x0104); /* NC */ + ibmcam_veio(uvd, 0, 0x0004, 0x011a); /* Same on 320x240, 640x480 */ + ibmcam_veio(uvd, 0, 0x003f, 0x011c); /* Same on 320x240, 640x480 */ + ibmcam_veio(uvd, 0, 0x0022, 0x012a); /* Same */ + ibmcam_veio(uvd, 0, 0x001c, 0x0118); /* Same on 320x240, 640x480 */ + ibmcam_model3_Packet1(uvd, 0x0021, 0x0001); /* Same */ + ibmcam_veio(uvd, 0, compression, 0x0109); + ibmcam_veio(uvd, 0, 0x0040, 0x0101); + ibmcam_veio(uvd, 0, 0x0040, 0x0103); + ibmcam_veio(uvd, 0, 0x0000, 0x0132); /* Same on 320x240, 640x480 */ + break; + } + ibmcam_model3_Packet1(uvd, 0x007e, 0x000e); /* Hue */ + ibmcam_model3_Packet1(uvd, 0x0036, 0x0011); /* Brightness */ + ibmcam_model3_Packet1(uvd, 0x0060, 0x0002); /* Sharpness */ + ibmcam_model3_Packet1(uvd, 0x0061, 0x0004); /* Sharpness */ + ibmcam_model3_Packet1(uvd, 0x0062, 0x0005); /* Sharpness */ + ibmcam_model3_Packet1(uvd, 0x0063, 0x0014); /* Sharpness */ + ibmcam_model3_Packet1(uvd, 0x0096, 0x00a0); /* Red gain */ + ibmcam_model3_Packet1(uvd, 0x0097, 0x0096); /* Blue gain */ + ibmcam_model3_Packet1(uvd, 0x0067, 0x0001); /* Contrast */ + ibmcam_model3_Packet1(uvd, 0x005b, 0x000c); /* Contrast */ + ibmcam_model3_Packet1(uvd, 0x005c, 0x0016); /* Contrast */ + ibmcam_model3_Packet1(uvd, 0x0098, 0x000b); + ibmcam_model3_Packet1(uvd, 0x002c, 0x0003); /* Was 1, broke 640x480 */ + ibmcam_model3_Packet1(uvd, 0x002f, 0x002a); + ibmcam_model3_Packet1(uvd, 0x0030, 0x0029); + ibmcam_model3_Packet1(uvd, 0x0037, 0x0002); + ibmcam_model3_Packet1(uvd, 0x0038, 0x0059); + ibmcam_model3_Packet1(uvd, 0x003d, 0x002e); + ibmcam_model3_Packet1(uvd, 0x003e, 0x0028); + ibmcam_model3_Packet1(uvd, 0x0078, 0x0005); + ibmcam_model3_Packet1(uvd, 0x007b, 0x0011); + ibmcam_model3_Packet1(uvd, 0x007d, 0x004b); + ibmcam_model3_Packet1(uvd, 0x007f, 0x0022); + ibmcam_model3_Packet1(uvd, 0x0080, 0x000c); + ibmcam_model3_Packet1(uvd, 0x0081, 0x000b); + ibmcam_model3_Packet1(uvd, 0x0083, 0x00fd); + ibmcam_model3_Packet1(uvd, 0x0086, 0x000b); + ibmcam_model3_Packet1(uvd, 0x0087, 0x000b); + ibmcam_model3_Packet1(uvd, 0x007e, 0x000e); + ibmcam_model3_Packet1(uvd, 0x0096, 0x00a0); /* Red gain */ + ibmcam_model3_Packet1(uvd, 0x0097, 0x0096); /* Blue gain */ + ibmcam_model3_Packet1(uvd, 0x0098, 0x000b); + + switch (uvd->videosize) { + case VIDEOSIZE_160x120: + ibmcam_veio(uvd, 0, 0x0002, 0x0106); + ibmcam_veio(uvd, 0, 0x0008, 0x0107); + ibmcam_veio(uvd, 0, f_rate, 0x0111); /* Frame rate */ + ibmcam_model3_Packet1(uvd, 0x001f, 0x0000); /* Same */ + ibmcam_model3_Packet1(uvd, 0x0039, 0x001f); /* Same */ + ibmcam_model3_Packet1(uvd, 0x003b, 0x003c); /* Same */ + ibmcam_model3_Packet1(uvd, 0x0040, 0x000a); + ibmcam_model3_Packet1(uvd, 0x0051, 0x000a); + break; + case VIDEOSIZE_320x240: + ibmcam_veio(uvd, 0, 0x0003, 0x0106); + ibmcam_veio(uvd, 0, 0x0062, 0x0107); + ibmcam_veio(uvd, 0, f_rate, 0x0111); /* Frame rate */ + ibmcam_model3_Packet1(uvd, 0x001f, 0x0000); /* Same */ + ibmcam_model3_Packet1(uvd, 0x0039, 0x001f); /* Same */ + ibmcam_model3_Packet1(uvd, 0x003b, 0x003c); /* Same */ + ibmcam_model3_Packet1(uvd, 0x0040, 0x0008); + ibmcam_model3_Packet1(uvd, 0x0051, 0x000b); + break; + case VIDEOSIZE_640x480: + ibmcam_veio(uvd, 0, 0x0002, 0x0106); /* Adjustments */ + ibmcam_veio(uvd, 0, 0x00b4, 0x0107); /* Adjustments */ + ibmcam_veio(uvd, 0, f_rate, 0x0111); /* Frame rate */ + ibmcam_model3_Packet1(uvd, 0x001f, 0x0002); /* !Same */ + ibmcam_model3_Packet1(uvd, 0x0039, 0x003e); /* !Same */ + ibmcam_model3_Packet1(uvd, 0x0040, 0x0008); + ibmcam_model3_Packet1(uvd, 0x0051, 0x000a); + break; + } + + /* 01.01.08 - Added for RCA video in support -LO */ + if(init_model3_input) { + if (debug > 0) + info("Setting input to RCA."); + for (i=0; i < ARRAY_SIZE(initData); i++) { + ibmcam_veio(uvd, initData[i].req, initData[i].value, initData[i].index); + } + } + + ibmcam_veio(uvd, 0, 0x0001, 0x0114); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); +} + +/* + * ibmcam_video_stop() + * + * This code tells camera to stop streaming. The interface remains + * configured and bandwidth - claimed. + */ +static void ibmcam_video_stop(struct uvd *uvd) +{ + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + ibmcam_veio(uvd, 0, 0x00, 0x010c); + ibmcam_veio(uvd, 0, 0x00, 0x010c); + ibmcam_veio(uvd, 0, 0x01, 0x0114); + ibmcam_veio(uvd, 0, 0xc0, 0x010c); + ibmcam_veio(uvd, 0, 0x00, 0x010c); + ibmcam_send_FF_04_02(uvd); + ibmcam_veio(uvd, 1, 0x00, 0x0100); + ibmcam_veio(uvd, 0, 0x81, 0x0100); /* LED Off */ + break; + case IBMCAM_MODEL_2: +case IBMCAM_MODEL_4: + ibmcam_veio(uvd, 0, 0x0000, 0x010c); /* Stop the camera */ + + ibmcam_model2_Packet1(uvd, 0x0030, 0x0004); + + ibmcam_veio(uvd, 0, 0x0080, 0x0100); /* LED Off */ + ibmcam_veio(uvd, 0, 0x0020, 0x0111); + ibmcam_veio(uvd, 0, 0x00a0, 0x0111); + + ibmcam_model2_Packet1(uvd, 0x0030, 0x0002); + + ibmcam_veio(uvd, 0, 0x0020, 0x0111); + ibmcam_veio(uvd, 0, 0x0000, 0x0112); + break; + case IBMCAM_MODEL_3: +#if 1 + ibmcam_veio(uvd, 0, 0x0000, 0x010c); + + /* Here we are supposed to select video interface alt. setting 0 */ + ibmcam_veio(uvd, 0, 0x0006, 0x012c); + + ibmcam_model3_Packet1(uvd, 0x0046, 0x0000); + + ibmcam_veio(uvd, 1, 0x0000, 0x0116); + ibmcam_veio(uvd, 0, 0x0064, 0x0116); + ibmcam_veio(uvd, 1, 0x0000, 0x0115); + ibmcam_veio(uvd, 0, 0x0003, 0x0115); + ibmcam_veio(uvd, 0, 0x0008, 0x0123); + ibmcam_veio(uvd, 0, 0x0000, 0x0117); + ibmcam_veio(uvd, 0, 0x0000, 0x0112); + ibmcam_veio(uvd, 0, 0x0080, 0x0100); + IBMCAM_T(uvd)->initialized = 0; +#endif + break; + } /* switch */ +} + +/* + * ibmcam_reinit_iso() + * + * This procedure sends couple of commands to the camera and then + * resets the video pipe. This sequence was observed to reinit the + * camera or, at least, to initiate ISO data stream. + * + * History: + * 1/2/00 Created. + */ +static void ibmcam_reinit_iso(struct uvd *uvd, int do_stop) +{ + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + if (do_stop) + ibmcam_video_stop(uvd); + ibmcam_veio(uvd, 0, 0x0001, 0x0114); + ibmcam_veio(uvd, 0, 0x00c0, 0x010c); + usb_clear_halt(uvd->dev, usb_rcvisocpipe(uvd->dev, uvd->video_endp)); + ibmcam_model1_setup_after_video_if(uvd); + break; + case IBMCAM_MODEL_2: + ibmcam_model2_setup_after_video_if(uvd); + break; + case IBMCAM_MODEL_3: + ibmcam_video_stop(uvd); + ibmcam_model3_setup_after_video_if(uvd); + break; + case IBMCAM_MODEL_4: + ibmcam_model4_setup_after_video_if(uvd); + break; + } +} + +static void ibmcam_video_start(struct uvd *uvd) +{ + ibmcam_change_lighting_conditions(uvd); + ibmcam_set_sharpness(uvd); + ibmcam_reinit_iso(uvd, 0); +} + +/* + * Return negative code on failure, 0 on success. + */ +static int ibmcam_setup_on_open(struct uvd *uvd) +{ + int setup_ok = 0; /* Success by default */ + /* Send init sequence only once, it's large! */ + if (!IBMCAM_T(uvd)->initialized) { /* FIXME rename */ + switch (IBMCAM_T(uvd)->camera_model) { + case IBMCAM_MODEL_1: + setup_ok = ibmcam_model1_setup(uvd); + break; + case IBMCAM_MODEL_2: + setup_ok = ibmcam_model2_setup(uvd); + break; + case IBMCAM_MODEL_3: + case IBMCAM_MODEL_4: + /* We do all setup when Isoc stream is requested */ + break; + } + IBMCAM_T(uvd)->initialized = (setup_ok != 0); + } + return setup_ok; +} + +static void ibmcam_configure_video(struct uvd *uvd) +{ + if (uvd == NULL) + return; + + RESTRICT_TO_RANGE(init_brightness, 0, 255); + RESTRICT_TO_RANGE(init_contrast, 0, 255); + RESTRICT_TO_RANGE(init_color, 0, 255); + RESTRICT_TO_RANGE(init_hue, 0, 255); + RESTRICT_TO_RANGE(hue_correction, 0, 255); + + memset(&uvd->vpic, 0, sizeof(uvd->vpic)); + memset(&uvd->vpic_old, 0x55, sizeof(uvd->vpic_old)); + + uvd->vpic.colour = init_color << 8; + uvd->vpic.hue = init_hue << 8; + uvd->vpic.brightness = init_brightness << 8; + uvd->vpic.contrast = init_contrast << 8; + uvd->vpic.whiteness = 105 << 8; /* This one isn't used */ + uvd->vpic.depth = 24; + uvd->vpic.palette = VIDEO_PALETTE_RGB24; + + memset(&uvd->vcap, 0, sizeof(uvd->vcap)); + strcpy(uvd->vcap.name, "IBM USB Camera"); + uvd->vcap.type = VID_TYPE_CAPTURE; + uvd->vcap.channels = 1; + uvd->vcap.audios = 0; + uvd->vcap.maxwidth = VIDEOSIZE_X(uvd->canvas); + uvd->vcap.maxheight = VIDEOSIZE_Y(uvd->canvas); + uvd->vcap.minwidth = min_canvasWidth; + uvd->vcap.minheight = min_canvasHeight; + + memset(&uvd->vchan, 0, sizeof(uvd->vchan)); + uvd->vchan.flags = 0; + uvd->vchan.tuners = 0; + uvd->vchan.channel = 0; + uvd->vchan.type = VIDEO_TYPE_CAMERA; + strcpy(uvd->vchan.name, "Camera"); +} + +/* + * ibmcam_probe() + * + * This procedure queries device descriptor and accepts the interface + * if it looks like IBM C-it camera. + * + * History: + * 22-Jan-2000 Moved camera init code to ibmcam_open() + * 27=Jan-2000 Changed to use static structures, added locking. + * 24-May-2000 Corrected to prevent race condition (MOD_xxx_USE_COUNT). + * 03-Jul-2000 Fixed endianness bug. + * 12-Nov-2000 Reworked to comply with new probe() signature. + * 23-Jan-2001 Added compatibility with 2.2.x kernels. + */ +static int ibmcam_probe(struct usb_interface *intf, const struct usb_device_id *devid) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct uvd *uvd = NULL; + int ix, i, nas, model=0, canvasX=0, canvasY=0; + int actInterface=-1, inactInterface=-1, maxPS=0; + __u8 ifnum = intf->altsetting->desc.bInterfaceNumber; + unsigned char video_ep = 0; + + if (debug >= 1) + info("ibmcam_probe(%p,%u.)", intf, ifnum); + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + /* Check the version/revision */ + switch (le16_to_cpu(dev->descriptor.bcdDevice)) { + case 0x0002: + if (ifnum != 2) + return -ENODEV; + model = IBMCAM_MODEL_1; + break; + case 0x030A: + if (ifnum != 0) + return -ENODEV; + if ((le16_to_cpu(dev->descriptor.idProduct) == NETCAM_PRODUCT_ID) || + (le16_to_cpu(dev->descriptor.idProduct) == VEO_800D_PRODUCT_ID)) + model = IBMCAM_MODEL_4; + else + model = IBMCAM_MODEL_2; + break; + case 0x0301: + if (ifnum != 0) + return -ENODEV; + model = IBMCAM_MODEL_3; + break; + default: + err("IBM camera with revision 0x%04x is not supported.", + le16_to_cpu(dev->descriptor.bcdDevice)); + return -ENODEV; + } + + /* Print detailed info on what we found so far */ + do { + char *brand = NULL; + switch (le16_to_cpu(dev->descriptor.idProduct)) { + case NETCAM_PRODUCT_ID: + brand = "IBM NetCamera"; + break; + case VEO_800C_PRODUCT_ID: + brand = "Veo Stingray [800C]"; + break; + case VEO_800D_PRODUCT_ID: + brand = "Veo Stingray [800D]"; + break; + case IBMCAM_PRODUCT_ID: + default: + brand = "IBM PC Camera"; /* a.k.a. Xirlink C-It */ + break; + } + info("%s USB camera found (model %d, rev. 0x%04x)", + brand, model, le16_to_cpu(dev->descriptor.bcdDevice)); + } while (0); + + /* Validate found interface: must have one ISO endpoint */ + nas = intf->num_altsetting; + if (debug > 0) + info("Number of alternate settings=%d.", nas); + if (nas < 2) { + err("Too few alternate settings for this camera!"); + return -ENODEV; + } + /* Validate all alternate settings */ + for (ix=0; ix < nas; ix++) { + const struct usb_host_interface *interface; + const struct usb_endpoint_descriptor *endpoint; + + interface = &intf->altsetting[ix]; + i = interface->desc.bAlternateSetting; + if (interface->desc.bNumEndpoints != 1) { + err("Interface %d. has %u. endpoints!", + ifnum, (unsigned)(interface->desc.bNumEndpoints)); + return -ENODEV; + } + endpoint = &interface->endpoint[0].desc; + if (video_ep == 0) + video_ep = endpoint->bEndpointAddress; + else if (video_ep != endpoint->bEndpointAddress) { + err("Alternate settings have different endpoint addresses!"); + return -ENODEV; + } + if ((endpoint->bmAttributes & 0x03) != 0x01) { + err("Interface %d. has non-ISO endpoint!", ifnum); + return -ENODEV; + } + if ((endpoint->bEndpointAddress & 0x80) == 0) { + err("Interface %d. has ISO OUT endpoint!", ifnum); + return -ENODEV; + } + if (le16_to_cpu(endpoint->wMaxPacketSize) == 0) { + if (inactInterface < 0) + inactInterface = i; + else { + err("More than one inactive alt. setting!"); + return -ENODEV; + } + } else { + if (actInterface < 0) { + actInterface = i; + maxPS = le16_to_cpu(endpoint->wMaxPacketSize); + if (debug > 0) + info("Active setting=%d. maxPS=%d.", i, maxPS); + } else + err("More than one active alt. setting! Ignoring #%d.", i); + } + } + if ((maxPS <= 0) || (actInterface < 0) || (inactInterface < 0)) { + err("Failed to recognize the camera!"); + return -ENODEV; + } + + /* Validate options */ + switch (model) { + case IBMCAM_MODEL_1: + RESTRICT_TO_RANGE(lighting, 0, 2); + RESTRICT_TO_RANGE(size, SIZE_128x96, SIZE_352x288); + if (framerate < 0) + framerate = 2; + canvasX = 352; + canvasY = 288; + break; + case IBMCAM_MODEL_2: + RESTRICT_TO_RANGE(lighting, 0, 15); + RESTRICT_TO_RANGE(size, SIZE_176x144, SIZE_352x240); + if (framerate < 0) + framerate = 2; + canvasX = 352; + canvasY = 240; + break; + case IBMCAM_MODEL_3: + RESTRICT_TO_RANGE(lighting, 0, 15); /* FIXME */ + switch (size) { + case SIZE_160x120: + canvasX = 160; + canvasY = 120; + if (framerate < 0) + framerate = 2; + RESTRICT_TO_RANGE(framerate, 0, 5); + break; + default: + info("IBM camera: using 320x240"); + size = SIZE_320x240; + /* No break here */ + case SIZE_320x240: + canvasX = 320; + canvasY = 240; + if (framerate < 0) + framerate = 3; + RESTRICT_TO_RANGE(framerate, 0, 5); + break; + case SIZE_640x480: + canvasX = 640; + canvasY = 480; + framerate = 0; /* Slowest, and maybe even that is too fast */ + break; + } + break; + case IBMCAM_MODEL_4: + RESTRICT_TO_RANGE(lighting, 0, 2); + switch (size) { + case SIZE_128x96: + canvasX = 128; + canvasY = 96; + break; + case SIZE_160x120: + canvasX = 160; + canvasY = 120; + break; + default: + info("IBM NetCamera: using 176x144"); + size = SIZE_176x144; + /* No break here */ + case SIZE_176x144: + canvasX = 176; + canvasY = 144; + break; + case SIZE_320x240: + canvasX = 320; + canvasY = 240; + break; + case SIZE_352x288: + canvasX = 352; + canvasY = 288; + break; + } + break; + default: + err("IBM camera: Model %d. not supported!", model); + return -ENODEV; + } + + uvd = usbvideo_AllocateDevice(cams); + if (uvd != NULL) { + /* Here uvd is a fully allocated uvd object */ + uvd->flags = flags; + uvd->debug = debug; + uvd->dev = dev; + uvd->iface = ifnum; + uvd->ifaceAltInactive = inactInterface; + uvd->ifaceAltActive = actInterface; + uvd->video_endp = video_ep; + uvd->iso_packet_len = maxPS; + uvd->paletteBits = 1L << VIDEO_PALETTE_RGB24; + uvd->defaultPalette = VIDEO_PALETTE_RGB24; + uvd->canvas = VIDEOSIZE(canvasX, canvasY); + uvd->videosize = ibmcam_size_to_videosize(size); + + /* Initialize ibmcam-specific data */ + assert(IBMCAM_T(uvd) != NULL); + IBMCAM_T(uvd)->camera_model = model; + IBMCAM_T(uvd)->initialized = 0; + + ibmcam_configure_video(uvd); + + i = usbvideo_RegisterVideoDevice(uvd); + if (i != 0) { + err("usbvideo_RegisterVideoDevice() failed."); + uvd = NULL; + } + } + usb_set_intfdata (intf, uvd); + return 0; +} + + +static struct usb_device_id id_table[] = { + { USB_DEVICE_VER(IBMCAM_VENDOR_ID, IBMCAM_PRODUCT_ID, 0x0002, 0x0002) }, /* Model 1 */ + { USB_DEVICE_VER(IBMCAM_VENDOR_ID, IBMCAM_PRODUCT_ID, 0x030a, 0x030a) }, /* Model 2 */ + { USB_DEVICE_VER(IBMCAM_VENDOR_ID, IBMCAM_PRODUCT_ID, 0x0301, 0x0301) }, /* Model 3 */ + { USB_DEVICE_VER(IBMCAM_VENDOR_ID, NETCAM_PRODUCT_ID, 0x030a, 0x030a) }, /* Model 4 */ + { USB_DEVICE_VER(IBMCAM_VENDOR_ID, VEO_800C_PRODUCT_ID, 0x030a, 0x030a) }, /* Model 2 */ + { USB_DEVICE_VER(IBMCAM_VENDOR_ID, VEO_800D_PRODUCT_ID, 0x030a, 0x030a) }, /* Model 4 */ + { } /* Terminating entry */ +}; + +/* + * ibmcam_init() + * + * This code is run to initialize the driver. + * + * History: + * 1/27/00 Reworked to use statically allocated ibmcam structures. + * 21/10/00 Completely redesigned to use usbvideo services. + */ +static int __init ibmcam_init(void) +{ + struct usbvideo_cb cbTbl; + memset(&cbTbl, 0, sizeof(cbTbl)); + cbTbl.probe = ibmcam_probe; + cbTbl.setupOnOpen = ibmcam_setup_on_open; + cbTbl.videoStart = ibmcam_video_start; + cbTbl.videoStop = ibmcam_video_stop; + cbTbl.processData = ibmcam_ProcessIsocData; + cbTbl.postProcess = usbvideo_DeinterlaceFrame; + cbTbl.adjustPicture = ibmcam_adjust_picture; + cbTbl.getFPS = ibmcam_calculate_fps; + return usbvideo_register( + &cams, + MAX_IBMCAM, + sizeof(ibmcam_t), + "ibmcam", + &cbTbl, + THIS_MODULE, + id_table); +} + +static void __exit ibmcam_cleanup(void) +{ + usbvideo_Deregister(&cams); +} + +MODULE_DEVICE_TABLE(usb, id_table); + +module_init(ibmcam_init); +module_exit(ibmcam_cleanup); diff --git a/drivers/media/video/usbvideo/konicawc.c b/drivers/media/video/usbvideo/konicawc.c new file mode 100644 index 000000000000..e2ede583518f --- /dev/null +++ b/drivers/media/video/usbvideo/konicawc.c @@ -0,0 +1,978 @@ +/* + * konicawc.c - konica webcam driver + * + * Author: Simon Evans <spse@secret.org.uk> + * + * Copyright (C) 2002 Simon Evans + * + * Licence: GPL + * + * Driver for USB webcams based on Konica chipset. This + * chipset is used in Intel YC76 camera. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/usb_input.h> + +#include "usbvideo.h" + +#define MAX_BRIGHTNESS 108 +#define MAX_CONTRAST 108 +#define MAX_SATURATION 108 +#define MAX_SHARPNESS 108 +#define MAX_WHITEBAL 372 +#define MAX_SPEED 6 + + +#define MAX_CAMERAS 1 + +#define DRIVER_VERSION "v1.4" +#define DRIVER_DESC "Konica Webcam driver" + +enum ctrl_req { + SetWhitebal = 0x01, + SetBrightness = 0x02, + SetSharpness = 0x03, + SetContrast = 0x04, + SetSaturation = 0x05, +}; + + +enum frame_sizes { + SIZE_160X120 = 0, + SIZE_160X136 = 1, + SIZE_176X144 = 2, + SIZE_320X240 = 3, + +}; + +#define MAX_FRAME_SIZE SIZE_320X240 + +static struct usbvideo *cams; + +#ifdef CONFIG_USB_DEBUG +static int debug; +#define DEBUG(n, format, arg...) \ + if (n <= debug) { \ + printk(KERN_DEBUG __FILE__ ":%s(): " format "\n", __FUNCTION__ , ## arg); \ + } +#else +#define DEBUG(n, arg...) +static const int debug = 0; +#endif + + +/* Some default values for initial camera settings, + can be set by modprobe */ + +static int size; +static int speed = 6; /* Speed (fps) 0 (slowest) to 6 (fastest) */ +static int brightness = MAX_BRIGHTNESS/2; +static int contrast = MAX_CONTRAST/2; +static int saturation = MAX_SATURATION/2; +static int sharpness = MAX_SHARPNESS/2; +static int whitebal = 3*(MAX_WHITEBAL/4); + +static const int spd_to_iface[] = { 1, 0, 3, 2, 4, 5, 6 }; + +/* These FPS speeds are from the windows config box. They are + * indexed on size (0-2) and speed (0-6). Divide by 3 to get the + * real fps. + */ + +static const int spd_to_fps[][7] = { { 24, 40, 48, 60, 72, 80, 100 }, + { 24, 40, 48, 60, 72, 80, 100 }, + { 18, 30, 36, 45, 54, 60, 75 }, + { 6, 10, 12, 15, 18, 21, 25 } }; + +struct cam_size { + u16 width; + u16 height; + u8 cmd; +}; + +static const struct cam_size camera_sizes[] = { { 160, 120, 0x7 }, + { 160, 136, 0xa }, + { 176, 144, 0x4 }, + { 320, 240, 0x5 } }; + +struct konicawc { + u8 brightness; /* camera uses 0 - 9, x11 for real value */ + u8 contrast; /* as above */ + u8 saturation; /* as above */ + u8 sharpness; /* as above */ + u8 white_bal; /* 0 - 33, x11 for real value */ + u8 speed; /* Stored as 0 - 6, used as index in spd_to_* (above) */ + u8 size; /* Frame Size */ + int height; + int width; + struct urb *sts_urb[USBVIDEO_NUMSBUF]; + u8 sts_buf[USBVIDEO_NUMSBUF][FRAMES_PER_DESC]; + struct urb *last_data_urb; + int lastframe; + int cur_frame_size; /* number of bytes in current frame size */ + int maxline; /* number of lines per frame */ + int yplanesz; /* Number of bytes in the Y plane */ + unsigned int buttonsts:1; +#ifdef CONFIG_INPUT + struct input_dev *input; + char input_physname[64]; +#endif +}; + + +#define konicawc_set_misc(uvd, req, value, index) konicawc_ctrl_msg(uvd, USB_DIR_OUT, req, value, index, NULL, 0) +#define konicawc_get_misc(uvd, req, value, index, buf, sz) konicawc_ctrl_msg(uvd, USB_DIR_IN, req, value, index, buf, sz) +#define konicawc_set_value(uvd, value, index) konicawc_ctrl_msg(uvd, USB_DIR_OUT, 2, value, index, NULL, 0) + + +static int konicawc_ctrl_msg(struct uvd *uvd, u8 dir, u8 request, u16 value, u16 index, void *buf, int len) +{ + int retval = usb_control_msg(uvd->dev, + dir ? usb_rcvctrlpipe(uvd->dev, 0) : usb_sndctrlpipe(uvd->dev, 0), + request, 0x40 | dir, value, index, buf, len, 1000); + return retval < 0 ? retval : 0; +} + + +static inline void konicawc_camera_on(struct uvd *uvd) +{ + DEBUG(0, "camera on"); + konicawc_set_misc(uvd, 0x2, 1, 0x0b); +} + + +static inline void konicawc_camera_off(struct uvd *uvd) +{ + DEBUG(0, "camera off"); + konicawc_set_misc(uvd, 0x2, 0, 0x0b); +} + + +static void konicawc_set_camera_size(struct uvd *uvd) +{ + struct konicawc *cam = (struct konicawc *)uvd->user_data; + + konicawc_set_misc(uvd, 0x2, camera_sizes[cam->size].cmd, 0x08); + cam->width = camera_sizes[cam->size].width; + cam->height = camera_sizes[cam->size].height; + cam->yplanesz = cam->height * cam->width; + cam->cur_frame_size = (cam->yplanesz * 3) / 2; + cam->maxline = cam->yplanesz / 256; + uvd->videosize = VIDEOSIZE(cam->width, cam->height); +} + + +static int konicawc_setup_on_open(struct uvd *uvd) +{ + struct konicawc *cam = (struct konicawc *)uvd->user_data; + + DEBUG(1, "setting brightness to %d (%d)", cam->brightness, + cam->brightness * 11); + konicawc_set_value(uvd, cam->brightness, SetBrightness); + DEBUG(1, "setting white balance to %d (%d)", cam->white_bal, + cam->white_bal * 11); + konicawc_set_value(uvd, cam->white_bal, SetWhitebal); + DEBUG(1, "setting contrast to %d (%d)", cam->contrast, + cam->contrast * 11); + konicawc_set_value(uvd, cam->contrast, SetContrast); + DEBUG(1, "setting saturation to %d (%d)", cam->saturation, + cam->saturation * 11); + konicawc_set_value(uvd, cam->saturation, SetSaturation); + DEBUG(1, "setting sharpness to %d (%d)", cam->sharpness, + cam->sharpness * 11); + konicawc_set_value(uvd, cam->sharpness, SetSharpness); + konicawc_set_camera_size(uvd); + cam->lastframe = -2; + cam->buttonsts = 0; + return 0; +} + + +static void konicawc_adjust_picture(struct uvd *uvd) +{ + struct konicawc *cam = (struct konicawc *)uvd->user_data; + + konicawc_camera_off(uvd); + DEBUG(1, "new brightness: %d", uvd->vpic.brightness); + uvd->vpic.brightness = (uvd->vpic.brightness > MAX_BRIGHTNESS) ? MAX_BRIGHTNESS : uvd->vpic.brightness; + if(cam->brightness != uvd->vpic.brightness / 11) { + cam->brightness = uvd->vpic.brightness / 11; + DEBUG(1, "setting brightness to %d (%d)", cam->brightness, + cam->brightness * 11); + konicawc_set_value(uvd, cam->brightness, SetBrightness); + } + + DEBUG(1, "new contrast: %d", uvd->vpic.contrast); + uvd->vpic.contrast = (uvd->vpic.contrast > MAX_CONTRAST) ? MAX_CONTRAST : uvd->vpic.contrast; + if(cam->contrast != uvd->vpic.contrast / 11) { + cam->contrast = uvd->vpic.contrast / 11; + DEBUG(1, "setting contrast to %d (%d)", cam->contrast, + cam->contrast * 11); + konicawc_set_value(uvd, cam->contrast, SetContrast); + } + konicawc_camera_on(uvd); +} + +#ifdef CONFIG_INPUT + +static void konicawc_register_input(struct konicawc *cam, struct usb_device *dev) +{ + struct input_dev *input_dev; + + usb_make_path(dev, cam->input_physname, sizeof(cam->input_physname)); + strncat(cam->input_physname, "/input0", sizeof(cam->input_physname)); + + cam->input = input_dev = input_allocate_device(); + if (!input_dev) { + warn("Not enough memory for camera's input device\n"); + return; + } + + input_dev->name = "Konicawc snapshot button"; + input_dev->phys = cam->input_physname; + usb_to_input_id(dev, &input_dev->id); + input_dev->cdev.dev = &dev->dev; + + input_dev->evbit[0] = BIT(EV_KEY); + input_dev->keybit[LONG(BTN_0)] = BIT(BTN_0); + + input_dev->private = cam; + + input_register_device(cam->input); +} + +static void konicawc_unregister_input(struct konicawc *cam) +{ + if (cam->input) { + input_unregister_device(cam->input); + cam->input = NULL; + } +} + +static void konicawc_report_buttonstat(struct konicawc *cam) +{ + if (cam->input) { + input_report_key(cam->input, BTN_0, cam->buttonsts); + input_sync(cam->input); + } +} + +#else + +static inline void konicawc_register_input(struct konicawc *cam, struct usb_device *dev) { } +static inline void konicawc_unregister_input(struct konicawc *cam) { } +static inline void konicawc_report_buttonstat(struct konicawc *cam) { } + +#endif /* CONFIG_INPUT */ + +static int konicawc_compress_iso(struct uvd *uvd, struct urb *dataurb, struct urb *stsurb) +{ + char *cdata; + int i, totlen = 0; + unsigned char *status = stsurb->transfer_buffer; + int keep = 0, discard = 0, bad = 0; + struct konicawc *cam = (struct konicawc *)uvd->user_data; + + for (i = 0; i < dataurb->number_of_packets; i++) { + int button = cam->buttonsts; + unsigned char sts; + int n = dataurb->iso_frame_desc[i].actual_length; + int st = dataurb->iso_frame_desc[i].status; + cdata = dataurb->transfer_buffer + + dataurb->iso_frame_desc[i].offset; + + /* Detect and ignore errored packets */ + if (st < 0) { + DEBUG(1, "Data error: packet=%d. len=%d. status=%d.", + i, n, st); + uvd->stats.iso_err_count++; + continue; + } + + /* Detect and ignore empty packets */ + if (n <= 0) { + uvd->stats.iso_skip_count++; + continue; + } + + /* See what the status data said about the packet */ + sts = *(status+stsurb->iso_frame_desc[i].offset); + + /* sts: 0x80-0xff: frame start with frame number (ie 0-7f) + * otherwise: + * bit 0 0: keep packet + * 1: drop packet (padding data) + * + * bit 4 0 button not clicked + * 1 button clicked + * button is used to `take a picture' (in software) + */ + + if(sts < 0x80) { + button = !!(sts & 0x40); + sts &= ~0x40; + } + + /* work out the button status, but don't do + anything with it for now */ + + if(button != cam->buttonsts) { + DEBUG(2, "button: %sclicked", button ? "" : "un"); + cam->buttonsts = button; + konicawc_report_buttonstat(cam); + } + + if(sts == 0x01) { /* drop frame */ + discard++; + continue; + } + + if((sts > 0x01) && (sts < 0x80)) { + info("unknown status %2.2x", sts); + bad++; + continue; + } + if(!sts && cam->lastframe == -2) { + DEBUG(2, "dropping frame looking for image start"); + continue; + } + + keep++; + if(sts & 0x80) { /* frame start */ + unsigned char marker[] = { 0, 0xff, 0, 0x00 }; + + if(cam->lastframe == -2) { + DEBUG(2, "found initial image"); + cam->lastframe = -1; + } + + marker[3] = sts & 0x7F; + RingQueue_Enqueue(&uvd->dp, marker, 4); + totlen += 4; + } + + totlen += n; /* Little local accounting */ + RingQueue_Enqueue(&uvd->dp, cdata, n); + } + DEBUG(8, "finished: keep = %d discard = %d bad = %d added %d bytes", + keep, discard, bad, totlen); + return totlen; +} + + +static void resubmit_urb(struct uvd *uvd, struct urb *urb) +{ + int i, ret; + for (i = 0; i < FRAMES_PER_DESC; i++) { + urb->iso_frame_desc[i].status = 0; + } + urb->dev = uvd->dev; + urb->status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + DEBUG(3, "submitting urb of length %d", urb->transfer_buffer_length); + if(ret) + err("usb_submit_urb error (%d)", ret); + +} + + +static void konicawc_isoc_irq(struct urb *urb, struct pt_regs *regs) +{ + struct uvd *uvd = urb->context; + struct konicawc *cam = (struct konicawc *)uvd->user_data; + + /* We don't want to do anything if we are about to be removed! */ + if (!CAMERA_IS_OPERATIONAL(uvd)) + return; + + if (!uvd->streaming) { + DEBUG(1, "Not streaming, but interrupt!"); + return; + } + + DEBUG(3, "got frame %d len = %d buflen =%d", urb->start_frame, urb->actual_length, urb->transfer_buffer_length); + + uvd->stats.urb_count++; + + if (urb->transfer_buffer_length > 32) { + cam->last_data_urb = urb; + return; + } + /* Copy the data received into ring queue */ + if(cam->last_data_urb) { + int len = 0; + if(urb->start_frame != cam->last_data_urb->start_frame) + err("Lost sync on frames"); + else if (!urb->status && !cam->last_data_urb->status) + len = konicawc_compress_iso(uvd, cam->last_data_urb, urb); + + resubmit_urb(uvd, cam->last_data_urb); + resubmit_urb(uvd, urb); + cam->last_data_urb = NULL; + uvd->stats.urb_length = len; + uvd->stats.data_count += len; + if(len) + RingQueue_WakeUpInterruptible(&uvd->dp); + return; + } + return; +} + + +static int konicawc_start_data(struct uvd *uvd) +{ + struct usb_device *dev = uvd->dev; + int i, errFlag; + struct konicawc *cam = (struct konicawc *)uvd->user_data; + int pktsz; + struct usb_interface *intf; + struct usb_host_interface *interface = NULL; + + intf = usb_ifnum_to_if(dev, uvd->iface); + if (intf) + interface = usb_altnum_to_altsetting(intf, + spd_to_iface[cam->speed]); + if (!interface) + return -ENXIO; + pktsz = le16_to_cpu(interface->endpoint[1].desc.wMaxPacketSize); + DEBUG(1, "pktsz = %d", pktsz); + if (!CAMERA_IS_OPERATIONAL(uvd)) { + err("Camera is not operational"); + return -EFAULT; + } + uvd->curframe = -1; + konicawc_camera_on(uvd); + /* Alternate interface 1 is is the biggest frame size */ + i = usb_set_interface(dev, uvd->iface, uvd->ifaceAltActive); + if (i < 0) { + err("usb_set_interface error"); + uvd->last_error = i; + return -EBUSY; + } + + /* We double buffer the Iso lists */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + int j, k; + struct urb *urb = uvd->sbuf[i].urb; + urb->dev = dev; + urb->context = uvd; + urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp); + urb->interval = 1; + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = uvd->sbuf[i].data; + urb->complete = konicawc_isoc_irq; + urb->number_of_packets = FRAMES_PER_DESC; + urb->transfer_buffer_length = pktsz * FRAMES_PER_DESC; + for (j=k=0; j < FRAMES_PER_DESC; j++, k += pktsz) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = pktsz; + } + + urb = cam->sts_urb[i]; + urb->dev = dev; + urb->context = uvd; + urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1); + urb->interval = 1; + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = cam->sts_buf[i]; + urb->complete = konicawc_isoc_irq; + urb->number_of_packets = FRAMES_PER_DESC; + urb->transfer_buffer_length = FRAMES_PER_DESC; + for (j=0; j < FRAMES_PER_DESC; j++) { + urb->iso_frame_desc[j].offset = j; + urb->iso_frame_desc[j].length = 1; + } + } + + cam->last_data_urb = NULL; + + /* Submit all URBs */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + errFlag = usb_submit_urb(cam->sts_urb[i], GFP_KERNEL); + if (errFlag) + err("usb_submit_isoc(%d) ret %d", i, errFlag); + + errFlag = usb_submit_urb(uvd->sbuf[i].urb, GFP_KERNEL); + if (errFlag) + err ("usb_submit_isoc(%d) ret %d", i, errFlag); + } + + uvd->streaming = 1; + DEBUG(1, "streaming=1 video_endp=$%02x", uvd->video_endp); + return 0; +} + + +static void konicawc_stop_data(struct uvd *uvd) +{ + int i, j; + struct konicawc *cam; + + if ((uvd == NULL) || (!uvd->streaming) || (uvd->dev == NULL)) + return; + + konicawc_camera_off(uvd); + uvd->streaming = 0; + cam = (struct konicawc *)uvd->user_data; + cam->last_data_urb = NULL; + + /* Unschedule all of the iso td's */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + usb_kill_urb(uvd->sbuf[i].urb); + usb_kill_urb(cam->sts_urb[i]); + } + + if (!uvd->remove_pending) { + /* Set packet size to 0 */ + j = usb_set_interface(uvd->dev, uvd->iface, uvd->ifaceAltInactive); + if (j < 0) { + err("usb_set_interface() error %d.", j); + uvd->last_error = j; + } + } +} + + +static void konicawc_process_isoc(struct uvd *uvd, struct usbvideo_frame *frame) +{ + struct konicawc *cam = (struct konicawc *)uvd->user_data; + int maxline = cam->maxline; + int yplanesz = cam->yplanesz; + + assert(frame != NULL); + + DEBUG(5, "maxline = %d yplanesz = %d", maxline, yplanesz); + DEBUG(3, "Frame state = %d", frame->scanstate); + + if(frame->scanstate == ScanState_Scanning) { + int drop = 0; + int curframe; + int fdrops = 0; + DEBUG(3, "Searching for marker, queue len = %d", RingQueue_GetLength(&uvd->dp)); + while(RingQueue_GetLength(&uvd->dp) >= 4) { + if ((RING_QUEUE_PEEK(&uvd->dp, 0) == 0x00) && + (RING_QUEUE_PEEK(&uvd->dp, 1) == 0xff) && + (RING_QUEUE_PEEK(&uvd->dp, 2) == 0x00) && + (RING_QUEUE_PEEK(&uvd->dp, 3) < 0x80)) { + curframe = RING_QUEUE_PEEK(&uvd->dp, 3); + if(cam->lastframe >= 0) { + fdrops = (0x80 + curframe - cam->lastframe) & 0x7F; + fdrops--; + if(fdrops) { + info("Dropped %d frames (%d -> %d)", fdrops, + cam->lastframe, curframe); + } + } + cam->lastframe = curframe; + frame->curline = 0; + frame->scanstate = ScanState_Lines; + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, 4); + break; + } + RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, 1); + drop++; + } + if(drop) + DEBUG(2, "dropped %d bytes looking for new frame", drop); + } + + if(frame->scanstate == ScanState_Scanning) + return; + + /* Try to move data from queue into frame buffer + * We get data in blocks of 384 bytes made up of: + * 256 Y, 64 U, 64 V. + * This needs to be written out as a Y plane, a U plane and a V plane. + */ + + while ( frame->curline < maxline && (RingQueue_GetLength(&uvd->dp) >= 384)) { + /* Y */ + RingQueue_Dequeue(&uvd->dp, frame->data + (frame->curline * 256), 256); + /* U */ + RingQueue_Dequeue(&uvd->dp, frame->data + yplanesz + (frame->curline * 64), 64); + /* V */ + RingQueue_Dequeue(&uvd->dp, frame->data + (5 * yplanesz)/4 + (frame->curline * 64), 64); + frame->seqRead_Length += 384; + frame->curline++; + } + /* See if we filled the frame */ + if (frame->curline == maxline) { + DEBUG(5, "got whole frame"); + + frame->frameState = FrameState_Done_Hold; + frame->curline = 0; + uvd->curframe = -1; + uvd->stats.frame_num++; + } +} + + +static int konicawc_find_fps(int size, int fps) +{ + int i; + + fps *= 3; + DEBUG(1, "konica_find_fps: size = %d fps = %d", size, fps); + if(fps <= spd_to_fps[size][0]) + return 0; + + if(fps >= spd_to_fps[size][MAX_SPEED]) + return MAX_SPEED; + + for(i = 0; i < MAX_SPEED; i++) { + if((fps >= spd_to_fps[size][i]) && (fps <= spd_to_fps[size][i+1])) { + DEBUG(2, "fps %d between %d and %d", fps, i, i+1); + if( (fps - spd_to_fps[size][i]) < (spd_to_fps[size][i+1] - fps)) + return i; + else + return i+1; + } + } + return MAX_SPEED+1; +} + + +static int konicawc_set_video_mode(struct uvd *uvd, struct video_window *vw) +{ + struct konicawc *cam = (struct konicawc *)uvd->user_data; + int newspeed = cam->speed; + int newsize; + int x = vw->width; + int y = vw->height; + int fps = vw->flags; + + if(x > 0 && y > 0) { + DEBUG(2, "trying to find size %d,%d", x, y); + for(newsize = 0; newsize <= MAX_FRAME_SIZE; newsize++) { + if((camera_sizes[newsize].width == x) && (camera_sizes[newsize].height == y)) + break; + } + } else { + newsize = cam->size; + } + + if(newsize > MAX_FRAME_SIZE) { + DEBUG(1, "couldn't find size %d,%d", x, y); + return -EINVAL; + } + + if(fps > 0) { + DEBUG(1, "trying to set fps to %d", fps); + newspeed = konicawc_find_fps(newsize, fps); + DEBUG(1, "find_fps returned %d (%d)", newspeed, spd_to_fps[newsize][newspeed]); + } + + if(newspeed > MAX_SPEED) + return -EINVAL; + + DEBUG(1, "setting size to %d speed to %d", newsize, newspeed); + if((newsize == cam->size) && (newspeed == cam->speed)) { + DEBUG(1, "Nothing to do"); + return 0; + } + DEBUG(0, "setting to %dx%d @ %d fps", camera_sizes[newsize].width, + camera_sizes[newsize].height, spd_to_fps[newsize][newspeed]/3); + + konicawc_stop_data(uvd); + uvd->ifaceAltActive = spd_to_iface[newspeed]; + DEBUG(1, "new interface = %d", uvd->ifaceAltActive); + cam->speed = newspeed; + + if(cam->size != newsize) { + cam->size = newsize; + konicawc_set_camera_size(uvd); + } + + /* Flush the input queue and clear any current frame in progress */ + + RingQueue_Flush(&uvd->dp); + cam->lastframe = -2; + if(uvd->curframe != -1) { + uvd->frame[uvd->curframe].curline = 0; + uvd->frame[uvd->curframe].seqRead_Length = 0; + uvd->frame[uvd->curframe].seqRead_Index = 0; + } + + konicawc_start_data(uvd); + return 0; +} + + +static int konicawc_calculate_fps(struct uvd *uvd) +{ + struct konicawc *cam = uvd->user_data; + return spd_to_fps[cam->size][cam->speed]/3; +} + + +static void konicawc_configure_video(struct uvd *uvd) +{ + struct konicawc *cam = (struct konicawc *)uvd->user_data; + u8 buf[2]; + + memset(&uvd->vpic, 0, sizeof(uvd->vpic)); + memset(&uvd->vpic_old, 0x55, sizeof(uvd->vpic_old)); + + RESTRICT_TO_RANGE(brightness, 0, MAX_BRIGHTNESS); + RESTRICT_TO_RANGE(contrast, 0, MAX_CONTRAST); + RESTRICT_TO_RANGE(saturation, 0, MAX_SATURATION); + RESTRICT_TO_RANGE(sharpness, 0, MAX_SHARPNESS); + RESTRICT_TO_RANGE(whitebal, 0, MAX_WHITEBAL); + + cam->brightness = brightness / 11; + cam->contrast = contrast / 11; + cam->saturation = saturation / 11; + cam->sharpness = sharpness / 11; + cam->white_bal = whitebal / 11; + + uvd->vpic.colour = 108; + uvd->vpic.hue = 108; + uvd->vpic.brightness = brightness; + uvd->vpic.contrast = contrast; + uvd->vpic.whiteness = whitebal; + uvd->vpic.depth = 6; + uvd->vpic.palette = VIDEO_PALETTE_YUV420P; + + memset(&uvd->vcap, 0, sizeof(uvd->vcap)); + strcpy(uvd->vcap.name, "Konica Webcam"); + uvd->vcap.type = VID_TYPE_CAPTURE; + uvd->vcap.channels = 1; + uvd->vcap.audios = 0; + uvd->vcap.minwidth = camera_sizes[SIZE_160X120].width; + uvd->vcap.minheight = camera_sizes[SIZE_160X120].height; + uvd->vcap.maxwidth = camera_sizes[SIZE_320X240].width; + uvd->vcap.maxheight = camera_sizes[SIZE_320X240].height; + + memset(&uvd->vchan, 0, sizeof(uvd->vchan)); + uvd->vchan.flags = 0 ; + uvd->vchan.tuners = 0; + uvd->vchan.channel = 0; + uvd->vchan.type = VIDEO_TYPE_CAMERA; + strcpy(uvd->vchan.name, "Camera"); + + /* Talk to device */ + DEBUG(1, "device init"); + if(!konicawc_get_misc(uvd, 0x3, 0, 0x10, buf, 2)) + DEBUG(2, "3,10 -> %2.2x %2.2x", buf[0], buf[1]); + if(!konicawc_get_misc(uvd, 0x3, 0, 0x10, buf, 2)) + DEBUG(2, "3,10 -> %2.2x %2.2x", buf[0], buf[1]); + if(konicawc_set_misc(uvd, 0x2, 0, 0xd)) + DEBUG(2, "2,0,d failed"); + DEBUG(1, "setting initial values"); +} + +static int konicawc_probe(struct usb_interface *intf, const struct usb_device_id *devid) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct uvd *uvd = NULL; + int ix, i, nas; + int actInterface=-1, inactInterface=-1, maxPS=0; + unsigned char video_ep = 0; + + DEBUG(1, "konicawc_probe(%p)", intf); + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + info("Konica Webcam (rev. 0x%04x)", le16_to_cpu(dev->descriptor.bcdDevice)); + RESTRICT_TO_RANGE(speed, 0, MAX_SPEED); + + /* Validate found interface: must have one ISO endpoint */ + nas = intf->num_altsetting; + if (nas != 8) { + err("Incorrect number of alternate settings (%d) for this camera!", nas); + return -ENODEV; + } + /* Validate all alternate settings */ + for (ix=0; ix < nas; ix++) { + const struct usb_host_interface *interface; + const struct usb_endpoint_descriptor *endpoint; + + interface = &intf->altsetting[ix]; + i = interface->desc.bAlternateSetting; + if (interface->desc.bNumEndpoints != 2) { + err("Interface %d. has %u. endpoints!", + interface->desc.bInterfaceNumber, + (unsigned)(interface->desc.bNumEndpoints)); + return -ENODEV; + } + endpoint = &interface->endpoint[1].desc; + DEBUG(1, "found endpoint: addr: 0x%2.2x maxps = 0x%4.4x", + endpoint->bEndpointAddress, le16_to_cpu(endpoint->wMaxPacketSize)); + if (video_ep == 0) + video_ep = endpoint->bEndpointAddress; + else if (video_ep != endpoint->bEndpointAddress) { + err("Alternate settings have different endpoint addresses!"); + return -ENODEV; + } + if ((endpoint->bmAttributes & 0x03) != 0x01) { + err("Interface %d. has non-ISO endpoint!", + interface->desc.bInterfaceNumber); + return -ENODEV; + } + if ((endpoint->bEndpointAddress & 0x80) == 0) { + err("Interface %d. has ISO OUT endpoint!", + interface->desc.bInterfaceNumber); + return -ENODEV; + } + if (le16_to_cpu(endpoint->wMaxPacketSize) == 0) { + if (inactInterface < 0) + inactInterface = i; + else { + err("More than one inactive alt. setting!"); + return -ENODEV; + } + } else { + if (i == spd_to_iface[speed]) { + /* This one is the requested one */ + actInterface = i; + } + } + if (le16_to_cpu(endpoint->wMaxPacketSize) > maxPS) + maxPS = le16_to_cpu(endpoint->wMaxPacketSize); + } + if(actInterface == -1) { + err("Cant find required endpoint"); + return -ENODEV; + } + + DEBUG(1, "Selecting requested active setting=%d. maxPS=%d.", actInterface, maxPS); + + uvd = usbvideo_AllocateDevice(cams); + if (uvd != NULL) { + struct konicawc *cam = (struct konicawc *)(uvd->user_data); + /* Here uvd is a fully allocated uvd object */ + for(i = 0; i < USBVIDEO_NUMSBUF; i++) { + cam->sts_urb[i] = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL); + if(cam->sts_urb[i] == NULL) { + while(i--) { + usb_free_urb(cam->sts_urb[i]); + } + err("can't allocate urbs"); + return -ENOMEM; + } + } + cam->speed = speed; + RESTRICT_TO_RANGE(size, SIZE_160X120, SIZE_320X240); + cam->width = camera_sizes[size].width; + cam->height = camera_sizes[size].height; + cam->size = size; + + uvd->flags = 0; + uvd->debug = debug; + uvd->dev = dev; + uvd->iface = intf->altsetting->desc.bInterfaceNumber; + uvd->ifaceAltInactive = inactInterface; + uvd->ifaceAltActive = actInterface; + uvd->video_endp = video_ep; + uvd->iso_packet_len = maxPS; + uvd->paletteBits = 1L << VIDEO_PALETTE_YUV420P; + uvd->defaultPalette = VIDEO_PALETTE_YUV420P; + uvd->canvas = VIDEOSIZE(320, 240); + uvd->videosize = VIDEOSIZE(cam->width, cam->height); + + /* Initialize konicawc specific data */ + konicawc_configure_video(uvd); + + i = usbvideo_RegisterVideoDevice(uvd); + uvd->max_frame_size = (320 * 240 * 3)/2; + if (i != 0) { + err("usbvideo_RegisterVideoDevice() failed."); + uvd = NULL; + } + + konicawc_register_input(cam, dev); + } + + if (uvd) { + usb_set_intfdata (intf, uvd); + return 0; + } + return -EIO; +} + + +static void konicawc_free_uvd(struct uvd *uvd) +{ + int i; + struct konicawc *cam = (struct konicawc *)uvd->user_data; + + konicawc_unregister_input(cam); + + for (i = 0; i < USBVIDEO_NUMSBUF; i++) { + usb_free_urb(cam->sts_urb[i]); + cam->sts_urb[i] = NULL; + } +} + + +static struct usb_device_id id_table[] = { + { USB_DEVICE(0x04c8, 0x0720) }, /* Intel YC 76 */ + { } /* Terminating entry */ +}; + + +static int __init konicawc_init(void) +{ + struct usbvideo_cb cbTbl; + info(DRIVER_DESC " " DRIVER_VERSION); + memset(&cbTbl, 0, sizeof(cbTbl)); + cbTbl.probe = konicawc_probe; + cbTbl.setupOnOpen = konicawc_setup_on_open; + cbTbl.processData = konicawc_process_isoc; + cbTbl.getFPS = konicawc_calculate_fps; + cbTbl.setVideoMode = konicawc_set_video_mode; + cbTbl.startDataPump = konicawc_start_data; + cbTbl.stopDataPump = konicawc_stop_data; + cbTbl.adjustPicture = konicawc_adjust_picture; + cbTbl.userFree = konicawc_free_uvd; + return usbvideo_register( + &cams, + MAX_CAMERAS, + sizeof(struct konicawc), + "konicawc", + &cbTbl, + THIS_MODULE, + id_table); +} + + +static void __exit konicawc_cleanup(void) +{ + usbvideo_Deregister(&cams); +} + + +MODULE_DEVICE_TABLE(usb, id_table); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Simon Evans <spse@secret.org.uk>"); +MODULE_DESCRIPTION(DRIVER_DESC); +module_param(speed, int, 0); +MODULE_PARM_DESC(speed, "Initial speed: 0 (slowest) - 6 (fastest)"); +module_param(size, int, 0); +MODULE_PARM_DESC(size, "Initial Size 0: 160x120 1: 160x136 2: 176x144 3: 320x240"); +module_param(brightness, int, 0); +MODULE_PARM_DESC(brightness, "Initial brightness 0 - 108"); +module_param(contrast, int, 0); +MODULE_PARM_DESC(contrast, "Initial contrast 0 - 108"); +module_param(saturation, int, 0); +MODULE_PARM_DESC(saturation, "Initial saturation 0 - 108"); +module_param(sharpness, int, 0); +MODULE_PARM_DESC(sharpness, "Initial brightness 0 - 108"); +module_param(whitebal, int, 0); +MODULE_PARM_DESC(whitebal, "Initial white balance 0 - 363"); + +#ifdef CONFIG_USB_DEBUG +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level: 0-9 (default=0)"); +#endif + +module_init(konicawc_init); +module_exit(konicawc_cleanup); diff --git a/drivers/media/video/usbvideo/ultracam.c b/drivers/media/video/usbvideo/ultracam.c new file mode 100644 index 000000000000..75ff755224df --- /dev/null +++ b/drivers/media/video/usbvideo/ultracam.c @@ -0,0 +1,679 @@ +/* + * USB NB Camera driver + * + * HISTORY: + * 25-Dec-2002 Dmitri Removed lighting, sharpness parameters, methods. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/init.h> + +#include "usbvideo.h" + +#define ULTRACAM_VENDOR_ID 0x0461 +#define ULTRACAM_PRODUCT_ID 0x0813 + +#define MAX_CAMERAS 4 /* How many devices we allow to connect */ + +/* + * This structure lives in uvd_t->user field. + */ +typedef struct { + int initialized; /* Had we already sent init sequence? */ + int camera_model; /* What type of IBM camera we got? */ + int has_hdr; +} ultracam_t; +#define ULTRACAM_T(uvd) ((ultracam_t *)((uvd)->user_data)) + +static struct usbvideo *cams = NULL; + +static int debug = 0; + +static int flags = 0; /* FLAGS_DISPLAY_HINTS | FLAGS_OVERLAY_STATS; */ + +static const int min_canvasWidth = 8; +static const int min_canvasHeight = 4; + +#define FRAMERATE_MIN 0 +#define FRAMERATE_MAX 6 +static int framerate = -1; + +/* + * Here we define several initialization variables. They may + * be used to automatically set color, hue, brightness and + * contrast to desired values. This is particularly useful in + * case of webcams (which have no controls and no on-screen + * output) and also when a client V4L software is used that + * does not have some of those controls. In any case it's + * good to have startup values as options. + * + * These values are all in [0..255] range. This simplifies + * operation. Note that actual values of V4L variables may + * be scaled up (as much as << 8). User can see that only + * on overlay output, however, or through a V4L client. + */ +static int init_brightness = 128; +static int init_contrast = 192; +static int init_color = 128; +static int init_hue = 128; +static int hue_correction = 128; + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level: 0-9 (default=0)"); +module_param(flags, int, 0); +MODULE_PARM_DESC(flags, + "Bitfield: 0=VIDIOCSYNC, " + "1=B/W, " + "2=show hints, " + "3=show stats, " + "4=test pattern, " + "5=separate frames, " + "6=clean frames"); +module_param(framerate, int, 0); +MODULE_PARM_DESC(framerate, "Framerate setting: 0=slowest, 6=fastest (default=2)"); + +module_param(init_brightness, int, 0); +MODULE_PARM_DESC(init_brightness, "Brightness preconfiguration: 0-255 (default=128)"); +module_param(init_contrast, int, 0); +MODULE_PARM_DESC(init_contrast, "Contrast preconfiguration: 0-255 (default=192)"); +module_param(init_color, int, 0); +MODULE_PARM_DESC(init_color, "Color preconfiguration: 0-255 (default=128)"); +module_param(init_hue, int, 0); +MODULE_PARM_DESC(init_hue, "Hue preconfiguration: 0-255 (default=128)"); +module_param(hue_correction, int, 0); +MODULE_PARM_DESC(hue_correction, "YUV colorspace regulation: 0-255 (default=128)"); + +/* + * ultracam_ProcessIsocData() + * + * Generic routine to parse the ring queue data. It employs either + * ultracam_find_header() or ultracam_parse_lines() to do most + * of work. + * + * 02-Nov-2000 First (mostly dummy) version. + * 06-Nov-2000 Rewrote to dump all data into frame. + */ +static void ultracam_ProcessIsocData(struct uvd *uvd, struct usbvideo_frame *frame) +{ + int n; + + assert(uvd != NULL); + assert(frame != NULL); + + /* Try to move data from queue into frame buffer */ + n = RingQueue_GetLength(&uvd->dp); + if (n > 0) { + int m; + /* See how much spare we have left */ + m = uvd->max_frame_size - frame->seqRead_Length; + if (n > m) + n = m; + /* Now move that much data into frame buffer */ + RingQueue_Dequeue( + &uvd->dp, + frame->data + frame->seqRead_Length, + m); + frame->seqRead_Length += m; + } + /* See if we filled the frame */ + if (frame->seqRead_Length >= uvd->max_frame_size) { + frame->frameState = FrameState_Done; + uvd->curframe = -1; + uvd->stats.frame_num++; + } +} + +/* + * ultracam_veio() + * + * History: + * 1/27/00 Added check for dev == NULL; this happens if camera is unplugged. + */ +static int ultracam_veio( + struct uvd *uvd, + unsigned char req, + unsigned short value, + unsigned short index, + int is_out) +{ + static const char proc[] = "ultracam_veio"; + unsigned char cp[8] /* = { 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef } */; + int i; + + if (!CAMERA_IS_OPERATIONAL(uvd)) + return 0; + + if (!is_out) { + i = usb_control_msg( + uvd->dev, + usb_rcvctrlpipe(uvd->dev, 0), + req, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + cp, + sizeof(cp), + 1000); +#if 1 + info("USB => %02x%02x%02x%02x%02x%02x%02x%02x " + "(req=$%02x val=$%04x ind=$%04x)", + cp[0],cp[1],cp[2],cp[3],cp[4],cp[5],cp[6],cp[7], + req, value, index); +#endif + } else { + i = usb_control_msg( + uvd->dev, + usb_sndctrlpipe(uvd->dev, 0), + req, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + NULL, + 0, + 1000); + } + if (i < 0) { + err("%s: ERROR=%d. Camera stopped; Reconnect or reload driver.", + proc, i); + uvd->last_error = i; + } + return i; +} + +/* + * ultracam_calculate_fps() + */ +static int ultracam_calculate_fps(struct uvd *uvd) +{ + return 3 + framerate*4 + framerate/2; +} + +/* + * ultracam_adjust_contrast() + */ +static void ultracam_adjust_contrast(struct uvd *uvd) +{ +} + +/* + * ultracam_set_brightness() + * + * This procedure changes brightness of the picture. + */ +static void ultracam_set_brightness(struct uvd *uvd) +{ +} + +static void ultracam_set_hue(struct uvd *uvd) +{ +} + +/* + * ultracam_adjust_picture() + * + * This procedure gets called from V4L interface to update picture settings. + * Here we change brightness and contrast. + */ +static void ultracam_adjust_picture(struct uvd *uvd) +{ + ultracam_adjust_contrast(uvd); + ultracam_set_brightness(uvd); + ultracam_set_hue(uvd); +} + +/* + * ultracam_video_stop() + * + * This code tells camera to stop streaming. The interface remains + * configured and bandwidth - claimed. + */ +static void ultracam_video_stop(struct uvd *uvd) +{ +} + +/* + * ultracam_reinit_iso() + * + * This procedure sends couple of commands to the camera and then + * resets the video pipe. This sequence was observed to reinit the + * camera or, at least, to initiate ISO data stream. + */ +static void ultracam_reinit_iso(struct uvd *uvd, int do_stop) +{ +} + +static void ultracam_video_start(struct uvd *uvd) +{ + ultracam_reinit_iso(uvd, 0); +} + +static int ultracam_resetPipe(struct uvd *uvd) +{ + usb_clear_halt(uvd->dev, uvd->video_endp); + return 0; +} + +static int ultracam_alternateSetting(struct uvd *uvd, int setting) +{ + static const char proc[] = "ultracam_alternateSetting"; + int i; + i = usb_set_interface(uvd->dev, uvd->iface, setting); + if (i < 0) { + err("%s: usb_set_interface error", proc); + uvd->last_error = i; + return -EBUSY; + } + return 0; +} + +/* + * Return negative code on failure, 0 on success. + */ +static int ultracam_setup_on_open(struct uvd *uvd) +{ + int setup_ok = 0; /* Success by default */ + /* Send init sequence only once, it's large! */ + if (!ULTRACAM_T(uvd)->initialized) { + ultracam_alternateSetting(uvd, 0x04); + ultracam_alternateSetting(uvd, 0x00); + ultracam_veio(uvd, 0x02, 0x0004, 0x000b, 1); + ultracam_veio(uvd, 0x02, 0x0001, 0x0005, 1); + ultracam_veio(uvd, 0x02, 0x8000, 0x0000, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0000, 1); + ultracam_veio(uvd, 0x00, 0x00b0, 0x0001, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0002, 1); + ultracam_veio(uvd, 0x00, 0x000c, 0x0003, 1); + ultracam_veio(uvd, 0x00, 0x000b, 0x0004, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0005, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0006, 1); + ultracam_veio(uvd, 0x00, 0x0079, 0x0007, 1); + ultracam_veio(uvd, 0x00, 0x003b, 0x0008, 1); + ultracam_veio(uvd, 0x00, 0x0002, 0x000f, 1); + ultracam_veio(uvd, 0x00, 0x0001, 0x0010, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0011, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00bf, 1); + ultracam_veio(uvd, 0x00, 0x0001, 0x00c0, 1); + ultracam_veio(uvd, 0x00, 0x0010, 0x00cb, 1); + ultracam_veio(uvd, 0x01, 0x00a4, 0x0001, 1); + ultracam_veio(uvd, 0x01, 0x0010, 0x0002, 1); + ultracam_veio(uvd, 0x01, 0x0066, 0x0007, 1); + ultracam_veio(uvd, 0x01, 0x000b, 0x0008, 1); + ultracam_veio(uvd, 0x01, 0x0034, 0x0009, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x000a, 1); + ultracam_veio(uvd, 0x01, 0x002e, 0x000b, 1); + ultracam_veio(uvd, 0x01, 0x00d6, 0x000c, 1); + ultracam_veio(uvd, 0x01, 0x00fc, 0x000d, 1); + ultracam_veio(uvd, 0x01, 0x00f1, 0x000e, 1); + ultracam_veio(uvd, 0x01, 0x00da, 0x000f, 1); + ultracam_veio(uvd, 0x01, 0x0036, 0x0010, 1); + ultracam_veio(uvd, 0x01, 0x000b, 0x0011, 1); + ultracam_veio(uvd, 0x01, 0x0001, 0x0012, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x0013, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x0014, 1); + ultracam_veio(uvd, 0x01, 0x0087, 0x0051, 1); + ultracam_veio(uvd, 0x01, 0x0040, 0x0052, 1); + ultracam_veio(uvd, 0x01, 0x0058, 0x0053, 1); + ultracam_veio(uvd, 0x01, 0x0040, 0x0054, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x0040, 1); + ultracam_veio(uvd, 0x01, 0x0010, 0x0041, 1); + ultracam_veio(uvd, 0x01, 0x0020, 0x0042, 1); + ultracam_veio(uvd, 0x01, 0x0030, 0x0043, 1); + ultracam_veio(uvd, 0x01, 0x0040, 0x0044, 1); + ultracam_veio(uvd, 0x01, 0x0050, 0x0045, 1); + ultracam_veio(uvd, 0x01, 0x0060, 0x0046, 1); + ultracam_veio(uvd, 0x01, 0x0070, 0x0047, 1); + ultracam_veio(uvd, 0x01, 0x0080, 0x0048, 1); + ultracam_veio(uvd, 0x01, 0x0090, 0x0049, 1); + ultracam_veio(uvd, 0x01, 0x00a0, 0x004a, 1); + ultracam_veio(uvd, 0x01, 0x00b0, 0x004b, 1); + ultracam_veio(uvd, 0x01, 0x00c0, 0x004c, 1); + ultracam_veio(uvd, 0x01, 0x00d0, 0x004d, 1); + ultracam_veio(uvd, 0x01, 0x00e0, 0x004e, 1); + ultracam_veio(uvd, 0x01, 0x00f0, 0x004f, 1); + ultracam_veio(uvd, 0x01, 0x00ff, 0x0050, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x0056, 1); + ultracam_veio(uvd, 0x00, 0x0080, 0x00c1, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c2, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00ca, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0080, 0x00c1, 1); + ultracam_veio(uvd, 0x00, 0x0004, 0x00c2, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00ca, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0002, 0x00c1, 1); + ultracam_veio(uvd, 0x00, 0x0020, 0x00c2, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00ca, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c3, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c4, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c5, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c6, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c7, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c8, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c3, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c4, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c5, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c6, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c7, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c8, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0040, 0x00c1, 1); + ultracam_veio(uvd, 0x00, 0x0017, 0x00c2, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00ca, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c3, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c4, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c5, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c6, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c7, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c8, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c3, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c4, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c5, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c6, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c7, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c8, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c9, 1); + ultracam_veio(uvd, 0x00, 0x00c0, 0x00c1, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00c2, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00ca, 1); + ultracam_veio(uvd, 0x02, 0xc040, 0x0001, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x0008, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x0009, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x000a, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x000b, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x000c, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x000d, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x000e, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x000f, 0); + ultracam_veio(uvd, 0x01, 0x0000, 0x0010, 0); + ultracam_veio(uvd, 0x01, 0x000b, 0x0008, 1); + ultracam_veio(uvd, 0x01, 0x0034, 0x0009, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x000a, 1); + ultracam_veio(uvd, 0x01, 0x002e, 0x000b, 1); + ultracam_veio(uvd, 0x01, 0x00d6, 0x000c, 1); + ultracam_veio(uvd, 0x01, 0x00fc, 0x000d, 1); + ultracam_veio(uvd, 0x01, 0x00f1, 0x000e, 1); + ultracam_veio(uvd, 0x01, 0x00da, 0x000f, 1); + ultracam_veio(uvd, 0x01, 0x0036, 0x0010, 1); + ultracam_veio(uvd, 0x01, 0x0000, 0x0001, 0); + ultracam_veio(uvd, 0x01, 0x0064, 0x0001, 1); + ultracam_veio(uvd, 0x01, 0x0059, 0x0051, 1); + ultracam_veio(uvd, 0x01, 0x003f, 0x0052, 1); + ultracam_veio(uvd, 0x01, 0x0094, 0x0053, 1); + ultracam_veio(uvd, 0x01, 0x00ff, 0x0011, 1); + ultracam_veio(uvd, 0x01, 0x0003, 0x0012, 1); + ultracam_veio(uvd, 0x01, 0x00f7, 0x0013, 1); + ultracam_veio(uvd, 0x00, 0x0009, 0x0011, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0001, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x0000, 1); + ultracam_veio(uvd, 0x00, 0x0020, 0x00c1, 1); + ultracam_veio(uvd, 0x00, 0x0010, 0x00c2, 1); + ultracam_veio(uvd, 0x00, 0x0000, 0x00ca, 1); + ultracam_alternateSetting(uvd, 0x04); + ultracam_veio(uvd, 0x02, 0x0000, 0x0001, 1); + ultracam_veio(uvd, 0x02, 0x0000, 0x0001, 1); + ultracam_veio(uvd, 0x02, 0x0000, 0x0006, 1); + ultracam_veio(uvd, 0x02, 0x9000, 0x0007, 1); + ultracam_veio(uvd, 0x02, 0x0042, 0x0001, 1); + ultracam_veio(uvd, 0x02, 0x0000, 0x000b, 0); + ultracam_resetPipe(uvd); + ULTRACAM_T(uvd)->initialized = (setup_ok != 0); + } + return setup_ok; +} + +static void ultracam_configure_video(struct uvd *uvd) +{ + if (uvd == NULL) + return; + + RESTRICT_TO_RANGE(init_brightness, 0, 255); + RESTRICT_TO_RANGE(init_contrast, 0, 255); + RESTRICT_TO_RANGE(init_color, 0, 255); + RESTRICT_TO_RANGE(init_hue, 0, 255); + RESTRICT_TO_RANGE(hue_correction, 0, 255); + + memset(&uvd->vpic, 0, sizeof(uvd->vpic)); + memset(&uvd->vpic_old, 0x55, sizeof(uvd->vpic_old)); + + uvd->vpic.colour = init_color << 8; + uvd->vpic.hue = init_hue << 8; + uvd->vpic.brightness = init_brightness << 8; + uvd->vpic.contrast = init_contrast << 8; + uvd->vpic.whiteness = 105 << 8; /* This one isn't used */ + uvd->vpic.depth = 24; + uvd->vpic.palette = VIDEO_PALETTE_RGB24; + + memset(&uvd->vcap, 0, sizeof(uvd->vcap)); + strcpy(uvd->vcap.name, "IBM Ultra Camera"); + uvd->vcap.type = VID_TYPE_CAPTURE; + uvd->vcap.channels = 1; + uvd->vcap.audios = 0; + uvd->vcap.maxwidth = VIDEOSIZE_X(uvd->canvas); + uvd->vcap.maxheight = VIDEOSIZE_Y(uvd->canvas); + uvd->vcap.minwidth = min_canvasWidth; + uvd->vcap.minheight = min_canvasHeight; + + memset(&uvd->vchan, 0, sizeof(uvd->vchan)); + uvd->vchan.flags = 0; + uvd->vchan.tuners = 0; + uvd->vchan.channel = 0; + uvd->vchan.type = VIDEO_TYPE_CAMERA; + strcpy(uvd->vchan.name, "Camera"); +} + +/* + * ultracam_probe() + * + * This procedure queries device descriptor and accepts the interface + * if it looks like our camera. + * + * History: + * 12-Nov-2000 Reworked to comply with new probe() signature. + * 23-Jan-2001 Added compatibility with 2.2.x kernels. + */ +static int ultracam_probe(struct usb_interface *intf, const struct usb_device_id *devid) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct uvd *uvd = NULL; + int ix, i, nas; + int actInterface=-1, inactInterface=-1, maxPS=0; + unsigned char video_ep = 0; + + if (debug >= 1) + info("ultracam_probe(%p)", intf); + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + info("IBM Ultra camera found (rev. 0x%04x)", + le16_to_cpu(dev->descriptor.bcdDevice)); + + /* Validate found interface: must have one ISO endpoint */ + nas = intf->num_altsetting; + if (debug > 0) + info("Number of alternate settings=%d.", nas); + if (nas < 8) { + err("Too few alternate settings for this camera!"); + return -ENODEV; + } + /* Validate all alternate settings */ + for (ix=0; ix < nas; ix++) { + const struct usb_host_interface *interface; + const struct usb_endpoint_descriptor *endpoint; + + interface = &intf->altsetting[ix]; + i = interface->desc.bAlternateSetting; + if (interface->desc.bNumEndpoints != 1) { + err("Interface %d. has %u. endpoints!", + interface->desc.bInterfaceNumber, + (unsigned)(interface->desc.bNumEndpoints)); + return -ENODEV; + } + endpoint = &interface->endpoint[0].desc; + if (video_ep == 0) + video_ep = endpoint->bEndpointAddress; + else if (video_ep != endpoint->bEndpointAddress) { + err("Alternate settings have different endpoint addresses!"); + return -ENODEV; + } + if ((endpoint->bmAttributes & 0x03) != 0x01) { + err("Interface %d. has non-ISO endpoint!", + interface->desc.bInterfaceNumber); + return -ENODEV; + } + if ((endpoint->bEndpointAddress & 0x80) == 0) { + err("Interface %d. has ISO OUT endpoint!", + interface->desc.bInterfaceNumber); + return -ENODEV; + } + if (le16_to_cpu(endpoint->wMaxPacketSize) == 0) { + if (inactInterface < 0) + inactInterface = i; + else { + err("More than one inactive alt. setting!"); + return -ENODEV; + } + } else { + if (actInterface < 0) { + actInterface = i; + maxPS = le16_to_cpu(endpoint->wMaxPacketSize); + if (debug > 0) + info("Active setting=%d. maxPS=%d.", i, maxPS); + } else { + /* Got another active alt. setting */ + if (maxPS < le16_to_cpu(endpoint->wMaxPacketSize)) { + /* This one is better! */ + actInterface = i; + maxPS = le16_to_cpu(endpoint->wMaxPacketSize); + if (debug > 0) { + info("Even better ctive setting=%d. maxPS=%d.", + i, maxPS); + } + } + } + } + } + if ((maxPS <= 0) || (actInterface < 0) || (inactInterface < 0)) { + err("Failed to recognize the camera!"); + return -ENODEV; + } + + uvd = usbvideo_AllocateDevice(cams); + if (uvd != NULL) { + /* Here uvd is a fully allocated uvd object */ + uvd->flags = flags; + uvd->debug = debug; + uvd->dev = dev; + uvd->iface = intf->altsetting->desc.bInterfaceNumber; + uvd->ifaceAltInactive = inactInterface; + uvd->ifaceAltActive = actInterface; + uvd->video_endp = video_ep; + uvd->iso_packet_len = maxPS; + uvd->paletteBits = 1L << VIDEO_PALETTE_RGB24; + uvd->defaultPalette = VIDEO_PALETTE_RGB24; + uvd->canvas = VIDEOSIZE(640, 480); /* FIXME */ + uvd->videosize = uvd->canvas; /* ultracam_size_to_videosize(size);*/ + + /* Initialize ibmcam-specific data */ + assert(ULTRACAM_T(uvd) != NULL); + ULTRACAM_T(uvd)->camera_model = 0; /* Not used yet */ + ULTRACAM_T(uvd)->initialized = 0; + + ultracam_configure_video(uvd); + + i = usbvideo_RegisterVideoDevice(uvd); + if (i != 0) { + err("usbvideo_RegisterVideoDevice() failed."); + uvd = NULL; + } + } + + if (uvd) { + usb_set_intfdata (intf, uvd); + return 0; + } + return -EIO; +} + + +static struct usb_device_id id_table[] = { + { USB_DEVICE(ULTRACAM_VENDOR_ID, ULTRACAM_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +/* + * ultracam_init() + * + * This code is run to initialize the driver. + */ +static int __init ultracam_init(void) +{ + struct usbvideo_cb cbTbl; + memset(&cbTbl, 0, sizeof(cbTbl)); + cbTbl.probe = ultracam_probe; + cbTbl.setupOnOpen = ultracam_setup_on_open; + cbTbl.videoStart = ultracam_video_start; + cbTbl.videoStop = ultracam_video_stop; + cbTbl.processData = ultracam_ProcessIsocData; + cbTbl.postProcess = usbvideo_DeinterlaceFrame; + cbTbl.adjustPicture = ultracam_adjust_picture; + cbTbl.getFPS = ultracam_calculate_fps; + return usbvideo_register( + &cams, + MAX_CAMERAS, + sizeof(ultracam_t), + "ultracam", + &cbTbl, + THIS_MODULE, + id_table); +} + +static void __exit ultracam_cleanup(void) +{ + usbvideo_Deregister(&cams); +} + +MODULE_DEVICE_TABLE(usb, id_table); +MODULE_LICENSE("GPL"); + +module_init(ultracam_init); +module_exit(ultracam_cleanup); diff --git a/drivers/media/video/usbvideo/usbvideo.c b/drivers/media/video/usbvideo/usbvideo.c new file mode 100644 index 000000000000..0b51fae720a9 --- /dev/null +++ b/drivers/media/video/usbvideo/usbvideo.c @@ -0,0 +1,2190 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/smp_lock.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <linux/spinlock.h> + +#include <asm/io.h> + +#include "usbvideo.h" + +#if defined(MAP_NR) +#define virt_to_page(v) MAP_NR(v) /* Kernels 2.2.x */ +#endif + +static int video_nr = -1; +module_param(video_nr, int, 0); + +/* + * Local prototypes. + */ +static void usbvideo_Disconnect(struct usb_interface *intf); +static void usbvideo_CameraRelease(struct uvd *uvd); + +static int usbvideo_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static int usbvideo_v4l_mmap(struct file *file, struct vm_area_struct *vma); +static int usbvideo_v4l_open(struct inode *inode, struct file *file); +static ssize_t usbvideo_v4l_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos); +static int usbvideo_v4l_close(struct inode *inode, struct file *file); + +static int usbvideo_StartDataPump(struct uvd *uvd); +static void usbvideo_StopDataPump(struct uvd *uvd); +static int usbvideo_GetFrame(struct uvd *uvd, int frameNum); +static int usbvideo_NewFrame(struct uvd *uvd, int framenum); +static void usbvideo_SoftwareContrastAdjustment(struct uvd *uvd, + struct usbvideo_frame *frame); + +/*******************************/ +/* Memory management functions */ +/*******************************/ +static void *usbvideo_rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void usbvideo_rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +static void RingQueue_Initialize(struct RingQueue *rq) +{ + assert(rq != NULL); + init_waitqueue_head(&rq->wqh); +} + +static void RingQueue_Allocate(struct RingQueue *rq, int rqLen) +{ + /* Make sure the requested size is a power of 2 and + round up if necessary. This allows index wrapping + using masks rather than modulo */ + + int i = 1; + assert(rq != NULL); + assert(rqLen > 0); + + while(rqLen >> i) + i++; + if(rqLen != 1 << (i-1)) + rqLen = 1 << i; + + rq->length = rqLen; + rq->ri = rq->wi = 0; + rq->queue = usbvideo_rvmalloc(rq->length); + assert(rq->queue != NULL); +} + +static int RingQueue_IsAllocated(const struct RingQueue *rq) +{ + if (rq == NULL) + return 0; + return (rq->queue != NULL) && (rq->length > 0); +} + +static void RingQueue_Free(struct RingQueue *rq) +{ + assert(rq != NULL); + if (RingQueue_IsAllocated(rq)) { + usbvideo_rvfree(rq->queue, rq->length); + rq->queue = NULL; + rq->length = 0; + } +} + +int RingQueue_Dequeue(struct RingQueue *rq, unsigned char *dst, int len) +{ + int rql, toread; + + assert(rq != NULL); + assert(dst != NULL); + + rql = RingQueue_GetLength(rq); + if(!rql) + return 0; + + /* Clip requested length to available data */ + if(len > rql) + len = rql; + + toread = len; + if(rq->ri > rq->wi) { + /* Read data from tail */ + int read = (toread < (rq->length - rq->ri)) ? toread : rq->length - rq->ri; + memcpy(dst, rq->queue + rq->ri, read); + toread -= read; + dst += read; + rq->ri = (rq->ri + read) & (rq->length-1); + } + if(toread) { + /* Read data from head */ + memcpy(dst, rq->queue + rq->ri, toread); + rq->ri = (rq->ri + toread) & (rq->length-1); + } + return len; +} + +EXPORT_SYMBOL(RingQueue_Dequeue); + +int RingQueue_Enqueue(struct RingQueue *rq, const unsigned char *cdata, int n) +{ + int enqueued = 0; + + assert(rq != NULL); + assert(cdata != NULL); + assert(rq->length > 0); + while (n > 0) { + int m, q_avail; + + /* Calculate the largest chunk that fits the tail of the ring */ + q_avail = rq->length - rq->wi; + if (q_avail <= 0) { + rq->wi = 0; + q_avail = rq->length; + } + m = n; + assert(q_avail > 0); + if (m > q_avail) + m = q_avail; + + memcpy(rq->queue + rq->wi, cdata, m); + RING_QUEUE_ADVANCE_INDEX(rq, wi, m); + cdata += m; + enqueued += m; + n -= m; + } + return enqueued; +} + +EXPORT_SYMBOL(RingQueue_Enqueue); + +static void RingQueue_InterruptibleSleepOn(struct RingQueue *rq) +{ + assert(rq != NULL); + interruptible_sleep_on(&rq->wqh); +} + +void RingQueue_WakeUpInterruptible(struct RingQueue *rq) +{ + assert(rq != NULL); + if (waitqueue_active(&rq->wqh)) + wake_up_interruptible(&rq->wqh); +} + +EXPORT_SYMBOL(RingQueue_WakeUpInterruptible); + +void RingQueue_Flush(struct RingQueue *rq) +{ + assert(rq != NULL); + rq->ri = 0; + rq->wi = 0; +} + +EXPORT_SYMBOL(RingQueue_Flush); + + +/* + * usbvideo_VideosizeToString() + * + * This procedure converts given videosize value to readable string. + * + * History: + * 07-Aug-2000 Created. + * 19-Oct-2000 Reworked for usbvideo module. + */ +static void usbvideo_VideosizeToString(char *buf, int bufLen, videosize_t vs) +{ + char tmp[40]; + int n; + + n = 1 + sprintf(tmp, "%ldx%ld", VIDEOSIZE_X(vs), VIDEOSIZE_Y(vs)); + assert(n < sizeof(tmp)); + if ((buf == NULL) || (bufLen < n)) + err("usbvideo_VideosizeToString: buffer is too small."); + else + memmove(buf, tmp, n); +} + +/* + * usbvideo_OverlayChar() + * + * History: + * 01-Feb-2000 Created. + */ +static void usbvideo_OverlayChar(struct uvd *uvd, struct usbvideo_frame *frame, + int x, int y, int ch) +{ + static const unsigned short digits[16] = { + 0xF6DE, /* 0 */ + 0x2492, /* 1 */ + 0xE7CE, /* 2 */ + 0xE79E, /* 3 */ + 0xB792, /* 4 */ + 0xF39E, /* 5 */ + 0xF3DE, /* 6 */ + 0xF492, /* 7 */ + 0xF7DE, /* 8 */ + 0xF79E, /* 9 */ + 0x77DA, /* a */ + 0xD75C, /* b */ + 0xF24E, /* c */ + 0xD6DC, /* d */ + 0xF34E, /* e */ + 0xF348 /* f */ + }; + unsigned short digit; + int ix, iy; + + if ((uvd == NULL) || (frame == NULL)) + return; + + if (ch >= '0' && ch <= '9') + ch -= '0'; + else if (ch >= 'A' && ch <= 'F') + ch = 10 + (ch - 'A'); + else if (ch >= 'a' && ch <= 'f') + ch = 10 + (ch - 'a'); + else + return; + digit = digits[ch]; + + for (iy=0; iy < 5; iy++) { + for (ix=0; ix < 3; ix++) { + if (digit & 0x8000) { + if (uvd->paletteBits & (1L << VIDEO_PALETTE_RGB24)) { +/* TODO */ RGB24_PUTPIXEL(frame, x+ix, y+iy, 0xFF, 0xFF, 0xFF); + } + } + digit = digit << 1; + } + } +} + +/* + * usbvideo_OverlayString() + * + * History: + * 01-Feb-2000 Created. + */ +static void usbvideo_OverlayString(struct uvd *uvd, struct usbvideo_frame *frame, + int x, int y, const char *str) +{ + while (*str) { + usbvideo_OverlayChar(uvd, frame, x, y, *str); + str++; + x += 4; /* 3 pixels character + 1 space */ + } +} + +/* + * usbvideo_OverlayStats() + * + * Overlays important debugging information. + * + * History: + * 01-Feb-2000 Created. + */ +static void usbvideo_OverlayStats(struct uvd *uvd, struct usbvideo_frame *frame) +{ + const int y_diff = 8; + char tmp[16]; + int x = 10, y=10; + long i, j, barLength; + const int qi_x1 = 60, qi_y1 = 10; + const int qi_x2 = VIDEOSIZE_X(frame->request) - 10, qi_h = 10; + + /* Call the user callback, see if we may proceed after that */ + if (VALID_CALLBACK(uvd, overlayHook)) { + if (GET_CALLBACK(uvd, overlayHook)(uvd, frame) < 0) + return; + } + + /* + * We draw a (mostly) hollow rectangle with qi_xxx coordinates. + * Left edge symbolizes the queue index 0; right edge symbolizes + * the full capacity of the queue. + */ + barLength = qi_x2 - qi_x1 - 2; + if ((barLength > 10) && (uvd->paletteBits & (1L << VIDEO_PALETTE_RGB24))) { +/* TODO */ long u_lo, u_hi, q_used; + long m_ri, m_wi, m_lo, m_hi; + + /* + * Determine fill zones (used areas of the queue): + * 0 xxxxxxx u_lo ...... uvd->dp.ri xxxxxxxx u_hi ..... uvd->dp.length + * + * if u_lo < 0 then there is no first filler. + */ + + q_used = RingQueue_GetLength(&uvd->dp); + if ((uvd->dp.ri + q_used) >= uvd->dp.length) { + u_hi = uvd->dp.length; + u_lo = (q_used + uvd->dp.ri) & (uvd->dp.length-1); + } else { + u_hi = (q_used + uvd->dp.ri); + u_lo = -1; + } + + /* Convert byte indices into screen units */ + m_ri = qi_x1 + ((barLength * uvd->dp.ri) / uvd->dp.length); + m_wi = qi_x1 + ((barLength * uvd->dp.wi) / uvd->dp.length); + m_lo = (u_lo > 0) ? (qi_x1 + ((barLength * u_lo) / uvd->dp.length)) : -1; + m_hi = qi_x1 + ((barLength * u_hi) / uvd->dp.length); + + for (j=qi_y1; j < (qi_y1 + qi_h); j++) { + for (i=qi_x1; i < qi_x2; i++) { + /* Draw border lines */ + if ((j == qi_y1) || (j == (qi_y1 + qi_h - 1)) || + (i == qi_x1) || (i == (qi_x2 - 1))) { + RGB24_PUTPIXEL(frame, i, j, 0xFF, 0xFF, 0xFF); + continue; + } + /* For all other points the Y coordinate does not matter */ + if ((i >= m_ri) && (i <= (m_ri + 3))) { + RGB24_PUTPIXEL(frame, i, j, 0x00, 0xFF, 0x00); + } else if ((i >= m_wi) && (i <= (m_wi + 3))) { + RGB24_PUTPIXEL(frame, i, j, 0xFF, 0x00, 0x00); + } else if ((i < m_lo) || ((i > m_ri) && (i < m_hi))) + RGB24_PUTPIXEL(frame, i, j, 0x00, 0x00, 0xFF); + } + } + } + + sprintf(tmp, "%8lx", uvd->stats.frame_num); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8lx", uvd->stats.urb_count); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8lx", uvd->stats.urb_length); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8lx", uvd->stats.data_count); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8lx", uvd->stats.header_count); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8lx", uvd->stats.iso_skip_count); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8lx", uvd->stats.iso_err_count); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8x", uvd->vpic.colour); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8x", uvd->vpic.hue); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8x", uvd->vpic.brightness >> 8); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8x", uvd->vpic.contrast >> 12); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; + + sprintf(tmp, "%8d", uvd->vpic.whiteness >> 8); + usbvideo_OverlayString(uvd, frame, x, y, tmp); + y += y_diff; +} + +/* + * usbvideo_ReportStatistics() + * + * This procedure prints packet and transfer statistics. + * + * History: + * 14-Jan-2000 Corrected default multiplier. + */ +static void usbvideo_ReportStatistics(const struct uvd *uvd) +{ + if ((uvd != NULL) && (uvd->stats.urb_count > 0)) { + unsigned long allPackets, badPackets, goodPackets, percent; + allPackets = uvd->stats.urb_count * CAMERA_URB_FRAMES; + badPackets = uvd->stats.iso_skip_count + uvd->stats.iso_err_count; + goodPackets = allPackets - badPackets; + /* Calculate percentage wisely, remember integer limits */ + assert(allPackets != 0); + if (goodPackets < (((unsigned long)-1)/100)) + percent = (100 * goodPackets) / allPackets; + else + percent = goodPackets / (allPackets / 100); + info("Packet Statistics: Total=%lu. Empty=%lu. Usage=%lu%%", + allPackets, badPackets, percent); + if (uvd->iso_packet_len > 0) { + unsigned long allBytes, xferBytes; + char multiplier = ' '; + allBytes = allPackets * uvd->iso_packet_len; + xferBytes = uvd->stats.data_count; + assert(allBytes != 0); + if (xferBytes < (((unsigned long)-1)/100)) + percent = (100 * xferBytes) / allBytes; + else + percent = xferBytes / (allBytes / 100); + /* Scale xferBytes for easy reading */ + if (xferBytes > 10*1024) { + xferBytes /= 1024; + multiplier = 'K'; + if (xferBytes > 10*1024) { + xferBytes /= 1024; + multiplier = 'M'; + if (xferBytes > 10*1024) { + xferBytes /= 1024; + multiplier = 'G'; + if (xferBytes > 10*1024) { + xferBytes /= 1024; + multiplier = 'T'; + } + } + } + } + info("Transfer Statistics: Transferred=%lu%cB Usage=%lu%%", + xferBytes, multiplier, percent); + } + } +} + +/* + * usbvideo_TestPattern() + * + * Procedure forms a test pattern (yellow grid on blue background). + * + * Parameters: + * fullframe: if TRUE then entire frame is filled, otherwise the procedure + * continues from the current scanline. + * pmode 0: fill the frame with solid blue color (like on VCR or TV) + * 1: Draw a colored grid + * + * History: + * 01-Feb-2000 Created. + */ +void usbvideo_TestPattern(struct uvd *uvd, int fullframe, int pmode) +{ + struct usbvideo_frame *frame; + int num_cell = 0; + int scan_length = 0; + static int num_pass = 0; + + if (uvd == NULL) { + err("%s: uvd == NULL", __FUNCTION__); + return; + } + if ((uvd->curframe < 0) || (uvd->curframe >= USBVIDEO_NUMFRAMES)) { + err("%s: uvd->curframe=%d.", __FUNCTION__, uvd->curframe); + return; + } + + /* Grab the current frame */ + frame = &uvd->frame[uvd->curframe]; + + /* Optionally start at the beginning */ + if (fullframe) { + frame->curline = 0; + frame->seqRead_Length = 0; + } +#if 0 + { /* For debugging purposes only */ + char tmp[20]; + usbvideo_VideosizeToString(tmp, sizeof(tmp), frame->request); + info("testpattern: frame=%s", tmp); + } +#endif + /* Form every scan line */ + for (; frame->curline < VIDEOSIZE_Y(frame->request); frame->curline++) { + int i; + unsigned char *f = frame->data + + (VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL * frame->curline); + for (i=0; i < VIDEOSIZE_X(frame->request); i++) { + unsigned char cb=0x80; + unsigned char cg = 0; + unsigned char cr = 0; + + if (pmode == 1) { + if (frame->curline % 32 == 0) + cb = 0, cg = cr = 0xFF; + else if (i % 32 == 0) { + if (frame->curline % 32 == 1) + num_cell++; + cb = 0, cg = cr = 0xFF; + } else { + cb = ((num_cell*7) + num_pass) & 0xFF; + cg = ((num_cell*5) + num_pass*2) & 0xFF; + cr = ((num_cell*3) + num_pass*3) & 0xFF; + } + } else { + /* Just the blue screen */ + } + + *f++ = cb; + *f++ = cg; + *f++ = cr; + scan_length += 3; + } + } + + frame->frameState = FrameState_Done; + frame->seqRead_Length += scan_length; + ++num_pass; + + /* We do this unconditionally, regardless of FLAGS_OVERLAY_STATS */ + usbvideo_OverlayStats(uvd, frame); +} + +EXPORT_SYMBOL(usbvideo_TestPattern); + + +#ifdef DEBUG +/* + * usbvideo_HexDump() + * + * A debugging tool. Prints hex dumps. + * + * History: + * 29-Jul-2000 Added printing of offsets. + */ +void usbvideo_HexDump(const unsigned char *data, int len) +{ + const int bytes_per_line = 32; + char tmp[128]; /* 32*3 + 5 */ + int i, k; + + for (i=k=0; len > 0; i++, len--) { + if (i > 0 && ((i % bytes_per_line) == 0)) { + printk("%s\n", tmp); + k=0; + } + if ((i % bytes_per_line) == 0) + k += sprintf(&tmp[k], "%04x: ", i); + k += sprintf(&tmp[k], "%02x ", data[i]); + } + if (k > 0) + printk("%s\n", tmp); +} + +EXPORT_SYMBOL(usbvideo_HexDump); + +#endif + +/* ******************************************************************** */ + +/* XXX: this piece of crap really wants some error handling.. */ +static void usbvideo_ClientIncModCount(struct uvd *uvd) +{ + if (uvd == NULL) { + err("%s: uvd == NULL", __FUNCTION__); + return; + } + if (uvd->handle == NULL) { + err("%s: uvd->handle == NULL", __FUNCTION__); + return; + } + if (uvd->handle->md_module == NULL) { + err("%s: uvd->handle->md_module == NULL", __FUNCTION__); + return; + } + if (!try_module_get(uvd->handle->md_module)) { + err("%s: try_module_get() == 0", __FUNCTION__); + return; + } +} + +static void usbvideo_ClientDecModCount(struct uvd *uvd) +{ + if (uvd == NULL) { + err("%s: uvd == NULL", __FUNCTION__); + return; + } + if (uvd->handle == NULL) { + err("%s: uvd->handle == NULL", __FUNCTION__); + return; + } + if (uvd->handle->md_module == NULL) { + err("%s: uvd->handle->md_module == NULL", __FUNCTION__); + return; + } + module_put(uvd->handle->md_module); +} + +int usbvideo_register( + struct usbvideo **pCams, + const int num_cams, + const int num_extra, + const char *driverName, + const struct usbvideo_cb *cbTbl, + struct module *md, + const struct usb_device_id *id_table) +{ + struct usbvideo *cams; + int i, base_size, result; + + /* Check parameters for sanity */ + if ((num_cams <= 0) || (pCams == NULL) || (cbTbl == NULL)) { + err("%s: Illegal call", __FUNCTION__); + return -EINVAL; + } + + /* Check registration callback - must be set! */ + if (cbTbl->probe == NULL) { + err("%s: probe() is required!", __FUNCTION__); + return -EINVAL; + } + + base_size = num_cams * sizeof(struct uvd) + sizeof(struct usbvideo); + cams = (struct usbvideo *) kzalloc(base_size, GFP_KERNEL); + if (cams == NULL) { + err("Failed to allocate %d. bytes for usbvideo struct", base_size); + return -ENOMEM; + } + dbg("%s: Allocated $%p (%d. bytes) for %d. cameras", + __FUNCTION__, cams, base_size, num_cams); + + /* Copy callbacks, apply defaults for those that are not set */ + memmove(&cams->cb, cbTbl, sizeof(cams->cb)); + if (cams->cb.getFrame == NULL) + cams->cb.getFrame = usbvideo_GetFrame; + if (cams->cb.disconnect == NULL) + cams->cb.disconnect = usbvideo_Disconnect; + if (cams->cb.startDataPump == NULL) + cams->cb.startDataPump = usbvideo_StartDataPump; + if (cams->cb.stopDataPump == NULL) + cams->cb.stopDataPump = usbvideo_StopDataPump; + + cams->num_cameras = num_cams; + cams->cam = (struct uvd *) &cams[1]; + cams->md_module = md; + if (cams->md_module == NULL) + warn("%s: module == NULL!", __FUNCTION__); + mutex_init(&cams->lock); /* to 1 == available */ + + for (i = 0; i < num_cams; i++) { + struct uvd *up = &cams->cam[i]; + + up->handle = cams; + + /* Allocate user_data separately because of kmalloc's limits */ + if (num_extra > 0) { + up->user_size = num_cams * num_extra; + up->user_data = kmalloc(up->user_size, GFP_KERNEL); + if (up->user_data == NULL) { + err("%s: Failed to allocate user_data (%d. bytes)", + __FUNCTION__, up->user_size); + while (i) { + up = &cams->cam[--i]; + kfree(up->user_data); + } + kfree(cams); + return -ENOMEM; + } + dbg("%s: Allocated cams[%d].user_data=$%p (%d. bytes)", + __FUNCTION__, i, up->user_data, up->user_size); + } + } + + /* + * Register ourselves with USB stack. + */ + strcpy(cams->drvName, (driverName != NULL) ? driverName : "Unknown"); + cams->usbdrv.name = cams->drvName; + cams->usbdrv.probe = cams->cb.probe; + cams->usbdrv.disconnect = cams->cb.disconnect; + cams->usbdrv.id_table = id_table; + + /* + * Update global handle to usbvideo. This is very important + * because probe() can be called before usb_register() returns. + * If the handle is not yet updated then the probe() will fail. + */ + *pCams = cams; + result = usb_register(&cams->usbdrv); + if (result) { + for (i = 0; i < num_cams; i++) { + struct uvd *up = &cams->cam[i]; + kfree(up->user_data); + } + kfree(cams); + } + + return result; +} + +EXPORT_SYMBOL(usbvideo_register); + +/* + * usbvideo_Deregister() + * + * Procedure frees all usbvideo and user data structures. Be warned that + * if you had some dynamically allocated components in ->user field then + * you should free them before calling here. + */ +void usbvideo_Deregister(struct usbvideo **pCams) +{ + struct usbvideo *cams; + int i; + + if (pCams == NULL) { + err("%s: pCams == NULL", __FUNCTION__); + return; + } + cams = *pCams; + if (cams == NULL) { + err("%s: cams == NULL", __FUNCTION__); + return; + } + + dbg("%s: Deregistering %s driver.", __FUNCTION__, cams->drvName); + usb_deregister(&cams->usbdrv); + + dbg("%s: Deallocating cams=$%p (%d. cameras)", __FUNCTION__, cams, cams->num_cameras); + for (i=0; i < cams->num_cameras; i++) { + struct uvd *up = &cams->cam[i]; + int warning = 0; + + if (up->user_data != NULL) { + if (up->user_size <= 0) + ++warning; + } else { + if (up->user_size > 0) + ++warning; + } + if (warning) { + err("%s: Warning: user_data=$%p user_size=%d.", + __FUNCTION__, up->user_data, up->user_size); + } else { + dbg("%s: Freeing %d. $%p->user_data=$%p", + __FUNCTION__, i, up, up->user_data); + kfree(up->user_data); + } + } + /* Whole array was allocated in one chunk */ + dbg("%s: Freed %d uvd structures", + __FUNCTION__, cams->num_cameras); + kfree(cams); + *pCams = NULL; +} + +EXPORT_SYMBOL(usbvideo_Deregister); + +/* + * usbvideo_Disconnect() + * + * This procedure stops all driver activity. Deallocation of + * the interface-private structure (pointed by 'ptr') is done now + * (if we don't have any open files) or later, when those files + * are closed. After that driver should be removable. + * + * This code handles surprise removal. The uvd->user is a counter which + * increments on open() and decrements on close(). If we see here that + * this counter is not 0 then we have a client who still has us opened. + * We set uvd->remove_pending flag as early as possible, and after that + * all access to the camera will gracefully fail. These failures should + * prompt client to (eventually) close the video device, and then - in + * usbvideo_v4l_close() - we decrement uvd->uvd_used and usage counter. + * + * History: + * 22-Jan-2000 Added polling of MOD_IN_USE to delay removal until all users gone. + * 27-Jan-2000 Reworked to allow pending disconnects; see xxx_close() + * 24-May-2000 Corrected to prevent race condition (MOD_xxx_USE_COUNT). + * 19-Oct-2000 Moved to usbvideo module. + */ +static void usbvideo_Disconnect(struct usb_interface *intf) +{ + struct uvd *uvd = usb_get_intfdata (intf); + int i; + + if (uvd == NULL) { + err("%s($%p): Illegal call.", __FUNCTION__, intf); + return; + } + + usb_set_intfdata (intf, NULL); + + usbvideo_ClientIncModCount(uvd); + if (uvd->debug > 0) + info("%s(%p.)", __FUNCTION__, intf); + + mutex_lock(&uvd->lock); + uvd->remove_pending = 1; /* Now all ISO data will be ignored */ + + /* At this time we ask to cancel outstanding URBs */ + GET_CALLBACK(uvd, stopDataPump)(uvd); + + for (i=0; i < USBVIDEO_NUMSBUF; i++) + usb_free_urb(uvd->sbuf[i].urb); + + usb_put_dev(uvd->dev); + uvd->dev = NULL; /* USB device is no more */ + + video_unregister_device(&uvd->vdev); + if (uvd->debug > 0) + info("%s: Video unregistered.", __FUNCTION__); + + if (uvd->user) + info("%s: In use, disconnect pending.", __FUNCTION__); + else + usbvideo_CameraRelease(uvd); + mutex_unlock(&uvd->lock); + info("USB camera disconnected."); + + usbvideo_ClientDecModCount(uvd); +} + +/* + * usbvideo_CameraRelease() + * + * This code does final release of uvd. This happens + * after the device is disconnected -and- all clients + * closed their files. + * + * History: + * 27-Jan-2000 Created. + */ +static void usbvideo_CameraRelease(struct uvd *uvd) +{ + if (uvd == NULL) { + err("%s: Illegal call", __FUNCTION__); + return; + } + + RingQueue_Free(&uvd->dp); + if (VALID_CALLBACK(uvd, userFree)) + GET_CALLBACK(uvd, userFree)(uvd); + uvd->uvd_used = 0; /* This is atomic, no need to take mutex */ +} + +/* + * usbvideo_find_struct() + * + * This code searches the array of preallocated (static) structures + * and returns index of the first one that isn't in use. Returns -1 + * if there are no free structures. + * + * History: + * 27-Jan-2000 Created. + */ +static int usbvideo_find_struct(struct usbvideo *cams) +{ + int u, rv = -1; + + if (cams == NULL) { + err("No usbvideo handle?"); + return -1; + } + mutex_lock(&cams->lock); + for (u = 0; u < cams->num_cameras; u++) { + struct uvd *uvd = &cams->cam[u]; + if (!uvd->uvd_used) /* This one is free */ + { + uvd->uvd_used = 1; /* In use now */ + mutex_init(&uvd->lock); /* to 1 == available */ + uvd->dev = NULL; + rv = u; + break; + } + } + mutex_unlock(&cams->lock); + return rv; +} + +static struct file_operations usbvideo_fops = { + .owner = THIS_MODULE, + .open = usbvideo_v4l_open, + .release =usbvideo_v4l_close, + .read = usbvideo_v4l_read, + .mmap = usbvideo_v4l_mmap, + .ioctl = usbvideo_v4l_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; +static const struct video_device usbvideo_template = { + .owner = THIS_MODULE, + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_CPIA, + .fops = &usbvideo_fops, +}; + +struct uvd *usbvideo_AllocateDevice(struct usbvideo *cams) +{ + int i, devnum; + struct uvd *uvd = NULL; + + if (cams == NULL) { + err("No usbvideo handle?"); + return NULL; + } + + devnum = usbvideo_find_struct(cams); + if (devnum == -1) { + err("IBM USB camera driver: Too many devices!"); + return NULL; + } + uvd = &cams->cam[devnum]; + dbg("Device entry #%d. at $%p", devnum, uvd); + + /* Not relying upon caller we increase module counter ourselves */ + usbvideo_ClientIncModCount(uvd); + + mutex_lock(&uvd->lock); + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + uvd->sbuf[i].urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL); + if (uvd->sbuf[i].urb == NULL) { + err("usb_alloc_urb(%d.) failed.", FRAMES_PER_DESC); + uvd->uvd_used = 0; + uvd = NULL; + goto allocate_done; + } + } + uvd->user=0; + uvd->remove_pending = 0; + uvd->last_error = 0; + RingQueue_Initialize(&uvd->dp); + + /* Initialize video device structure */ + uvd->vdev = usbvideo_template; + sprintf(uvd->vdev.name, "%.20s USB Camera", cams->drvName); + /* + * The client is free to overwrite those because we + * return control to the client's probe function right now. + */ +allocate_done: + mutex_unlock(&uvd->lock); + usbvideo_ClientDecModCount(uvd); + return uvd; +} + +EXPORT_SYMBOL(usbvideo_AllocateDevice); + +int usbvideo_RegisterVideoDevice(struct uvd *uvd) +{ + char tmp1[20], tmp2[20]; /* Buffers for printing */ + + if (uvd == NULL) { + err("%s: Illegal call.", __FUNCTION__); + return -EINVAL; + } + if (uvd->video_endp == 0) { + info("%s: No video endpoint specified; data pump disabled.", __FUNCTION__); + } + if (uvd->paletteBits == 0) { + err("%s: No palettes specified!", __FUNCTION__); + return -EINVAL; + } + if (uvd->defaultPalette == 0) { + info("%s: No default palette!", __FUNCTION__); + } + + uvd->max_frame_size = VIDEOSIZE_X(uvd->canvas) * + VIDEOSIZE_Y(uvd->canvas) * V4L_BYTES_PER_PIXEL; + usbvideo_VideosizeToString(tmp1, sizeof(tmp1), uvd->videosize); + usbvideo_VideosizeToString(tmp2, sizeof(tmp2), uvd->canvas); + + if (uvd->debug > 0) { + info("%s: iface=%d. endpoint=$%02x paletteBits=$%08lx", + __FUNCTION__, uvd->iface, uvd->video_endp, uvd->paletteBits); + } + if (video_register_device(&uvd->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + err("%s: video_register_device failed", __FUNCTION__); + return -EPIPE; + } + if (uvd->debug > 1) { + info("%s: video_register_device() successful", __FUNCTION__); + } + if (uvd->dev == NULL) { + err("%s: uvd->dev == NULL", __FUNCTION__); + return -EINVAL; + } + + info("%s on /dev/video%d: canvas=%s videosize=%s", + (uvd->handle != NULL) ? uvd->handle->drvName : "???", + uvd->vdev.minor, tmp2, tmp1); + + usb_get_dev(uvd->dev); + return 0; +} + +EXPORT_SYMBOL(usbvideo_RegisterVideoDevice); + +/* ******************************************************************** */ + +static int usbvideo_v4l_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct uvd *uvd = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end-vma->vm_start; + unsigned long page, pos; + + if (!CAMERA_IS_OPERATIONAL(uvd)) + return -EFAULT; + + if (size > (((USBVIDEO_NUMFRAMES * uvd->max_frame_size) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) + return -EINVAL; + + pos = (unsigned long) uvd->fbuf; + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +/* + * usbvideo_v4l_open() + * + * This is part of Video 4 Linux API. The driver can be opened by one + * client only (checks internal counter 'uvdser'). The procedure + * then allocates buffers needed for video processing. + * + * History: + * 22-Jan-2000 Rewrote, moved scratch buffer allocation here. Now the + * camera is also initialized here (once per connect), at + * expense of V4L client (it waits on open() call). + * 27-Jan-2000 Used USBVIDEO_NUMSBUF as number of URB buffers. + * 24-May-2000 Corrected to prevent race condition (MOD_xxx_USE_COUNT). + */ +static int usbvideo_v4l_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct uvd *uvd = (struct uvd *) dev; + const int sb_size = FRAMES_PER_DESC * uvd->iso_packet_len; + int i, errCode = 0; + + if (uvd->debug > 1) + info("%s($%p)", __FUNCTION__, dev); + + usbvideo_ClientIncModCount(uvd); + mutex_lock(&uvd->lock); + + if (uvd->user) { + err("%s: Someone tried to open an already opened device!", __FUNCTION__); + errCode = -EBUSY; + } else { + /* Clear statistics */ + memset(&uvd->stats, 0, sizeof(uvd->stats)); + + /* Clean pointers so we know if we allocated something */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) + uvd->sbuf[i].data = NULL; + + /* Allocate memory for the frame buffers */ + uvd->fbuf_size = USBVIDEO_NUMFRAMES * uvd->max_frame_size; + uvd->fbuf = usbvideo_rvmalloc(uvd->fbuf_size); + RingQueue_Allocate(&uvd->dp, RING_QUEUE_SIZE); + if ((uvd->fbuf == NULL) || + (!RingQueue_IsAllocated(&uvd->dp))) { + err("%s: Failed to allocate fbuf or dp", __FUNCTION__); + errCode = -ENOMEM; + } else { + /* Allocate all buffers */ + for (i=0; i < USBVIDEO_NUMFRAMES; i++) { + uvd->frame[i].frameState = FrameState_Unused; + uvd->frame[i].data = uvd->fbuf + i*(uvd->max_frame_size); + /* + * Set default sizes in case IOCTL (VIDIOCMCAPTURE) + * is not used (using read() instead). + */ + uvd->frame[i].canvas = uvd->canvas; + uvd->frame[i].seqRead_Index = 0; + } + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + uvd->sbuf[i].data = kmalloc(sb_size, GFP_KERNEL); + if (uvd->sbuf[i].data == NULL) { + errCode = -ENOMEM; + break; + } + } + } + if (errCode != 0) { + /* Have to free all that memory */ + if (uvd->fbuf != NULL) { + usbvideo_rvfree(uvd->fbuf, uvd->fbuf_size); + uvd->fbuf = NULL; + } + RingQueue_Free(&uvd->dp); + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + kfree(uvd->sbuf[i].data); + uvd->sbuf[i].data = NULL; + } + } + } + + /* If so far no errors then we shall start the camera */ + if (errCode == 0) { + /* Start data pump if we have valid endpoint */ + if (uvd->video_endp != 0) + errCode = GET_CALLBACK(uvd, startDataPump)(uvd); + if (errCode == 0) { + if (VALID_CALLBACK(uvd, setupOnOpen)) { + if (uvd->debug > 1) + info("%s: setupOnOpen callback", __FUNCTION__); + errCode = GET_CALLBACK(uvd, setupOnOpen)(uvd); + if (errCode < 0) { + err("%s: setupOnOpen callback failed (%d.).", + __FUNCTION__, errCode); + } else if (uvd->debug > 1) { + info("%s: setupOnOpen callback successful", __FUNCTION__); + } + } + if (errCode == 0) { + uvd->settingsAdjusted = 0; + if (uvd->debug > 1) + info("%s: Open succeeded.", __FUNCTION__); + uvd->user++; + file->private_data = uvd; + } + } + } + mutex_unlock(&uvd->lock); + if (errCode != 0) + usbvideo_ClientDecModCount(uvd); + if (uvd->debug > 0) + info("%s: Returning %d.", __FUNCTION__, errCode); + return errCode; +} + +/* + * usbvideo_v4l_close() + * + * This is part of Video 4 Linux API. The procedure + * stops streaming and deallocates all buffers that were earlier + * allocated in usbvideo_v4l_open(). + * + * History: + * 22-Jan-2000 Moved scratch buffer deallocation here. + * 27-Jan-2000 Used USBVIDEO_NUMSBUF as number of URB buffers. + * 24-May-2000 Moved MOD_DEC_USE_COUNT outside of code that can sleep. + */ +static int usbvideo_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + struct uvd *uvd = (struct uvd *) dev; + int i; + + if (uvd->debug > 1) + info("%s($%p)", __FUNCTION__, dev); + + mutex_lock(&uvd->lock); + GET_CALLBACK(uvd, stopDataPump)(uvd); + usbvideo_rvfree(uvd->fbuf, uvd->fbuf_size); + uvd->fbuf = NULL; + RingQueue_Free(&uvd->dp); + + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + kfree(uvd->sbuf[i].data); + uvd->sbuf[i].data = NULL; + } + +#if USBVIDEO_REPORT_STATS + usbvideo_ReportStatistics(uvd); +#endif + + uvd->user--; + if (uvd->remove_pending) { + if (uvd->debug > 0) + info("usbvideo_v4l_close: Final disconnect."); + usbvideo_CameraRelease(uvd); + } + mutex_unlock(&uvd->lock); + usbvideo_ClientDecModCount(uvd); + + if (uvd->debug > 1) + info("%s: Completed.", __FUNCTION__); + file->private_data = NULL; + return 0; +} + +/* + * usbvideo_v4l_ioctl() + * + * This is part of Video 4 Linux API. The procedure handles ioctl() calls. + * + * History: + * 22-Jan-2000 Corrected VIDIOCSPICT to reject unsupported settings. + */ +static int usbvideo_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct uvd *uvd = file->private_data; + + if (!CAMERA_IS_OPERATIONAL(uvd)) + return -EIO; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + *b = uvd->vcap; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + *v = uvd->vchan; + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if (v->channel != 0) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *pic = arg; + *pic = uvd->vpic; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *pic = arg; + /* + * Use temporary 'video_picture' structure to preserve our + * own settings (such as color depth, palette) that we + * aren't allowing everyone (V4L client) to change. + */ + uvd->vpic.brightness = pic->brightness; + uvd->vpic.hue = pic->hue; + uvd->vpic.colour = pic->colour; + uvd->vpic.contrast = pic->contrast; + uvd->settingsAdjusted = 0; /* Will force new settings */ + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + + if(VALID_CALLBACK(uvd, setVideoMode)) { + return GET_CALLBACK(uvd, setVideoMode)(uvd, vw); + } + + if (vw->flags) + return -EINVAL; + if (vw->clipcount) + return -EINVAL; + if (vw->width != VIDEOSIZE_X(uvd->canvas)) + return -EINVAL; + if (vw->height != VIDEOSIZE_Y(uvd->canvas)) + return -EINVAL; + + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + + vw->x = 0; + vw->y = 0; + vw->width = VIDEOSIZE_X(uvd->videosize); + vw->height = VIDEOSIZE_Y(uvd->videosize); + vw->chromakey = 0; + if (VALID_CALLBACK(uvd, getFPS)) + vw->flags = GET_CALLBACK(uvd, getFPS)(uvd); + else + vw->flags = 10; /* FIXME: do better! */ + return 0; + } + case VIDIOCGMBUF: + { + struct video_mbuf *vm = arg; + int i; + + memset(vm, 0, sizeof(*vm)); + vm->size = uvd->max_frame_size * USBVIDEO_NUMFRAMES; + vm->frames = USBVIDEO_NUMFRAMES; + for(i = 0; i < USBVIDEO_NUMFRAMES; i++) + vm->offsets[i] = i * uvd->max_frame_size; + + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + + if (uvd->debug >= 1) { + info("VIDIOCMCAPTURE: frame=%d. size=%dx%d, format=%d.", + vm->frame, vm->width, vm->height, vm->format); + } + /* + * Check if the requested size is supported. If the requestor + * requests too big a frame then we may be tricked into accessing + * outside of own preallocated frame buffer (in uvd->frame). + * This will cause oops or a security hole. Theoretically, we + * could only clamp the size down to acceptable bounds, but then + * we'd need to figure out how to insert our smaller buffer into + * larger caller's buffer... this is not an easy question. So we + * here just flatly reject too large requests, assuming that the + * caller will resubmit with smaller size. Callers should know + * what size we support (returned by VIDIOCGCAP). However vidcat, + * for one, does not care and allows to ask for any size. + */ + if ((vm->width > VIDEOSIZE_X(uvd->canvas)) || + (vm->height > VIDEOSIZE_Y(uvd->canvas))) { + if (uvd->debug > 0) { + info("VIDIOCMCAPTURE: Size=%dx%d too large; " + "allowed only up to %ldx%ld", vm->width, vm->height, + VIDEOSIZE_X(uvd->canvas), VIDEOSIZE_Y(uvd->canvas)); + } + return -EINVAL; + } + /* Check if the palette is supported */ + if (((1L << vm->format) & uvd->paletteBits) == 0) { + if (uvd->debug > 0) { + info("VIDIOCMCAPTURE: format=%d. not supported" + " (paletteBits=$%08lx)", + vm->format, uvd->paletteBits); + } + return -EINVAL; + } + if ((vm->frame < 0) || (vm->frame >= USBVIDEO_NUMFRAMES)) { + err("VIDIOCMCAPTURE: vm.frame=%d. !E [0-%d]", vm->frame, USBVIDEO_NUMFRAMES-1); + return -EINVAL; + } + if (uvd->frame[vm->frame].frameState == FrameState_Grabbing) { + /* Not an error - can happen */ + } + uvd->frame[vm->frame].request = VIDEOSIZE(vm->width, vm->height); + uvd->frame[vm->frame].palette = vm->format; + + /* Mark it as ready */ + uvd->frame[vm->frame].frameState = FrameState_Ready; + + return usbvideo_NewFrame(uvd, vm->frame); + } + case VIDIOCSYNC: + { + int *frameNum = arg; + int ret; + + if (*frameNum < 0 || *frameNum >= USBVIDEO_NUMFRAMES) + return -EINVAL; + + if (uvd->debug >= 1) + info("VIDIOCSYNC: syncing to frame %d.", *frameNum); + if (uvd->flags & FLAGS_NO_DECODING) + ret = usbvideo_GetFrame(uvd, *frameNum); + else if (VALID_CALLBACK(uvd, getFrame)) { + ret = GET_CALLBACK(uvd, getFrame)(uvd, *frameNum); + if ((ret < 0) && (uvd->debug >= 1)) { + err("VIDIOCSYNC: getFrame() returned %d.", ret); + } + } else { + err("VIDIOCSYNC: getFrame is not set"); + ret = -EFAULT; + } + + /* + * The frame is in FrameState_Done_Hold state. Release it + * right now because its data is already mapped into + * the user space and it's up to the application to + * make use of it until it asks for another frame. + */ + uvd->frame[*frameNum].frameState = FrameState_Unused; + return ret; + } + case VIDIOCGFBUF: + { + struct video_buffer *vb = arg; + + memset(vb, 0, sizeof(*vb)); + return 0; + } + case VIDIOCKEY: + return 0; + + case VIDIOCCAPTURE: + return -EINVAL; + + case VIDIOCSFBUF: + + case VIDIOCGTUNER: + case VIDIOCSTUNER: + + case VIDIOCGFREQ: + case VIDIOCSFREQ: + + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int usbvideo_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, usbvideo_v4l_do_ioctl); +} + +/* + * usbvideo_v4l_read() + * + * This is mostly boring stuff. We simply ask for a frame and when it + * arrives copy all the video data from it into user space. There is + * no obvious need to override this method. + * + * History: + * 20-Oct-2000 Created. + * 01-Nov-2000 Added mutex (uvd->lock). + */ +static ssize_t usbvideo_v4l_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct uvd *uvd = file->private_data; + int noblock = file->f_flags & O_NONBLOCK; + int frmx = -1, i; + struct usbvideo_frame *frame; + + if (!CAMERA_IS_OPERATIONAL(uvd) || (buf == NULL)) + return -EFAULT; + + if (uvd->debug >= 1) + info("%s: %Zd. bytes, noblock=%d.", __FUNCTION__, count, noblock); + + mutex_lock(&uvd->lock); + + /* See if a frame is completed, then use it. */ + for(i = 0; i < USBVIDEO_NUMFRAMES; i++) { + if ((uvd->frame[i].frameState == FrameState_Done) || + (uvd->frame[i].frameState == FrameState_Done_Hold) || + (uvd->frame[i].frameState == FrameState_Error)) { + frmx = i; + break; + } + } + + /* FIXME: If we don't start a frame here then who ever does? */ + if (noblock && (frmx == -1)) { + count = -EAGAIN; + goto read_done; + } + + /* + * If no FrameState_Done, look for a FrameState_Grabbing state. + * See if a frame is in process (grabbing), then use it. + * We will need to wait until it becomes cooked, of course. + */ + if (frmx == -1) { + for(i = 0; i < USBVIDEO_NUMFRAMES; i++) { + if (uvd->frame[i].frameState == FrameState_Grabbing) { + frmx = i; + break; + } + } + } + + /* + * If no frame is active, start one. We don't care which one + * it will be, so #0 is as good as any. + * In read access mode we don't have convenience of VIDIOCMCAPTURE + * to specify the requested palette (video format) on per-frame + * basis. This means that we have to return data in -some- format + * and just hope that the client knows what to do with it. + * The default format is configured in uvd->defaultPalette field + * as one of VIDEO_PALETTE_xxx values. We stuff it into the new + * frame and initiate the frame filling process. + */ + if (frmx == -1) { + if (uvd->defaultPalette == 0) { + err("%s: No default palette; don't know what to do!", __FUNCTION__); + count = -EFAULT; + goto read_done; + } + frmx = 0; + /* + * We have no per-frame control over video size. + * Therefore we only can use whatever size was + * specified as default. + */ + uvd->frame[frmx].request = uvd->videosize; + uvd->frame[frmx].palette = uvd->defaultPalette; + uvd->frame[frmx].frameState = FrameState_Ready; + usbvideo_NewFrame(uvd, frmx); + /* Now frame 0 is supposed to start filling... */ + } + + /* + * Get a pointer to the active frame. It is either previously + * completed frame or frame in progress but not completed yet. + */ + frame = &uvd->frame[frmx]; + + /* + * Sit back & wait until the frame gets filled and postprocessed. + * If we fail to get the picture [in time] then return the error. + * In this call we specify that we want the frame to be waited for, + * postprocessed and switched into FrameState_Done_Hold state. This + * state is used to hold the frame as "fully completed" between + * subsequent partial reads of the same frame. + */ + if (frame->frameState != FrameState_Done_Hold) { + long rv = -EFAULT; + if (uvd->flags & FLAGS_NO_DECODING) + rv = usbvideo_GetFrame(uvd, frmx); + else if (VALID_CALLBACK(uvd, getFrame)) + rv = GET_CALLBACK(uvd, getFrame)(uvd, frmx); + else + err("getFrame is not set"); + if ((rv != 0) || (frame->frameState != FrameState_Done_Hold)) { + count = rv; + goto read_done; + } + } + + /* + * Copy bytes to user space. We allow for partial reads, which + * means that the user application can request read less than + * the full frame size. It is up to the application to issue + * subsequent calls until entire frame is read. + * + * First things first, make sure we don't copy more than we + * have - even if the application wants more. That would be + * a big security embarassment! + */ + if ((count + frame->seqRead_Index) > frame->seqRead_Length) + count = frame->seqRead_Length - frame->seqRead_Index; + + /* + * Copy requested amount of data to user space. We start + * copying from the position where we last left it, which + * will be zero for a new frame (not read before). + */ + if (copy_to_user(buf, frame->data + frame->seqRead_Index, count)) { + count = -EFAULT; + goto read_done; + } + + /* Update last read position */ + frame->seqRead_Index += count; + if (uvd->debug >= 1) { + err("%s: {copy} count used=%Zd, new seqRead_Index=%ld", + __FUNCTION__, count, frame->seqRead_Index); + } + + /* Finally check if the frame is done with and "release" it */ + if (frame->seqRead_Index >= frame->seqRead_Length) { + /* All data has been read */ + frame->seqRead_Index = 0; + + /* Mark it as available to be used again. */ + uvd->frame[frmx].frameState = FrameState_Unused; + if (usbvideo_NewFrame(uvd, (frmx + 1) % USBVIDEO_NUMFRAMES)) { + err("%s: usbvideo_NewFrame failed.", __FUNCTION__); + } + } +read_done: + mutex_unlock(&uvd->lock); + return count; +} + +/* + * Make all of the blocks of data contiguous + */ +static int usbvideo_CompressIsochronous(struct uvd *uvd, struct urb *urb) +{ + char *cdata; + int i, totlen = 0; + + for (i = 0; i < urb->number_of_packets; i++) { + int n = urb->iso_frame_desc[i].actual_length; + int st = urb->iso_frame_desc[i].status; + + cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + /* Detect and ignore errored packets */ + if (st < 0) { + if (uvd->debug >= 1) + err("Data error: packet=%d. len=%d. status=%d.", i, n, st); + uvd->stats.iso_err_count++; + continue; + } + + /* Detect and ignore empty packets */ + if (n <= 0) { + uvd->stats.iso_skip_count++; + continue; + } + totlen += n; /* Little local accounting */ + RingQueue_Enqueue(&uvd->dp, cdata, n); + } + return totlen; +} + +static void usbvideo_IsocIrq(struct urb *urb, struct pt_regs *regs) +{ + int i, ret, len; + struct uvd *uvd = urb->context; + + /* We don't want to do anything if we are about to be removed! */ + if (!CAMERA_IS_OPERATIONAL(uvd)) + return; +#if 0 + if (urb->actual_length > 0) { + info("urb=$%p status=%d. errcount=%d. length=%d.", + urb, urb->status, urb->error_count, urb->actual_length); + } else { + static int c = 0; + if (c++ % 100 == 0) + info("No Isoc data"); + } +#endif + + if (!uvd->streaming) { + if (uvd->debug >= 1) + info("Not streaming, but interrupt!"); + return; + } + + uvd->stats.urb_count++; + if (urb->actual_length <= 0) + goto urb_done_with; + + /* Copy the data received into ring queue */ + len = usbvideo_CompressIsochronous(uvd, urb); + uvd->stats.urb_length = len; + if (len <= 0) + goto urb_done_with; + + /* Here we got some data */ + uvd->stats.data_count += len; + RingQueue_WakeUpInterruptible(&uvd->dp); + +urb_done_with: + for (i = 0; i < FRAMES_PER_DESC; i++) { + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = 0; + } + urb->status = 0; + urb->dev = uvd->dev; + ret = usb_submit_urb (urb, GFP_KERNEL); + if(ret) + err("usb_submit_urb error (%d)", ret); + return; +} + +/* + * usbvideo_StartDataPump() + * + * History: + * 27-Jan-2000 Used ibmcam->iface, ibmcam->ifaceAltActive instead + * of hardcoded values. Simplified by using for loop, + * allowed any number of URBs. + */ +static int usbvideo_StartDataPump(struct uvd *uvd) +{ + struct usb_device *dev = uvd->dev; + int i, errFlag; + + if (uvd->debug > 1) + info("%s($%p)", __FUNCTION__, uvd); + + if (!CAMERA_IS_OPERATIONAL(uvd)) { + err("%s: Camera is not operational", __FUNCTION__); + return -EFAULT; + } + uvd->curframe = -1; + + /* Alternate interface 1 is is the biggest frame size */ + i = usb_set_interface(dev, uvd->iface, uvd->ifaceAltActive); + if (i < 0) { + err("%s: usb_set_interface error", __FUNCTION__); + uvd->last_error = i; + return -EBUSY; + } + if (VALID_CALLBACK(uvd, videoStart)) + GET_CALLBACK(uvd, videoStart)(uvd); + else + err("%s: videoStart not set", __FUNCTION__); + + /* We double buffer the Iso lists */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + int j, k; + struct urb *urb = uvd->sbuf[i].urb; + urb->dev = dev; + urb->context = uvd; + urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp); + urb->interval = 1; + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = uvd->sbuf[i].data; + urb->complete = usbvideo_IsocIrq; + urb->number_of_packets = FRAMES_PER_DESC; + urb->transfer_buffer_length = uvd->iso_packet_len * FRAMES_PER_DESC; + for (j=k=0; j < FRAMES_PER_DESC; j++, k += uvd->iso_packet_len) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = uvd->iso_packet_len; + } + } + + /* Submit all URBs */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + errFlag = usb_submit_urb(uvd->sbuf[i].urb, GFP_KERNEL); + if (errFlag) + err("%s: usb_submit_isoc(%d) ret %d", __FUNCTION__, i, errFlag); + } + + uvd->streaming = 1; + if (uvd->debug > 1) + info("%s: streaming=1 video_endp=$%02x", __FUNCTION__, uvd->video_endp); + return 0; +} + +/* + * usbvideo_StopDataPump() + * + * This procedure stops streaming and deallocates URBs. Then it + * activates zero-bandwidth alt. setting of the video interface. + * + * History: + * 22-Jan-2000 Corrected order of actions to work after surprise removal. + * 27-Jan-2000 Used uvd->iface, uvd->ifaceAltInactive instead of hardcoded values. + */ +static void usbvideo_StopDataPump(struct uvd *uvd) +{ + int i, j; + + if ((uvd == NULL) || (!uvd->streaming) || (uvd->dev == NULL)) + return; + + if (uvd->debug > 1) + info("%s($%p)", __FUNCTION__, uvd); + + /* Unschedule all of the iso td's */ + for (i=0; i < USBVIDEO_NUMSBUF; i++) { + usb_kill_urb(uvd->sbuf[i].urb); + } + if (uvd->debug > 1) + info("%s: streaming=0", __FUNCTION__); + uvd->streaming = 0; + + if (!uvd->remove_pending) { + /* Invoke minidriver's magic to stop the camera */ + if (VALID_CALLBACK(uvd, videoStop)) + GET_CALLBACK(uvd, videoStop)(uvd); + else + err("%s: videoStop not set", __FUNCTION__); + + /* Set packet size to 0 */ + j = usb_set_interface(uvd->dev, uvd->iface, uvd->ifaceAltInactive); + if (j < 0) { + err("%s: usb_set_interface() error %d.", __FUNCTION__, j); + uvd->last_error = j; + } + } +} + +/* + * usbvideo_NewFrame() + * + * History: + * 29-Mar-00 Added copying of previous frame into the current one. + * 6-Aug-00 Added model 3 video sizes, removed redundant width, height. + */ +static int usbvideo_NewFrame(struct uvd *uvd, int framenum) +{ + struct usbvideo_frame *frame; + int n; + + if (uvd->debug > 1) + info("usbvideo_NewFrame($%p,%d.)", uvd, framenum); + + /* If we're not grabbing a frame right now and the other frame is */ + /* ready to be grabbed into, then use it instead */ + if (uvd->curframe != -1) + return 0; + + /* If necessary we adjust picture settings between frames */ + if (!uvd->settingsAdjusted) { + if (VALID_CALLBACK(uvd, adjustPicture)) + GET_CALLBACK(uvd, adjustPicture)(uvd); + uvd->settingsAdjusted = 1; + } + + n = (framenum + 1) % USBVIDEO_NUMFRAMES; + if (uvd->frame[n].frameState == FrameState_Ready) + framenum = n; + + frame = &uvd->frame[framenum]; + + frame->frameState = FrameState_Grabbing; + frame->scanstate = ScanState_Scanning; + frame->seqRead_Length = 0; /* Accumulated in xxx_parse_data() */ + frame->deinterlace = Deinterlace_None; + frame->flags = 0; /* No flags yet, up to minidriver (or us) to set them */ + uvd->curframe = framenum; + + /* + * Normally we would want to copy previous frame into the current one + * before we even start filling it with data; this allows us to stop + * filling at any moment; top portion of the frame will be new and + * bottom portion will stay as it was in previous frame. If we don't + * do that then missing chunks of video stream will result in flickering + * portions of old data whatever it was before. + * + * If we choose not to copy previous frame (to, for example, save few + * bus cycles - the frame can be pretty large!) then we have an option + * to clear the frame before using. If we experience losses in this + * mode then missing picture will be black (no flickering). + * + * Finally, if user chooses not to clean the current frame before + * filling it with data then the old data will be visible if we fail + * to refill entire frame with new data. + */ + if (!(uvd->flags & FLAGS_SEPARATE_FRAMES)) { + /* This copies previous frame into this one to mask losses */ + int prev = (framenum - 1 + USBVIDEO_NUMFRAMES) % USBVIDEO_NUMFRAMES; + memmove(frame->data, uvd->frame[prev].data, uvd->max_frame_size); + } else { + if (uvd->flags & FLAGS_CLEAN_FRAMES) { + /* This provides a "clean" frame but slows things down */ + memset(frame->data, 0, uvd->max_frame_size); + } + } + return 0; +} + +/* + * usbvideo_CollectRawData() + * + * This procedure can be used instead of 'processData' callback if you + * only want to dump the raw data from the camera into the output + * device (frame buffer). You can look at it with V4L client, but the + * image will be unwatchable. The main purpose of this code and of the + * mode FLAGS_NO_DECODING is debugging and capturing of datastreams from + * new, unknown cameras. This procedure will be automatically invoked + * instead of the specified callback handler when uvd->flags has bit + * FLAGS_NO_DECODING set. Therefore, any regular build of any driver + * based on usbvideo can use this feature at any time. + */ +static void usbvideo_CollectRawData(struct uvd *uvd, struct usbvideo_frame *frame) +{ + int n; + + assert(uvd != NULL); + assert(frame != NULL); + + /* Try to move data from queue into frame buffer */ + n = RingQueue_GetLength(&uvd->dp); + if (n > 0) { + int m; + /* See how much space we have left */ + m = uvd->max_frame_size - frame->seqRead_Length; + if (n > m) + n = m; + /* Now move that much data into frame buffer */ + RingQueue_Dequeue( + &uvd->dp, + frame->data + frame->seqRead_Length, + m); + frame->seqRead_Length += m; + } + /* See if we filled the frame */ + if (frame->seqRead_Length >= uvd->max_frame_size) { + frame->frameState = FrameState_Done; + uvd->curframe = -1; + uvd->stats.frame_num++; + } +} + +static int usbvideo_GetFrame(struct uvd *uvd, int frameNum) +{ + struct usbvideo_frame *frame = &uvd->frame[frameNum]; + + if (uvd->debug >= 2) + info("%s($%p,%d.)", __FUNCTION__, uvd, frameNum); + + switch (frame->frameState) { + case FrameState_Unused: + if (uvd->debug >= 2) + info("%s: FrameState_Unused", __FUNCTION__); + return -EINVAL; + case FrameState_Ready: + case FrameState_Grabbing: + case FrameState_Error: + { + int ntries, signalPending; + redo: + if (!CAMERA_IS_OPERATIONAL(uvd)) { + if (uvd->debug >= 2) + info("%s: Camera is not operational (1)", __FUNCTION__); + return -EIO; + } + ntries = 0; + do { + RingQueue_InterruptibleSleepOn(&uvd->dp); + signalPending = signal_pending(current); + if (!CAMERA_IS_OPERATIONAL(uvd)) { + if (uvd->debug >= 2) + info("%s: Camera is not operational (2)", __FUNCTION__); + return -EIO; + } + assert(uvd->fbuf != NULL); + if (signalPending) { + if (uvd->debug >= 2) + info("%s: Signal=$%08x", __FUNCTION__, signalPending); + if (uvd->flags & FLAGS_RETRY_VIDIOCSYNC) { + usbvideo_TestPattern(uvd, 1, 0); + uvd->curframe = -1; + uvd->stats.frame_num++; + if (uvd->debug >= 2) + info("%s: Forced test pattern screen", __FUNCTION__); + return 0; + } else { + /* Standard answer: Interrupted! */ + if (uvd->debug >= 2) + info("%s: Interrupted!", __FUNCTION__); + return -EINTR; + } + } else { + /* No signals - we just got new data in dp queue */ + if (uvd->flags & FLAGS_NO_DECODING) + usbvideo_CollectRawData(uvd, frame); + else if (VALID_CALLBACK(uvd, processData)) + GET_CALLBACK(uvd, processData)(uvd, frame); + else + err("%s: processData not set", __FUNCTION__); + } + } while (frame->frameState == FrameState_Grabbing); + if (uvd->debug >= 2) { + info("%s: Grabbing done; state=%d. (%lu. bytes)", + __FUNCTION__, frame->frameState, frame->seqRead_Length); + } + if (frame->frameState == FrameState_Error) { + int ret = usbvideo_NewFrame(uvd, frameNum); + if (ret < 0) { + err("%s: usbvideo_NewFrame() failed (%d.)", __FUNCTION__, ret); + return ret; + } + goto redo; + } + /* Note that we fall through to meet our destiny below */ + } + case FrameState_Done: + /* + * Do all necessary postprocessing of data prepared in + * "interrupt" code and the collecting code above. The + * frame gets marked as FrameState_Done by queue parsing code. + * This status means that we collected enough data and + * most likely processed it as we went through. However + * the data may need postprocessing, such as deinterlacing + * or picture adjustments implemented in software (horror!) + * + * As soon as the frame becomes "final" it gets promoted to + * FrameState_Done_Hold status where it will remain until the + * caller consumed all the video data from the frame. Then + * the empty shell of ex-frame is thrown out for dogs to eat. + * But we, worried about pets, will recycle the frame! + */ + uvd->stats.frame_num++; + if ((uvd->flags & FLAGS_NO_DECODING) == 0) { + if (VALID_CALLBACK(uvd, postProcess)) + GET_CALLBACK(uvd, postProcess)(uvd, frame); + if (frame->flags & USBVIDEO_FRAME_FLAG_SOFTWARE_CONTRAST) + usbvideo_SoftwareContrastAdjustment(uvd, frame); + } + frame->frameState = FrameState_Done_Hold; + if (uvd->debug >= 2) + info("%s: Entered FrameState_Done_Hold state.", __FUNCTION__); + return 0; + + case FrameState_Done_Hold: + /* + * We stay in this state indefinitely until someone external, + * like ioctl() or read() call finishes digesting the frame + * data. Then it will mark the frame as FrameState_Unused and + * it will be released back into the wild to roam freely. + */ + if (uvd->debug >= 2) + info("%s: FrameState_Done_Hold state.", __FUNCTION__); + return 0; + } + + /* Catch-all for other cases. We shall not be here. */ + err("%s: Invalid state %d.", __FUNCTION__, frame->frameState); + frame->frameState = FrameState_Unused; + return 0; +} + +/* + * usbvideo_DeinterlaceFrame() + * + * This procedure deinterlaces the given frame. Some cameras produce + * only half of scanlines - sometimes only even lines, sometimes only + * odd lines. The deinterlacing method is stored in frame->deinterlace + * variable. + * + * Here we scan the frame vertically and replace missing scanlines with + * average between surrounding ones - before and after. If we have no + * line above then we just copy next line. Similarly, if we need to + * create a last line then preceding line is used. + */ +void usbvideo_DeinterlaceFrame(struct uvd *uvd, struct usbvideo_frame *frame) +{ + if ((uvd == NULL) || (frame == NULL)) + return; + + if ((frame->deinterlace == Deinterlace_FillEvenLines) || + (frame->deinterlace == Deinterlace_FillOddLines)) + { + const int v4l_linesize = VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL; + int i = (frame->deinterlace == Deinterlace_FillEvenLines) ? 0 : 1; + + for (; i < VIDEOSIZE_Y(frame->request); i += 2) { + const unsigned char *fs1, *fs2; + unsigned char *fd; + int ip, in, j; /* Previous and next lines */ + + /* + * Need to average lines before and after 'i'. + * If we go out of bounds seeking those lines then + * we point back to existing line. + */ + ip = i - 1; /* First, get rough numbers */ + in = i + 1; + + /* Now validate */ + if (ip < 0) + ip = in; + if (in >= VIDEOSIZE_Y(frame->request)) + in = ip; + + /* Sanity check */ + if ((ip < 0) || (in < 0) || + (ip >= VIDEOSIZE_Y(frame->request)) || + (in >= VIDEOSIZE_Y(frame->request))) + { + err("Error: ip=%d. in=%d. req.height=%ld.", + ip, in, VIDEOSIZE_Y(frame->request)); + break; + } + + /* Now we need to average lines 'ip' and 'in' to produce line 'i' */ + fs1 = frame->data + (v4l_linesize * ip); + fs2 = frame->data + (v4l_linesize * in); + fd = frame->data + (v4l_linesize * i); + + /* Average lines around destination */ + for (j=0; j < v4l_linesize; j++) { + fd[j] = (unsigned char)((((unsigned) fs1[j]) + + ((unsigned)fs2[j])) >> 1); + } + } + } + + /* Optionally display statistics on the screen */ + if (uvd->flags & FLAGS_OVERLAY_STATS) + usbvideo_OverlayStats(uvd, frame); +} + +EXPORT_SYMBOL(usbvideo_DeinterlaceFrame); + +/* + * usbvideo_SoftwareContrastAdjustment() + * + * This code adjusts the contrast of the frame, assuming RGB24 format. + * As most software image processing, this job is CPU-intensive. + * Get a camera that supports hardware adjustment! + * + * History: + * 09-Feb-2001 Created. + */ +static void usbvideo_SoftwareContrastAdjustment(struct uvd *uvd, + struct usbvideo_frame *frame) +{ + int i, j, v4l_linesize; + signed long adj; + const int ccm = 128; /* Color correction median - see below */ + + if ((uvd == NULL) || (frame == NULL)) { + err("%s: Illegal call.", __FUNCTION__); + return; + } + adj = (uvd->vpic.contrast - 0x8000) >> 8; /* -128..+127 = -ccm..+(ccm-1)*/ + RESTRICT_TO_RANGE(adj, -ccm, ccm+1); + if (adj == 0) { + /* In rare case of no adjustment */ + return; + } + v4l_linesize = VIDEOSIZE_X(frame->request) * V4L_BYTES_PER_PIXEL; + for (i=0; i < VIDEOSIZE_Y(frame->request); i++) { + unsigned char *fd = frame->data + (v4l_linesize * i); + for (j=0; j < v4l_linesize; j++) { + signed long v = (signed long) fd[j]; + /* Magnify up to 2 times, reduce down to zero */ + v = 128 + ((ccm + adj) * (v - 128)) / ccm; + RESTRICT_TO_RANGE(v, 0, 0xFF); /* Must flatten tails */ + fd[j] = (unsigned char) v; + } + } +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/usbvideo/usbvideo.h b/drivers/media/video/usbvideo/usbvideo.h new file mode 100644 index 000000000000..135433c2680a --- /dev/null +++ b/drivers/media/video/usbvideo/usbvideo.h @@ -0,0 +1,394 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef usbvideo_h +#define usbvideo_h + +#include <linux/config.h> +#include <linux/videodev.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +/* Most helpful debugging aid */ +#define assert(expr) ((void) ((expr) ? 0 : (err("assert failed at line %d",__LINE__)))) + +#define USBVIDEO_REPORT_STATS 1 /* Set to 0 to block statistics on close */ + +/* Bit flags (options) */ +#define FLAGS_RETRY_VIDIOCSYNC (1 << 0) +#define FLAGS_MONOCHROME (1 << 1) +#define FLAGS_DISPLAY_HINTS (1 << 2) +#define FLAGS_OVERLAY_STATS (1 << 3) +#define FLAGS_FORCE_TESTPATTERN (1 << 4) +#define FLAGS_SEPARATE_FRAMES (1 << 5) +#define FLAGS_CLEAN_FRAMES (1 << 6) +#define FLAGS_NO_DECODING (1 << 7) + +/* Bit flags for frames (apply to the frame where they are specified) */ +#define USBVIDEO_FRAME_FLAG_SOFTWARE_CONTRAST (1 << 0) + +/* Camera capabilities (maximum) */ +#define CAMERA_URB_FRAMES 32 +#define CAMERA_MAX_ISO_PACKET 1023 /* 1022 actually sent by camera */ +#define FRAMES_PER_DESC (CAMERA_URB_FRAMES) +#define FRAME_SIZE_PER_DESC (CAMERA_MAX_ISO_PACKET) + +/* This macro restricts an int variable to an inclusive range */ +#define RESTRICT_TO_RANGE(v,mi,ma) { if ((v) < (mi)) (v) = (mi); else if ((v) > (ma)) (v) = (ma); } + +#define V4L_BYTES_PER_PIXEL 3 /* Because we produce RGB24 */ + +/* + * Use this macro to construct constants for different video sizes. + * We have to deal with different video sizes that have to be + * configured in the device or compared against when we receive + * a data. Normally one would define a bunch of VIDEOSIZE_x_by_y + * #defines and that's the end of story. However this solution + * does not allow to convert between real pixel sizes and the + * constant (integer) value that may be used to tag a frame or + * whatever. The set of macros below constructs videosize constants + * from the pixel size and allows to reconstruct the pixel size + * from the combined value later. + */ +#define VIDEOSIZE(x,y) (((x) & 0xFFFFL) | (((y) & 0xFFFFL) << 16)) +#define VIDEOSIZE_X(vs) ((vs) & 0xFFFFL) +#define VIDEOSIZE_Y(vs) (((vs) >> 16) & 0xFFFFL) +typedef unsigned long videosize_t; + +/* + * This macro checks if the camera is still operational. The 'uvd' + * pointer must be valid, uvd->dev must be valid, we are not + * removing the device and the device has not erred on us. + */ +#define CAMERA_IS_OPERATIONAL(uvd) (\ + (uvd != NULL) && \ + ((uvd)->dev != NULL) && \ + ((uvd)->last_error == 0) && \ + (!(uvd)->remove_pending)) + +/* + * We use macros to do YUV -> RGB conversion because this is + * very important for speed and totally unimportant for size. + * + * YUV -> RGB Conversion + * --------------------- + * + * B = 1.164*(Y-16) + 2.018*(V-128) + * G = 1.164*(Y-16) - 0.813*(U-128) - 0.391*(V-128) + * R = 1.164*(Y-16) + 1.596*(U-128) + * + * If you fancy integer arithmetics (as you should), hear this: + * + * 65536*B = 76284*(Y-16) + 132252*(V-128) + * 65536*G = 76284*(Y-16) - 53281*(U-128) - 25625*(V-128) + * 65536*R = 76284*(Y-16) + 104595*(U-128) + * + * Make sure the output values are within [0..255] range. + */ +#define LIMIT_RGB(x) (((x) < 0) ? 0 : (((x) > 255) ? 255 : (x))) +#define YUV_TO_RGB_BY_THE_BOOK(my,mu,mv,mr,mg,mb) { \ + int mm_y, mm_yc, mm_u, mm_v, mm_r, mm_g, mm_b; \ + mm_y = (my) - 16; \ + mm_u = (mu) - 128; \ + mm_v = (mv) - 128; \ + mm_yc= mm_y * 76284; \ + mm_b = (mm_yc + 132252*mm_v ) >> 16; \ + mm_g = (mm_yc - 53281*mm_u - 25625*mm_v ) >> 16; \ + mm_r = (mm_yc + 104595*mm_u ) >> 16; \ + mb = LIMIT_RGB(mm_b); \ + mg = LIMIT_RGB(mm_g); \ + mr = LIMIT_RGB(mm_r); \ +} + +#define RING_QUEUE_SIZE (128*1024) /* Must be a power of 2 */ +#define RING_QUEUE_ADVANCE_INDEX(rq,ind,n) (rq)->ind = ((rq)->ind + (n)) & ((rq)->length-1) +#define RING_QUEUE_DEQUEUE_BYTES(rq,n) RING_QUEUE_ADVANCE_INDEX(rq,ri,n) +#define RING_QUEUE_PEEK(rq,ofs) ((rq)->queue[((ofs) + (rq)->ri) & ((rq)->length-1)]) + +struct RingQueue { + unsigned char *queue; /* Data from the Isoc data pump */ + int length; /* How many bytes allocated for the queue */ + int wi; /* That's where we write */ + int ri; /* Read from here until you hit write index */ + wait_queue_head_t wqh; /* Processes waiting */ +}; + +enum ScanState { + ScanState_Scanning, /* Scanning for header */ + ScanState_Lines /* Parsing lines */ +}; + +/* Completion states of the data parser */ +enum ParseState { + scan_Continue, /* Just parse next item */ + scan_NextFrame, /* Frame done, send it to V4L */ + scan_Out, /* Not enough data for frame */ + scan_EndParse /* End parsing */ +}; + +enum FrameState { + FrameState_Unused, /* Unused (no MCAPTURE) */ + FrameState_Ready, /* Ready to start grabbing */ + FrameState_Grabbing, /* In the process of being grabbed into */ + FrameState_Done, /* Finished grabbing, but not been synced yet */ + FrameState_Done_Hold, /* Are syncing or reading */ + FrameState_Error, /* Something bad happened while processing */ +}; + +/* + * Some frames may contain only even or odd lines. This type + * specifies what type of deinterlacing is required. + */ +enum Deinterlace { + Deinterlace_None=0, + Deinterlace_FillOddLines, + Deinterlace_FillEvenLines +}; + +#define USBVIDEO_NUMFRAMES 2 /* How many frames we work with */ +#define USBVIDEO_NUMSBUF 2 /* How many URBs linked in a ring */ + +/* This structure represents one Isoc request - URB and buffer */ +struct usbvideo_sbuf { + char *data; + struct urb *urb; +}; + +struct usbvideo_frame { + char *data; /* Frame buffer */ + unsigned long header; /* Significant bits from the header */ + + videosize_t canvas; /* The canvas (max. image) allocated */ + videosize_t request; /* That's what the application asked for */ + unsigned short palette; /* The desired format */ + + enum FrameState frameState;/* State of grabbing */ + enum ScanState scanstate; /* State of scanning */ + enum Deinterlace deinterlace; + int flags; /* USBVIDEO_FRAME_FLAG_xxx bit flags */ + + int curline; /* Line of frame we're working on */ + + long seqRead_Length; /* Raw data length of frame */ + long seqRead_Index; /* Amount of data that has been already read */ + + void *user; /* Additional data that user may need */ +}; + +/* Statistics that can be overlaid on screen */ +struct usbvideo_statistics { + unsigned long frame_num; /* Sequential number of the frame */ + unsigned long urb_count; /* How many URBs we received so far */ + unsigned long urb_length; /* Length of last URB */ + unsigned long data_count; /* How many bytes we received */ + unsigned long header_count; /* How many frame headers we found */ + unsigned long iso_skip_count; /* How many empty ISO packets received */ + unsigned long iso_err_count; /* How many bad ISO packets received */ +}; + +struct usbvideo; + +struct uvd { + struct video_device vdev; /* Must be the first field! */ + struct usb_device *dev; + struct usbvideo *handle; /* Points back to the struct usbvideo */ + void *user_data; /* Camera-dependent data */ + int user_size; /* Size of that camera-dependent data */ + int debug; /* Debug level for usbvideo */ + unsigned char iface; /* Video interface number */ + unsigned char video_endp; + unsigned char ifaceAltActive; + unsigned char ifaceAltInactive; /* Alt settings */ + unsigned long flags; /* FLAGS_USBVIDEO_xxx */ + unsigned long paletteBits; /* Which palettes we accept? */ + unsigned short defaultPalette; /* What palette to use for read() */ + struct mutex lock; + int user; /* user count for exclusive use */ + + videosize_t videosize; /* Current setting */ + videosize_t canvas; /* This is the width,height of the V4L canvas */ + int max_frame_size; /* Bytes in one video frame */ + + int uvd_used; /* Is this structure in use? */ + int streaming; /* Are we streaming Isochronous? */ + int grabbing; /* Are we grabbing? */ + int settingsAdjusted; /* Have we adjusted contrast etc.? */ + int last_error; /* What calamity struck us? */ + + char *fbuf; /* Videodev buffer area */ + int fbuf_size; /* Videodev buffer size */ + + int curframe; + int iso_packet_len; /* Videomode-dependent, saves bus bandwidth */ + + struct RingQueue dp; /* Isoc data pump */ + struct usbvideo_frame frame[USBVIDEO_NUMFRAMES]; + struct usbvideo_sbuf sbuf[USBVIDEO_NUMSBUF]; + + volatile int remove_pending; /* If set then about to exit */ + + struct video_picture vpic, vpic_old; /* Picture settings */ + struct video_capability vcap; /* Video capabilities */ + struct video_channel vchan; /* May be used for tuner support */ + struct usbvideo_statistics stats; + char videoName[32]; /* Holds name like "video7" */ +}; + +/* + * usbvideo callbacks (virtual methods). They are set when usbvideo + * services are registered. All of these default to NULL, except those + * that default to usbvideo-provided methods. + */ +struct usbvideo_cb { + int (*probe)(struct usb_interface *, const struct usb_device_id *); + void (*userFree)(struct uvd *); + void (*disconnect)(struct usb_interface *); + int (*setupOnOpen)(struct uvd *); + void (*videoStart)(struct uvd *); + void (*videoStop)(struct uvd *); + void (*processData)(struct uvd *, struct usbvideo_frame *); + void (*postProcess)(struct uvd *, struct usbvideo_frame *); + void (*adjustPicture)(struct uvd *); + int (*getFPS)(struct uvd *); + int (*overlayHook)(struct uvd *, struct usbvideo_frame *); + int (*getFrame)(struct uvd *, int); + int (*startDataPump)(struct uvd *uvd); + void (*stopDataPump)(struct uvd *uvd); + int (*setVideoMode)(struct uvd *uvd, struct video_window *vw); +}; + +struct usbvideo { + int num_cameras; /* As allocated */ + struct usb_driver usbdrv; /* Interface to the USB stack */ + char drvName[80]; /* Driver name */ + struct mutex lock; /* Mutex protecting camera structures */ + struct usbvideo_cb cb; /* Table of callbacks (virtual methods) */ + struct video_device vdt; /* Video device template */ + struct uvd *cam; /* Array of camera structures */ + struct module *md_module; /* Minidriver module */ +}; + + +/* + * This macro retrieves callback address from the struct uvd object. + * No validity checks are done here, so be sure to check the + * callback beforehand with VALID_CALLBACK. + */ +#define GET_CALLBACK(uvd,cbName) ((uvd)->handle->cb.cbName) + +/* + * This macro returns either callback pointer or NULL. This is safe + * macro, meaning that most of components of data structures involved + * may be NULL - this only results in NULL being returned. You may + * wish to use this macro to make sure that the callback is callable. + * However keep in mind that those checks take time. + */ +#define VALID_CALLBACK(uvd,cbName) ((((uvd) != NULL) && \ + ((uvd)->handle != NULL)) ? GET_CALLBACK(uvd,cbName) : NULL) + +int RingQueue_Dequeue(struct RingQueue *rq, unsigned char *dst, int len); +int RingQueue_Enqueue(struct RingQueue *rq, const unsigned char *cdata, int n); +void RingQueue_WakeUpInterruptible(struct RingQueue *rq); +void RingQueue_Flush(struct RingQueue *rq); + +static inline int RingQueue_GetLength(const struct RingQueue *rq) +{ + return (rq->wi - rq->ri + rq->length) & (rq->length-1); +} + +static inline int RingQueue_GetFreeSpace(const struct RingQueue *rq) +{ + return rq->length - RingQueue_GetLength(rq); +} + +void usbvideo_DrawLine( + struct usbvideo_frame *frame, + int x1, int y1, + int x2, int y2, + unsigned char cr, unsigned char cg, unsigned char cb); +void usbvideo_HexDump(const unsigned char *data, int len); +void usbvideo_SayAndWait(const char *what); +void usbvideo_TestPattern(struct uvd *uvd, int fullframe, int pmode); + +/* Memory allocation routines */ +unsigned long usbvideo_kvirt_to_pa(unsigned long adr); + +int usbvideo_register( + struct usbvideo **pCams, + const int num_cams, + const int num_extra, + const char *driverName, + const struct usbvideo_cb *cbTable, + struct module *md, + const struct usb_device_id *id_table); +struct uvd *usbvideo_AllocateDevice(struct usbvideo *cams); +int usbvideo_RegisterVideoDevice(struct uvd *uvd); +void usbvideo_Deregister(struct usbvideo **uvt); + +int usbvideo_v4l_initialize(struct video_device *dev); + +void usbvideo_DeinterlaceFrame(struct uvd *uvd, struct usbvideo_frame *frame); + +/* + * This code performs bounds checking - use it when working with + * new formats, or else you may get oopses all over the place. + * If pixel falls out of bounds then it gets shoved back (as close + * to place of offence as possible) and is painted bright red. + * + * There are two important concepts: frame width, height and + * V4L canvas width, height. The former is the area requested by + * the application -for this very frame-. The latter is the largest + * possible frame that we can serve (we advertise that via V4L ioctl). + * The frame data is expected to be formatted as lines of length + * VIDEOSIZE_X(fr->request), total VIDEOSIZE_Y(frame->request) lines. + */ +static inline void RGB24_PUTPIXEL( + struct usbvideo_frame *fr, + int ix, int iy, + unsigned char vr, + unsigned char vg, + unsigned char vb) +{ + register unsigned char *pf; + int limiter = 0, mx, my; + mx = ix; + my = iy; + if (mx < 0) { + mx=0; + limiter++; + } else if (mx >= VIDEOSIZE_X((fr)->request)) { + mx= VIDEOSIZE_X((fr)->request) - 1; + limiter++; + } + if (my < 0) { + my = 0; + limiter++; + } else if (my >= VIDEOSIZE_Y((fr)->request)) { + my = VIDEOSIZE_Y((fr)->request) - 1; + limiter++; + } + pf = (fr)->data + V4L_BYTES_PER_PIXEL*((iy)*VIDEOSIZE_X((fr)->request) + (ix)); + if (limiter) { + *pf++ = 0; + *pf++ = 0; + *pf++ = 0xFF; + } else { + *pf++ = (vb); + *pf++ = (vg); + *pf++ = (vr); + } +} + +#endif /* usbvideo_h */ diff --git a/drivers/media/video/usbvideo/vicam.c b/drivers/media/video/usbvideo/vicam.c new file mode 100644 index 000000000000..1d06e53ec7c5 --- /dev/null +++ b/drivers/media/video/usbvideo/vicam.c @@ -0,0 +1,1411 @@ +/* + * USB ViCam WebCam driver + * Copyright (c) 2002 Joe Burks (jburks@wavicle.org), + * Christopher L Cheney (ccheney@cheney.cx), + * Pavel Machek (pavel@suse.cz), + * John Tyner (jtyner@cs.ucr.edu), + * Monroe Williams (monroe@pobox.com) + * + * Supports 3COM HomeConnect PC Digital WebCam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This source code is based heavily on the CPiA webcam driver which was + * written by Peter Pregler, Scott J. Bertin and Johannes Erdfelt + * + * Portions of this code were also copied from usbvideo.c + * + * Special thanks to the the whole team at Sourceforge for help making + * this driver become a reality. Notably: + * Andy Armstrong who reverse engineered the color encoding and + * Pavel Machek and Chris Cheney who worked on reverse engineering the + * camera controls and wrote the first generation driver. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/videodev.h> +#include <linux/usb.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/mutex.h> +#include "usbvideo.h" + +// #define VICAM_DEBUG + +#ifdef VICAM_DEBUG +#define ADBG(lineno,fmt,args...) printk(fmt, jiffies, __FUNCTION__, lineno, ##args) +#define DBG(fmt,args...) ADBG((__LINE__),KERN_DEBUG __FILE__"(%ld):%s (%d):"fmt,##args) +#else +#define DBG(fmn,args...) do {} while(0) +#endif + +#define DRIVER_AUTHOR "Joe Burks, jburks@wavicle.org" +#define DRIVER_DESC "ViCam WebCam Driver" + +/* Define these values to match your device */ +#define USB_VICAM_VENDOR_ID 0x04c1 +#define USB_VICAM_PRODUCT_ID 0x009d + +#define VICAM_BYTES_PER_PIXEL 3 +#define VICAM_MAX_READ_SIZE (512*242+128) +#define VICAM_MAX_FRAME_SIZE (VICAM_BYTES_PER_PIXEL*320*240) +#define VICAM_FRAMES 2 + +#define VICAM_HEADER_SIZE 64 + +#define clamp( x, l, h ) max_t( __typeof__( x ), \ + ( l ), \ + min_t( __typeof__( x ), \ + ( h ), \ + ( x ) ) ) + +/* Not sure what all the bytes in these char + * arrays do, but they're necessary to make + * the camera work. + */ + +static unsigned char setup1[] = { + 0xB6, 0xC3, 0x1F, 0x00, 0x02, 0x64, 0xE7, 0x67, + 0xFD, 0xFF, 0x0E, 0xC0, 0xE7, 0x09, 0xDE, 0x00, + 0x8E, 0x00, 0xC0, 0x09, 0x40, 0x03, 0xC0, 0x17, + 0x44, 0x03, 0x4B, 0xAF, 0xC0, 0x07, 0x00, 0x00, + 0x4B, 0xAF, 0x97, 0xCF, 0x00, 0x00 +}; + +static unsigned char setup2[] = { + 0xB6, 0xC3, 0x03, 0x00, 0x03, 0x64, 0x18, 0x00, + 0x00, 0x00 +}; + +static unsigned char setup3[] = { + 0xB6, 0xC3, 0x01, 0x00, 0x06, 0x64, 0x00, 0x00 +}; + +static unsigned char setup4[] = { + 0xB6, 0xC3, 0x8F, 0x06, 0x02, 0x64, 0xE7, 0x07, + 0x00, 0x00, 0x08, 0xC0, 0xE7, 0x07, 0x00, 0x00, + 0x3E, 0xC0, 0xE7, 0x07, 0x54, 0x01, 0xAA, 0x00, + 0xE7, 0x07, 0xC8, 0x05, 0xB6, 0x00, 0xE7, 0x07, + 0x42, 0x01, 0xD2, 0x00, 0xE7, 0x07, 0x7C, 0x00, + 0x16, 0x00, 0xE7, 0x07, 0x56, 0x00, 0x18, 0x00, + 0xE7, 0x07, 0x06, 0x00, 0x92, 0xC0, 0xE7, 0x07, + 0x00, 0x00, 0x1E, 0xC0, 0xE7, 0x07, 0xFF, 0xFF, + 0x22, 0xC0, 0xE7, 0x07, 0x04, 0x00, 0x24, 0xC0, + 0xE7, 0x07, 0xEC, 0x27, 0x28, 0xC0, 0xE7, 0x07, + 0x16, 0x01, 0x8E, 0x00, 0xE7, 0x87, 0x01, 0x00, + 0x0E, 0xC0, 0x97, 0xCF, 0xD7, 0x09, 0x00, 0xC0, + 0xE7, 0x77, 0x01, 0x00, 0x92, 0xC0, 0x09, 0xC1, + 0xE7, 0x09, 0xFE, 0x05, 0x24, 0x01, 0xE7, 0x09, + 0x04, 0x06, 0x26, 0x01, 0xE7, 0x07, 0x07, 0x00, + 0x92, 0xC0, 0xE7, 0x05, 0x00, 0xC0, 0xC0, 0xDF, + 0x97, 0xCF, 0x17, 0x00, 0x57, 0x00, 0x17, 0x02, + 0xD7, 0x09, 0x00, 0xC0, 0xE7, 0x77, 0x01, 0x00, + 0x92, 0xC0, 0x0A, 0xC1, 0xE7, 0x57, 0xFF, 0xFF, + 0xFA, 0x05, 0x0D, 0xC0, 0xE7, 0x57, 0x00, 0x00, + 0xFA, 0x05, 0x0F, 0xC0, 0x9F, 0xAF, 0xC6, 0x00, + 0xE7, 0x05, 0x00, 0xC0, 0xC8, 0x05, 0xC1, 0x05, + 0xC0, 0x05, 0xC0, 0xDF, 0x97, 0xCF, 0x27, 0xDA, + 0xFA, 0x05, 0xEF, 0x07, 0x01, 0x00, 0x0B, 0x06, + 0x73, 0xCF, 0x9F, 0xAF, 0x78, 0x01, 0x9F, 0xAF, + 0x1A, 0x03, 0x6E, 0xCF, 0xE7, 0x09, 0xFC, 0x05, + 0x24, 0x01, 0xE7, 0x09, 0x02, 0x06, 0x26, 0x01, + 0xE7, 0x07, 0x07, 0x00, 0x92, 0xC0, 0xE7, 0x09, + 0xFC, 0x05, 0xFE, 0x05, 0xE7, 0x09, 0x02, 0x06, + 0x04, 0x06, 0xE7, 0x09, 0x00, 0x06, 0xFC, 0x05, + 0xE7, 0x09, 0xFE, 0x05, 0x00, 0x06, 0x27, 0xDA, + 0xFA, 0x05, 0xE7, 0x57, 0x01, 0x00, 0xFA, 0x05, + 0x02, 0xCA, 0x04, 0xC0, 0x97, 0xCF, 0x9F, 0xAF, + 0x66, 0x05, 0x97, 0xCF, 0xE7, 0x07, 0x40, 0x00, + 0x02, 0x06, 0xC8, 0x09, 0xFC, 0x05, 0x9F, 0xAF, + 0xDA, 0x02, 0x97, 0xCF, 0xCF, 0x17, 0x02, 0x00, + 0xEF, 0x57, 0x81, 0x00, 0x09, 0x06, 0x9F, 0xA0, + 0xB6, 0x01, 0xEF, 0x57, 0x80, 0x00, 0x09, 0x06, + 0x9F, 0xA0, 0x40, 0x02, 0xEF, 0x57, 0x01, 0x00, + 0x0B, 0x06, 0x9F, 0xA0, 0x46, 0x03, 0xE7, 0x07, + 0x01, 0x00, 0x0A, 0xC0, 0x46, 0xAF, 0x47, 0xAF, + 0x9F, 0xAF, 0x40, 0x02, 0xE7, 0x07, 0x2E, 0x00, + 0x0A, 0xC0, 0xEF, 0x87, 0x80, 0x00, 0x09, 0x06, + 0x97, 0xCF, 0x00, 0x0E, 0x01, 0x00, 0xC0, 0x57, + 0x51, 0x00, 0x9F, 0xC0, 0x9E, 0x02, 0xC0, 0x57, + 0x50, 0x00, 0x20, 0xC0, 0xC0, 0x57, 0x55, 0x00, + 0x12, 0xC0, 0xC0, 0x57, 0x56, 0x00, 0x9F, 0xC0, + 0x72, 0x02, 0x9F, 0xCF, 0xD6, 0x02, 0xC1, 0x0B, + 0x08, 0x06, 0x01, 0xD0, 0x6F, 0x90, 0x08, 0x06, + 0xC0, 0x07, 0x08, 0x00, 0xC1, 0x0B, 0x08, 0x06, + 0x9F, 0xAF, 0x28, 0x05, 0x97, 0xCF, 0x2F, 0x0E, + 0x02, 0x00, 0x08, 0x06, 0xC0, 0x07, 0x08, 0x00, + 0xC1, 0x0B, 0x08, 0x06, 0x9F, 0xAF, 0x28, 0x05, + 0x9F, 0xCF, 0xD6, 0x02, 0x2F, 0x0E, 0x02, 0x00, + 0x09, 0x06, 0xEF, 0x87, 0x80, 0x00, 0x09, 0x06, + 0x9F, 0xCF, 0xD6, 0x02, 0xEF, 0x67, 0x7F, 0xFF, + 0x09, 0x06, 0xE7, 0x67, 0xFF, 0xFD, 0x22, 0xC0, + 0xE7, 0x67, 0xEF, 0xFF, 0x24, 0xC0, 0xE7, 0x87, + 0x10, 0x00, 0x28, 0xC0, 0x9F, 0xAF, 0xB8, 0x05, + 0xE7, 0x87, 0xE0, 0x21, 0x24, 0xC0, 0x9F, 0xAF, + 0xA8, 0x05, 0xE7, 0x87, 0x08, 0x00, 0x24, 0xC0, + 0xE7, 0x67, 0xDF, 0xFF, 0x24, 0xC0, 0xC8, 0x07, + 0x0A, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xC1, 0x07, + 0x01, 0x00, 0x9F, 0xAF, 0x28, 0x05, 0x9F, 0xAF, + 0xB8, 0x05, 0xC0, 0x07, 0x9E, 0x00, 0x9F, 0xAF, + 0x44, 0x05, 0xE7, 0x67, 0xFF, 0xFE, 0x24, 0xC0, + 0xC0, 0x09, 0x20, 0xC0, 0xE7, 0x87, 0x00, 0x01, + 0x24, 0xC0, 0xC0, 0x77, 0x00, 0x02, 0x0F, 0xC1, + 0xE7, 0x67, 0xF7, 0xFF, 0x24, 0xC0, 0xE7, 0x67, + 0xF7, 0xFF, 0x24, 0xC0, 0xE7, 0x87, 0x08, 0x00, + 0x24, 0xC0, 0x08, 0xDA, 0x5E, 0xC1, 0xEF, 0x07, + 0x80, 0x00, 0x09, 0x06, 0x97, 0xCF, 0xEF, 0x07, + 0x01, 0x00, 0x0A, 0x06, 0x97, 0xCF, 0xEF, 0x07, + 0x00, 0x00, 0x0B, 0x06, 0xEF, 0x07, 0x00, 0x00, + 0x0A, 0x06, 0xEF, 0x67, 0x7F, 0xFF, 0x09, 0x06, + 0xEF, 0x07, 0x00, 0x00, 0x0D, 0x06, 0xE7, 0x67, + 0xEF, 0xFF, 0x28, 0xC0, 0xE7, 0x67, 0x17, 0xD8, + 0x24, 0xC0, 0xE7, 0x07, 0x00, 0x00, 0x1E, 0xC0, + 0xE7, 0x07, 0xFF, 0xFF, 0x22, 0xC0, 0x97, 0xCF, + 0xC8, 0x07, 0x0E, 0x06, 0x9F, 0xAF, 0xDA, 0x02, + 0xE7, 0x07, 0x00, 0x00, 0xF2, 0x05, 0xE7, 0x07, + 0x10, 0x00, 0xF6, 0x05, 0xE7, 0x07, 0x0E, 0x06, + 0xF4, 0x05, 0xE7, 0x07, 0xD6, 0x02, 0xF8, 0x05, + 0xC8, 0x07, 0xF2, 0x05, 0xC1, 0x07, 0x00, 0x80, + 0x50, 0xAF, 0x97, 0xCF, 0x2F, 0x0C, 0x02, 0x00, + 0x07, 0x06, 0x2F, 0x0C, 0x04, 0x00, 0x06, 0x06, + 0xE7, 0x07, 0x00, 0x00, 0xF2, 0x05, 0xE7, 0x07, + 0x10, 0x00, 0xF6, 0x05, 0xE7, 0x07, 0xE2, 0x05, + 0xF4, 0x05, 0xE7, 0x07, 0xCE, 0x02, 0xF8, 0x05, + 0xC8, 0x07, 0xF2, 0x05, 0xC1, 0x07, 0x00, 0x80, + 0x51, 0xAF, 0x97, 0xCF, 0x9F, 0xAF, 0x66, 0x04, + 0x9F, 0xAF, 0x1A, 0x03, 0x59, 0xAF, 0x97, 0xCF, + 0xC0, 0x07, 0x0E, 0x00, 0xC1, 0x0B, 0x0C, 0x06, + 0x41, 0xD1, 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, + 0x3C, 0x00, 0x9F, 0xAF, 0x44, 0x05, 0x68, 0x00, + 0xC0, 0x07, 0x3B, 0x00, 0x9F, 0xAF, 0x44, 0x05, + 0x6F, 0x00, 0x0C, 0x06, 0x68, 0x00, 0xE0, 0x07, + 0x04, 0x01, 0xE8, 0x0B, 0x0A, 0x06, 0xE8, 0x07, + 0x00, 0x00, 0xE0, 0x07, 0x00, 0x02, 0xE0, 0x07, + 0xEC, 0x01, 0xE0, 0x07, 0xFC, 0xFF, 0x97, 0xCF, + 0xE7, 0x07, 0xFF, 0xFF, 0xFA, 0x05, 0xEF, 0x07, + 0x00, 0x00, 0x0B, 0x06, 0xE7, 0x07, 0x0E, 0x06, + 0x24, 0x01, 0xE7, 0x07, 0x0E, 0x06, 0xFE, 0x05, + 0xE7, 0x07, 0x40, 0x00, 0x26, 0x01, 0xE7, 0x07, + 0x40, 0x00, 0x04, 0x06, 0xE7, 0x07, 0x07, 0x00, + 0x92, 0xC0, 0x97, 0xCF, 0xEF, 0x07, 0x02, 0x00, + 0x0B, 0x06, 0x9F, 0xAF, 0x78, 0x01, 0xEF, 0x77, + 0x80, 0x00, 0x07, 0x06, 0x9F, 0xC0, 0x14, 0x04, + 0xEF, 0x77, 0x01, 0x00, 0x07, 0x06, 0x37, 0xC0, + 0xEF, 0x77, 0x01, 0x00, 0x0D, 0x06, 0x0F, 0xC1, + 0xEF, 0x07, 0x01, 0x00, 0x0D, 0x06, 0xC0, 0x07, + 0x02, 0x00, 0xC1, 0x07, 0x30, 0x00, 0x9F, 0xAF, + 0x28, 0x05, 0xC0, 0x07, 0x01, 0x00, 0xC1, 0x07, + 0x02, 0x00, 0x9F, 0xAF, 0x28, 0x05, 0xC8, 0x07, + 0xFF, 0x4F, 0x9F, 0xAF, 0xA8, 0x05, 0xC0, 0x07, + 0x38, 0x00, 0x9F, 0xAF, 0x44, 0x05, 0xC1, 0x77, + 0x03, 0x00, 0x02, 0xC1, 0x08, 0xDA, 0x75, 0xC1, + 0xC1, 0x77, 0x01, 0x00, 0x0A, 0xC1, 0xC0, 0x07, + 0x01, 0x00, 0xC1, 0x07, 0x02, 0x00, 0x9F, 0xAF, + 0x28, 0x05, 0xEF, 0x07, 0x01, 0x00, 0x06, 0x06, + 0x2C, 0xCF, 0xC0, 0x07, 0x01, 0x00, 0xC1, 0x07, + 0x04, 0x00, 0x9F, 0xAF, 0x28, 0x05, 0xEF, 0x07, + 0x00, 0x00, 0x06, 0x06, 0x22, 0xCF, 0xEF, 0x07, + 0x00, 0x00, 0x0D, 0x06, 0xEF, 0x57, 0x01, 0x00, + 0x06, 0x06, 0x1B, 0xC0, 0xC0, 0x07, 0x01, 0x00, + 0xC1, 0x07, 0x01, 0x00, 0x9F, 0xAF, 0x28, 0x05, + 0xC0, 0x07, 0x02, 0x00, 0xC1, 0x07, 0x30, 0x00, + 0x9F, 0xAF, 0x28, 0x05, 0xC8, 0x07, 0xFF, 0x4F, + 0x9F, 0xAF, 0xA8, 0x05, 0xC0, 0x07, 0x38, 0x00, + 0x9F, 0xAF, 0x44, 0x05, 0xC1, 0x67, 0x03, 0x00, + 0xC1, 0x57, 0x03, 0x00, 0x02, 0xC0, 0x08, 0xDA, + 0x73, 0xC1, 0xC0, 0x07, 0x02, 0x00, 0xC1, 0x07, + 0x12, 0x00, 0xEF, 0x57, 0x00, 0x00, 0x06, 0x06, + 0x02, 0xC0, 0xC1, 0x07, 0x23, 0x00, 0x9F, 0xAF, + 0x28, 0x05, 0xC0, 0x07, 0x14, 0x00, 0xC1, 0x0B, + 0xEA, 0x05, 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, + 0x3E, 0x00, 0x9F, 0xAF, 0x0A, 0x05, 0xE7, 0x09, + 0xE4, 0x05, 0xFA, 0x05, 0x27, 0xD8, 0xFA, 0x05, + 0xE7, 0x07, 0x0E, 0x06, 0xFC, 0x05, 0xE7, 0x07, + 0x4E, 0x06, 0x00, 0x06, 0xE7, 0x07, 0x40, 0x00, + 0x02, 0x06, 0x9F, 0xAF, 0x66, 0x05, 0x9F, 0xAF, + 0xC6, 0x00, 0x97, 0xCF, 0xC1, 0x0B, 0xE2, 0x05, + 0x41, 0xD0, 0x01, 0xD2, 0xC1, 0x17, 0x23, 0x00, + 0x9F, 0xAF, 0xDC, 0x04, 0xC0, 0x07, 0x04, 0x00, + 0xC1, 0x0B, 0xE3, 0x05, 0x9F, 0xAF, 0x28, 0x05, + 0xC0, 0x07, 0x06, 0x00, 0xC1, 0x09, 0xE6, 0x05, + 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, 0x07, 0x00, + 0xC1, 0x09, 0xE6, 0x05, 0xC1, 0xD1, 0x9F, 0xAF, + 0x28, 0x05, 0xC0, 0x07, 0x0B, 0x00, 0xC1, 0x09, + 0xE8, 0x05, 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, + 0x0C, 0x00, 0xC1, 0x09, 0xE8, 0x05, 0xC1, 0xD1, + 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, 0x0D, 0x00, + 0xC1, 0x07, 0x09, 0x00, 0x9F, 0xAF, 0x28, 0x05, + 0xC0, 0x07, 0x03, 0x00, 0xC1, 0x07, 0x32, 0x00, + 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, 0x0F, 0x00, + 0xC1, 0x07, 0x00, 0x00, 0x9F, 0xAF, 0x28, 0x05, + 0x97, 0xCF, 0xE7, 0x67, 0xFF, 0xD9, 0x24, 0xC0, + 0xC8, 0x07, 0x0A, 0x00, 0x40, 0x00, 0xC0, 0x67, + 0x00, 0x02, 0x27, 0x80, 0x24, 0xC0, 0xE7, 0x87, + 0x00, 0x04, 0x24, 0xC0, 0xE7, 0x67, 0xFF, 0xF9, + 0x24, 0xC0, 0x01, 0xD2, 0x08, 0xDA, 0x72, 0xC1, + 0xE7, 0x87, 0x00, 0x20, 0x24, 0xC0, 0x97, 0xCF, + 0x27, 0x00, 0x1E, 0xC0, 0xE7, 0x87, 0xFF, 0x00, + 0x22, 0xC0, 0xE7, 0x67, 0x7F, 0xFF, 0x24, 0xC0, + 0xE7, 0x87, 0x80, 0x00, 0x24, 0xC0, 0xE7, 0x87, + 0x80, 0x00, 0x24, 0xC0, 0x97, 0xCF, 0x9F, 0xAF, + 0x0A, 0x05, 0x67, 0x00, 0x1E, 0xC0, 0xE7, 0x67, + 0xBF, 0xFF, 0x24, 0xC0, 0xE7, 0x87, 0x40, 0x00, + 0x24, 0xC0, 0xE7, 0x87, 0x40, 0x00, 0x24, 0xC0, + 0x97, 0xCF, 0x9F, 0xAF, 0x0A, 0x05, 0xE7, 0x67, + 0x00, 0xFF, 0x22, 0xC0, 0xE7, 0x67, 0xFF, 0xFE, + 0x24, 0xC0, 0xE7, 0x67, 0xFF, 0xFE, 0x24, 0xC0, + 0xC1, 0x09, 0x20, 0xC0, 0xE7, 0x87, 0x00, 0x01, + 0x24, 0xC0, 0x97, 0xCF, 0xC0, 0x07, 0x40, 0x00, + 0xC8, 0x09, 0xFC, 0x05, 0xE7, 0x67, 0x00, 0xFF, + 0x22, 0xC0, 0xE7, 0x67, 0xFF, 0xFE, 0x24, 0xC0, + 0xE7, 0x67, 0xBF, 0xFF, 0x24, 0xC0, 0xE7, 0x67, + 0xBF, 0xFF, 0x24, 0xC0, 0x00, 0xDA, 0xE8, 0x09, + 0x20, 0xC0, 0xE7, 0x87, 0x40, 0x00, 0x24, 0xC0, + 0xE7, 0x87, 0x40, 0x00, 0x24, 0xC0, 0x00, 0xDA, + 0xE8, 0x09, 0x20, 0xC0, 0x6D, 0xC1, 0xE7, 0x87, + 0x00, 0x01, 0x24, 0xC0, 0x97, 0xCF, 0xE7, 0x07, + 0x32, 0x00, 0x12, 0xC0, 0xE7, 0x77, 0x00, 0x80, + 0x12, 0xC0, 0x7C, 0xC0, 0x97, 0xCF, 0xE7, 0x07, + 0x20, 0x4E, 0x12, 0xC0, 0xE7, 0x77, 0x00, 0x80, + 0x12, 0xC0, 0x7C, 0xC0, 0x97, 0xCF, 0x09, 0x02, + 0x19, 0x00, 0x01, 0x01, 0x00, 0x80, 0x96, 0x09, + 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x05, 0x81, 0x02, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static unsigned char setup5[] = { + 0xB6, 0xC3, 0x2F, 0x01, 0x03, 0x64, 0x0E, 0x00, + 0x14, 0x00, 0x1A, 0x00, 0x20, 0x00, 0x26, 0x00, + 0x4A, 0x00, 0x64, 0x00, 0x6A, 0x00, 0x92, 0x00, + 0x9A, 0x00, 0xA0, 0x00, 0xB2, 0x00, 0xB8, 0x00, + 0xBE, 0x00, 0xC2, 0x00, 0xC8, 0x00, 0xCE, 0x00, + 0xDC, 0x00, 0xDA, 0x00, 0xE2, 0x00, 0xE0, 0x00, + 0xE8, 0x00, 0xE6, 0x00, 0xEE, 0x00, 0xEC, 0x00, + 0xF2, 0x00, 0xF8, 0x00, 0x02, 0x01, 0x0A, 0x01, + 0x0E, 0x01, 0x12, 0x01, 0x1E, 0x01, 0x22, 0x01, + 0x28, 0x01, 0x2C, 0x01, 0x32, 0x01, 0x36, 0x01, + 0x44, 0x01, 0x50, 0x01, 0x5E, 0x01, 0x72, 0x01, + 0x76, 0x01, 0x7A, 0x01, 0x80, 0x01, 0x88, 0x01, + 0x8C, 0x01, 0x94, 0x01, 0x9C, 0x01, 0xA0, 0x01, + 0xA4, 0x01, 0xAA, 0x01, 0xB0, 0x01, 0xB4, 0x01, + 0xBA, 0x01, 0xD0, 0x01, 0xDA, 0x01, 0xF6, 0x01, + 0xFA, 0x01, 0x02, 0x02, 0x34, 0x02, 0x3C, 0x02, + 0x44, 0x02, 0x4A, 0x02, 0x50, 0x02, 0x56, 0x02, + 0x74, 0x02, 0x78, 0x02, 0x7E, 0x02, 0x84, 0x02, + 0x8A, 0x02, 0x88, 0x02, 0x90, 0x02, 0x8E, 0x02, + 0x94, 0x02, 0xA2, 0x02, 0xA8, 0x02, 0xAE, 0x02, + 0xB4, 0x02, 0xBA, 0x02, 0xB8, 0x02, 0xC0, 0x02, + 0xBE, 0x02, 0xC4, 0x02, 0xD0, 0x02, 0xD4, 0x02, + 0xE0, 0x02, 0xE6, 0x02, 0xEE, 0x02, 0xF8, 0x02, + 0xFC, 0x02, 0x06, 0x03, 0x1E, 0x03, 0x24, 0x03, + 0x28, 0x03, 0x30, 0x03, 0x2E, 0x03, 0x3C, 0x03, + 0x4A, 0x03, 0x4E, 0x03, 0x54, 0x03, 0x58, 0x03, + 0x5E, 0x03, 0x66, 0x03, 0x6E, 0x03, 0x7A, 0x03, + 0x86, 0x03, 0x8E, 0x03, 0x96, 0x03, 0xB2, 0x03, + 0xB8, 0x03, 0xC6, 0x03, 0xCC, 0x03, 0xD4, 0x03, + 0xDA, 0x03, 0xE8, 0x03, 0xF4, 0x03, 0xFC, 0x03, + 0x04, 0x04, 0x20, 0x04, 0x2A, 0x04, 0x32, 0x04, + 0x36, 0x04, 0x3E, 0x04, 0x44, 0x04, 0x42, 0x04, + 0x48, 0x04, 0x4E, 0x04, 0x4C, 0x04, 0x54, 0x04, + 0x52, 0x04, 0x5A, 0x04, 0x5E, 0x04, 0x62, 0x04, + 0x68, 0x04, 0x74, 0x04, 0x7C, 0x04, 0x80, 0x04, + 0x88, 0x04, 0x8C, 0x04, 0x94, 0x04, 0x9A, 0x04, + 0xA2, 0x04, 0xA6, 0x04, 0xAE, 0x04, 0xB4, 0x04, + 0xC0, 0x04, 0xCC, 0x04, 0xD8, 0x04, 0x2A, 0x05, + 0x46, 0x05, 0x6C, 0x05, 0x00, 0x00 +}; + +/* rvmalloc / rvfree copied from usbvideo.c + * + * Not sure why these are not yet non-statics which I can reference through + * usbvideo.h the same as it is in 2.4.20. I bet this will get fixed sometime + * in the future. + * +*/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +struct vicam_camera { + u16 shutter_speed; // capture shutter speed + u16 gain; // capture gain + + u8 *raw_image; // raw data captured from the camera + u8 *framebuf; // processed data in RGB24 format + u8 *cntrlbuf; // area used to send control msgs + + struct video_device vdev; // v4l video device + struct usb_device *udev; // usb device + + /* guard against simultaneous accesses to the camera */ + struct mutex cam_lock; + + int is_initialized; + u8 open_count; + u8 bulkEndpoint; + int needsDummyRead; + +#if defined(CONFIG_VIDEO_PROC_FS) + struct proc_dir_entry *proc_dir; +#endif + +}; + +static int vicam_probe( struct usb_interface *intf, const struct usb_device_id *id); +static void vicam_disconnect(struct usb_interface *intf); +static void read_frame(struct vicam_camera *cam, int framenum); +static void vicam_decode_color(const u8 *, u8 *); + +static int __send_control_msg(struct vicam_camera *cam, + u8 request, + u16 value, + u16 index, + unsigned char *cp, + u16 size) +{ + int status; + + /* cp must be memory that has been allocated by kmalloc */ + + status = usb_control_msg(cam->udev, + usb_sndctrlpipe(cam->udev, 0), + request, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, + cp, size, 1000); + + status = min(status, 0); + + if (status < 0) { + printk(KERN_INFO "Failed sending control message, error %d.\n", + status); + } + + return status; +} + +static int send_control_msg(struct vicam_camera *cam, + u8 request, + u16 value, + u16 index, + unsigned char *cp, + u16 size) +{ + int status = -ENODEV; + mutex_lock(&cam->cam_lock); + if (cam->udev) { + status = __send_control_msg(cam, request, value, + index, cp, size); + } + mutex_unlock(&cam->cam_lock); + return status; +} +static int +initialize_camera(struct vicam_camera *cam) +{ + const struct { + u8 *data; + u32 size; + } firmware[] = { + { .data = setup1, .size = sizeof(setup1) }, + { .data = setup2, .size = sizeof(setup2) }, + { .data = setup3, .size = sizeof(setup3) }, + { .data = setup4, .size = sizeof(setup4) }, + { .data = setup5, .size = sizeof(setup5) }, + { .data = setup3, .size = sizeof(setup3) }, + { .data = NULL, .size = 0 } + }; + + int err, i; + + for (i = 0, err = 0; firmware[i].data && !err; i++) { + memcpy(cam->cntrlbuf, firmware[i].data, firmware[i].size); + + err = send_control_msg(cam, 0xff, 0, 0, + cam->cntrlbuf, firmware[i].size); + } + + return err; +} + +static int +set_camera_power(struct vicam_camera *cam, int state) +{ + int status; + + if ((status = send_control_msg(cam, 0x50, state, 0, NULL, 0)) < 0) + return status; + + if (state) { + send_control_msg(cam, 0x55, 1, 0, NULL, 0); + } + + return 0; +} + +static int +vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsigned long arg) +{ + void __user *user_arg = (void __user *)arg; + struct vicam_camera *cam = file->private_data; + int retval = 0; + + if (!cam) + return -ENODEV; + + switch (ioctlnr) { + /* query capabilities */ + case VIDIOCGCAP: + { + struct video_capability b; + + DBG("VIDIOCGCAP\n"); + memset(&b, 0, sizeof(b)); + strcpy(b.name, "ViCam-based Camera"); + b.type = VID_TYPE_CAPTURE; + b.channels = 1; + b.audios = 0; + b.maxwidth = 320; /* VIDEOSIZE_CIF */ + b.maxheight = 240; + b.minwidth = 320; /* VIDEOSIZE_48_48 */ + b.minheight = 240; + + if (copy_to_user(user_arg, &b, sizeof(b))) + retval = -EFAULT; + + break; + } + /* get/set video source - we are a camera and nothing else */ + case VIDIOCGCHAN: + { + struct video_channel v; + + DBG("VIDIOCGCHAN\n"); + if (copy_from_user(&v, user_arg, sizeof(v))) { + retval = -EFAULT; + break; + } + if (v.channel != 0) { + retval = -EINVAL; + break; + } + + v.channel = 0; + strcpy(v.name, "Camera"); + v.tuners = 0; + v.flags = 0; + v.type = VIDEO_TYPE_CAMERA; + v.norm = 0; + + if (copy_to_user(user_arg, &v, sizeof(v))) + retval = -EFAULT; + break; + } + + case VIDIOCSCHAN: + { + int v; + + if (copy_from_user(&v, user_arg, sizeof(v))) + retval = -EFAULT; + DBG("VIDIOCSCHAN %d\n", v); + + if (retval == 0 && v != 0) + retval = -EINVAL; + + break; + } + + /* image properties */ + case VIDIOCGPICT: + { + struct video_picture vp; + DBG("VIDIOCGPICT\n"); + memset(&vp, 0, sizeof (struct video_picture)); + vp.brightness = cam->gain << 8; + vp.depth = 24; + vp.palette = VIDEO_PALETTE_RGB24; + if (copy_to_user(user_arg, &vp, sizeof (struct video_picture))) + retval = -EFAULT; + break; + } + + case VIDIOCSPICT: + { + struct video_picture vp; + + if (copy_from_user(&vp, user_arg, sizeof(vp))) { + retval = -EFAULT; + break; + } + + DBG("VIDIOCSPICT depth = %d, pal = %d\n", vp.depth, + vp.palette); + + cam->gain = vp.brightness >> 8; + + if (vp.depth != 24 + || vp.palette != VIDEO_PALETTE_RGB24) + retval = -EINVAL; + + break; + } + + /* get/set capture window */ + case VIDIOCGWIN: + { + struct video_window vw; + vw.x = 0; + vw.y = 0; + vw.width = 320; + vw.height = 240; + vw.chromakey = 0; + vw.flags = 0; + vw.clips = NULL; + vw.clipcount = 0; + + DBG("VIDIOCGWIN\n"); + + if (copy_to_user(user_arg, (void *)&vw, sizeof(vw))) + retval = -EFAULT; + + // I'm not sure what the deal with a capture window is, it is very poorly described + // in the doc. So I won't support it now. + break; + } + + case VIDIOCSWIN: + { + + struct video_window vw; + + if (copy_from_user(&vw, user_arg, sizeof(vw))) { + retval = -EFAULT; + break; + } + + DBG("VIDIOCSWIN %d x %d\n", vw.width, vw.height); + + if ( vw.width != 320 || vw.height != 240 ) + retval = -EFAULT; + + break; + } + + /* mmap interface */ + case VIDIOCGMBUF: + { + struct video_mbuf vm; + int i; + + DBG("VIDIOCGMBUF\n"); + memset(&vm, 0, sizeof (vm)); + vm.size = + VICAM_MAX_FRAME_SIZE * VICAM_FRAMES; + vm.frames = VICAM_FRAMES; + for (i = 0; i < VICAM_FRAMES; i++) + vm.offsets[i] = VICAM_MAX_FRAME_SIZE * i; + + if (copy_to_user(user_arg, (void *)&vm, sizeof(vm))) + retval = -EFAULT; + + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + // int video_size; + + if (copy_from_user((void *)&vm, user_arg, sizeof(vm))) { + retval = -EFAULT; + break; + } + + DBG("VIDIOCMCAPTURE frame=%d, height=%d, width=%d, format=%d.\n",vm.frame,vm.width,vm.height,vm.format); + + if ( vm.frame >= VICAM_FRAMES || vm.format != VIDEO_PALETTE_RGB24 ) + retval = -EINVAL; + + // in theory right here we'd start the image capturing + // (fill in a bulk urb and submit it asynchronously) + // + // Instead we're going to do a total hack job for now and + // retrieve the frame in VIDIOCSYNC + + break; + } + + case VIDIOCSYNC: + { + int frame; + + if (copy_from_user((void *)&frame, user_arg, sizeof(int))) { + retval = -EFAULT; + break; + } + DBG("VIDIOCSYNC: %d\n", frame); + + read_frame(cam, frame); + vicam_decode_color(cam->raw_image, + cam->framebuf + + frame * VICAM_MAX_FRAME_SIZE ); + + break; + } + + /* pointless to implement overlay with this camera */ + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCKEY: + retval = -EINVAL; + break; + + /* tuner interface - we have none */ + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + retval = -EINVAL; + break; + + /* audio interface - we have none */ + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + retval = -EINVAL; + break; + default: + retval = -ENOIOCTLCMD; + break; + } + + return retval; +} + +static int +vicam_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct vicam_camera *cam = + (struct vicam_camera *) dev->priv; + DBG("open\n"); + + if (!cam) { + printk(KERN_ERR + "vicam video_device improperly initialized"); + return -EINVAL; + } + + /* the videodev_lock held above us protects us from + * simultaneous opens...for now. we probably shouldn't + * rely on this fact forever. + */ + + if (cam->open_count > 0) { + printk(KERN_INFO + "vicam_open called on already opened camera"); + return -EBUSY; + } + + cam->raw_image = kmalloc(VICAM_MAX_READ_SIZE, GFP_KERNEL); + if (!cam->raw_image) { + return -ENOMEM; + } + + cam->framebuf = rvmalloc(VICAM_MAX_FRAME_SIZE * VICAM_FRAMES); + if (!cam->framebuf) { + kfree(cam->raw_image); + return -ENOMEM; + } + + cam->cntrlbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!cam->cntrlbuf) { + kfree(cam->raw_image); + rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES); + return -ENOMEM; + } + + // First upload firmware, then turn the camera on + + if (!cam->is_initialized) { + initialize_camera(cam); + + cam->is_initialized = 1; + } + + set_camera_power(cam, 1); + + cam->needsDummyRead = 1; + cam->open_count++; + + file->private_data = cam; + + return 0; +} + +static int +vicam_close(struct inode *inode, struct file *file) +{ + struct vicam_camera *cam = file->private_data; + int open_count; + struct usb_device *udev; + + DBG("close\n"); + + /* it's not the end of the world if + * we fail to turn the camera off. + */ + + set_camera_power(cam, 0); + + kfree(cam->raw_image); + rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES); + kfree(cam->cntrlbuf); + + mutex_lock(&cam->cam_lock); + + cam->open_count--; + open_count = cam->open_count; + udev = cam->udev; + + mutex_unlock(&cam->cam_lock); + + if (!open_count && !udev) { + kfree(cam); + } + + return 0; +} + +static void vicam_decode_color(const u8 *data, u8 *rgb) +{ + /* vicam_decode_color - Convert from Vicam Y-Cr-Cb to RGB + * Copyright (C) 2002 Monroe Williams (monroe@pobox.com) + */ + + int i, prevY, nextY; + + prevY = 512; + nextY = 512; + + data += VICAM_HEADER_SIZE; + + for( i = 0; i < 240; i++, data += 512 ) { + const int y = ( i * 242 ) / 240; + + int j, prevX, nextX; + int Y, Cr, Cb; + + if ( y == 242 - 1 ) { + nextY = -512; + } + + prevX = 1; + nextX = 1; + + for ( j = 0; j < 320; j++, rgb += 3 ) { + const int x = ( j * 512 ) / 320; + const u8 * const src = &data[x]; + + if ( x == 512 - 1 ) { + nextX = -1; + } + + Cr = ( src[prevX] - src[0] ) + + ( src[nextX] - src[0] ); + Cr /= 2; + + Cb = ( src[prevY] - src[prevX + prevY] ) + + ( src[prevY] - src[nextX + prevY] ) + + ( src[nextY] - src[prevX + nextY] ) + + ( src[nextY] - src[nextX + nextY] ); + Cb /= 4; + + Y = 1160 * ( src[0] + ( Cr / 2 ) - 16 ); + + if ( i & 1 ) { + int Ct = Cr; + Cr = Cb; + Cb = Ct; + } + + if ( ( x ^ i ) & 1 ) { + Cr = -Cr; + Cb = -Cb; + } + + rgb[0] = clamp( ( ( Y + ( 2017 * Cb ) ) + + 500 ) / 900, 0, 255 ); + rgb[1] = clamp( ( ( Y - ( 392 * Cb ) - + ( 813 * Cr ) ) + + 500 ) / 1000, 0, 255 ); + rgb[2] = clamp( ( ( Y + ( 1594 * Cr ) ) + + 500 ) / 1300, 0, 255 ); + + prevX = -1; + } + + prevY = -512; + } +} + +static void +read_frame(struct vicam_camera *cam, int framenum) +{ + unsigned char *request = cam->cntrlbuf; + int realShutter; + int n; + int actual_length; + + if (cam->needsDummyRead) { + cam->needsDummyRead = 0; + read_frame(cam, framenum); + } + + memset(request, 0, 16); + request[0] = cam->gain; // 0 = 0% gain, FF = 100% gain + + request[1] = 0; // 512x242 capture + + request[2] = 0x90; // the function of these two bytes + request[3] = 0x07; // is not yet understood + + if (cam->shutter_speed > 60) { + // Short exposure + realShutter = + ((-15631900 / cam->shutter_speed) + 260533) / 1000; + request[4] = realShutter & 0xFF; + request[5] = (realShutter >> 8) & 0xFF; + request[6] = 0x03; + request[7] = 0x01; + } else { + // Long exposure + realShutter = 15600 / cam->shutter_speed - 1; + request[4] = 0; + request[5] = 0; + request[6] = realShutter & 0xFF; + request[7] = realShutter >> 8; + } + + // Per John Markus Bjørndalen, byte at index 8 causes problems if it isn't 0 + request[8] = 0; + // bytes 9-15 do not seem to affect exposure or image quality + + mutex_lock(&cam->cam_lock); + + if (!cam->udev) { + goto done; + } + + n = __send_control_msg(cam, 0x51, 0x80, 0, request, 16); + + if (n < 0) { + printk(KERN_ERR + " Problem sending frame capture control message"); + goto done; + } + + n = usb_bulk_msg(cam->udev, + usb_rcvbulkpipe(cam->udev, cam->bulkEndpoint), + cam->raw_image, + 512 * 242 + 128, &actual_length, 10000); + + if (n < 0) { + printk(KERN_ERR "Problem during bulk read of frame data: %d\n", + n); + } + + done: + mutex_unlock(&cam->cam_lock); +} + +static ssize_t +vicam_read( struct file *file, char __user *buf, size_t count, loff_t *ppos ) +{ + struct vicam_camera *cam = file->private_data; + + DBG("read %d bytes.\n", (int) count); + + if (*ppos >= VICAM_MAX_FRAME_SIZE) { + *ppos = 0; + return 0; + } + + if (*ppos == 0) { + read_frame(cam, 0); + vicam_decode_color(cam->raw_image, + cam->framebuf + + 0 * VICAM_MAX_FRAME_SIZE); + } + + count = min_t(size_t, count, VICAM_MAX_FRAME_SIZE - *ppos); + + if (copy_to_user(buf, &cam->framebuf[*ppos], count)) { + count = -EFAULT; + } else { + *ppos += count; + } + + if (count == VICAM_MAX_FRAME_SIZE) { + *ppos = 0; + } + + return count; +} + + +static int +vicam_mmap(struct file *file, struct vm_area_struct *vma) +{ + // TODO: allocate the raw frame buffer if necessary + unsigned long page, pos; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end-vma->vm_start; + struct vicam_camera *cam = file->private_data; + + if (!cam) + return -ENODEV; + + DBG("vicam_mmap: %ld\n", size); + + /* We let mmap allocate as much as it wants because Linux was adding 2048 bytes + * to the size the application requested for mmap and it was screwing apps up. + if (size > VICAM_FRAMES*VICAM_MAX_FRAME_SIZE) + return -EINVAL; + */ + + pos = (unsigned long)cam->framebuf; + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +#if defined(CONFIG_VIDEO_PROC_FS) + +static struct proc_dir_entry *vicam_proc_root = NULL; + +static int vicam_read_helper(char *page, char **start, off_t off, + int count, int *eof, int value) +{ + char *out = page; + int len; + + out += sprintf(out, "%d",value); + + len = out - page; + len -= off; + if (len < count) { + *eof = 1; + if (len <= 0) + return 0; + } else + len = count; + + *start = page + off; + return len; +} + +static int vicam_read_proc_shutter(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + return vicam_read_helper(page,start,off,count,eof, + ((struct vicam_camera *)data)->shutter_speed); +} + +static int vicam_read_proc_gain(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + return vicam_read_helper(page,start,off,count,eof, + ((struct vicam_camera *)data)->gain); +} + +static int +vicam_write_proc_shutter(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + u16 stmp; + char kbuf[8]; + struct vicam_camera *cam = (struct vicam_camera *) data; + + if (count > 6) + return -EINVAL; + + if (copy_from_user(kbuf, buffer, count)) + return -EFAULT; + + stmp = (u16) simple_strtoul(kbuf, NULL, 10); + if (stmp < 4 || stmp > 32000) + return -EINVAL; + + cam->shutter_speed = stmp; + + return count; +} + +static int +vicam_write_proc_gain(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + u16 gtmp; + char kbuf[8]; + + struct vicam_camera *cam = (struct vicam_camera *) data; + + if (count > 4) + return -EINVAL; + + if (copy_from_user(kbuf, buffer, count)) + return -EFAULT; + + gtmp = (u16) simple_strtoul(kbuf, NULL, 10); + if (gtmp > 255) + return -EINVAL; + cam->gain = gtmp; + + return count; +} + +static void +vicam_create_proc_root(void) +{ + vicam_proc_root = proc_mkdir("video/vicam", NULL); + + if (vicam_proc_root) + vicam_proc_root->owner = THIS_MODULE; + else + printk(KERN_ERR + "could not create /proc entry for vicam!"); +} + +static void +vicam_destroy_proc_root(void) +{ + if (vicam_proc_root) + remove_proc_entry("video/vicam", 0); +} + +static void +vicam_create_proc_entry(struct vicam_camera *cam) +{ + char name[64]; + struct proc_dir_entry *ent; + + DBG(KERN_INFO "vicam: creating proc entry\n"); + + if (!vicam_proc_root || !cam) { + printk(KERN_INFO + "vicam: could not create proc entry, %s pointer is null.\n", + (!cam ? "camera" : "root")); + return; + } + + sprintf(name, "video%d", cam->vdev.minor); + + cam->proc_dir = proc_mkdir(name, vicam_proc_root); + + if ( !cam->proc_dir ) + return; // FIXME: We should probably return an error here + + ent = create_proc_entry("shutter", S_IFREG | S_IRUGO | S_IWUSR, + cam->proc_dir); + if (ent) { + ent->data = cam; + ent->read_proc = vicam_read_proc_shutter; + ent->write_proc = vicam_write_proc_shutter; + ent->size = 64; + } + + ent = create_proc_entry("gain", S_IFREG | S_IRUGO | S_IWUSR, + cam->proc_dir); + if (ent) { + ent->data = cam; + ent->read_proc = vicam_read_proc_gain; + ent->write_proc = vicam_write_proc_gain; + ent->size = 64; + } +} + +static void +vicam_destroy_proc_entry(void *ptr) +{ + struct vicam_camera *cam = (struct vicam_camera *) ptr; + char name[16]; + + if ( !cam->proc_dir ) + return; + + sprintf(name, "video%d", cam->vdev.minor); + remove_proc_entry("shutter", cam->proc_dir); + remove_proc_entry("gain", cam->proc_dir); + remove_proc_entry(name,vicam_proc_root); + cam->proc_dir = NULL; + +} + +#else +static inline void vicam_create_proc_root(void) { } +static inline void vicam_destroy_proc_root(void) { } +static inline void vicam_create_proc_entry(struct vicam_camera *cam) { } +static inline void vicam_destroy_proc_entry(void *ptr) { } +#endif + +static struct file_operations vicam_fops = { + .owner = THIS_MODULE, + .open = vicam_open, + .release = vicam_close, + .read = vicam_read, + .mmap = vicam_mmap, + .ioctl = vicam_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device vicam_template = { + .owner = THIS_MODULE, + .name = "ViCam-based USB Camera", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_VICAM, + .fops = &vicam_fops, + .minor = -1, +}; + +/* table of devices that work with this driver */ +static struct usb_device_id vicam_table[] = { + {USB_DEVICE(USB_VICAM_VENDOR_ID, USB_VICAM_PRODUCT_ID)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, vicam_table); + +static struct usb_driver vicam_driver = { + .name = "vicam", + .probe = vicam_probe, + .disconnect = vicam_disconnect, + .id_table = vicam_table +}; + +/** + * vicam_probe + * @intf: the interface + * @id: the device id + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int +vicam_probe( struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + int bulkEndpoint = 0; + const struct usb_host_interface *interface; + const struct usb_endpoint_descriptor *endpoint; + struct vicam_camera *cam; + + printk(KERN_INFO "ViCam based webcam connected\n"); + + interface = intf->cur_altsetting; + + DBG(KERN_DEBUG "Interface %d. has %u. endpoints!\n", + interface->desc.bInterfaceNumber, (unsigned) (interface->desc.bNumEndpoints)); + endpoint = &interface->endpoint[0].desc; + + if ((endpoint->bEndpointAddress & 0x80) && + ((endpoint->bmAttributes & 3) == 0x02)) { + /* we found a bulk in endpoint */ + bulkEndpoint = endpoint->bEndpointAddress; + } else { + printk(KERN_ERR + "No bulk in endpoint was found ?! (this is bad)\n"); + } + + if ((cam = + kmalloc(sizeof (struct vicam_camera), GFP_KERNEL)) == NULL) { + printk(KERN_WARNING + "could not allocate kernel memory for vicam_camera struct\n"); + return -ENOMEM; + } + + memset(cam, 0, sizeof (struct vicam_camera)); + + cam->shutter_speed = 15; + + mutex_init(&cam->cam_lock); + + memcpy(&cam->vdev, &vicam_template, + sizeof (vicam_template)); + cam->vdev.priv = cam; // sort of a reverse mapping for those functions that get vdev only + + cam->udev = dev; + cam->bulkEndpoint = bulkEndpoint; + + if (video_register_device(&cam->vdev, VFL_TYPE_GRABBER, -1) == -1) { + kfree(cam); + printk(KERN_WARNING "video_register_device failed\n"); + return -EIO; + } + + vicam_create_proc_entry(cam); + + printk(KERN_INFO "ViCam webcam driver now controlling video device %d\n",cam->vdev.minor); + + usb_set_intfdata (intf, cam); + + return 0; +} + +static void +vicam_disconnect(struct usb_interface *intf) +{ + int open_count; + struct vicam_camera *cam = usb_get_intfdata (intf); + usb_set_intfdata (intf, NULL); + + /* we must unregister the device before taking its + * cam_lock. This is because the video open call + * holds the same lock as video unregister. if we + * unregister inside of the cam_lock and open also + * uses the cam_lock, we get deadlock. + */ + + video_unregister_device(&cam->vdev); + + /* stop the camera from being used */ + + mutex_lock(&cam->cam_lock); + + /* mark the camera as gone */ + + cam->udev = NULL; + + vicam_destroy_proc_entry(cam); + + /* the only thing left to do is synchronize with + * our close/release function on who should release + * the camera memory. if there are any users using the + * camera, it's their job. if there are no users, + * it's ours. + */ + + open_count = cam->open_count; + + mutex_unlock(&cam->cam_lock); + + if (!open_count) { + kfree(cam); + } + + printk(KERN_DEBUG "ViCam-based WebCam disconnected\n"); +} + +/* + */ +static int __init +usb_vicam_init(void) +{ + int retval; + DBG(KERN_INFO "ViCam-based WebCam driver startup\n"); + vicam_create_proc_root(); + retval = usb_register(&vicam_driver); + if (retval) + printk(KERN_WARNING "usb_register failed!\n"); + return retval; +} + +static void __exit +usb_vicam_exit(void) +{ + DBG(KERN_INFO + "ViCam-based WebCam driver shutdown\n"); + + usb_deregister(&vicam_driver); + vicam_destroy_proc_root(); +} + +module_init(usb_vicam_init); +module_exit(usb_vicam_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); |