/* OmniVision/Cypress FX2 Camera-to-USB Bridge Driver * * Copyright (c) 1999-2003 Mark W. McClelland, David Brownell * Many improvements by Bret Wallach * Color fixes by by Orion Sky Lawlor (2/26/2000) * OV7620 fixes by Charl P. Botha * Changes by Claudio Matsuoka * Kernel I2C interface improvements by Kyösti Mälkki * URB error messages from pwc driver by Nemosoft * Memory management (rvmalloc) code from bttv driver, by Gerd Knorr and others * * Based on the Linux CPiA driver written by Peter Pregler, * Scott J. Bertin and Johannes Erdfelt. * * Please see the file: linux/Documentation/usb/ov511.txt * and the website at: http://alpha.dyndns.org/ov511 * for more info. * * 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. NO WARRANTY OF ANY KIND is expressed or implied. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 68) # include #endif #include #include "ovfx2.h" #include "id.h" /* Driver will compile with older kernels, but will oops on open() */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 19) # error "Kernel 2.4.19 is the minimum for this driver" #endif /* * Version Information */ #define DRIVER_VERSION "v2.25" #define EMAIL "mark@alpha.dyndns.org" #define DRIVER_AUTHOR "Mark McClelland & Bret Wallach \ & Orion Sky Lawlor & Kevin Moore & Charl P. Botha \ & Claudio Matsuoka " #define DRIVER_DESC "ovfx2 USB Camera Driver" #define OVFX2_MAX_UNIT_VIDEO 16 /* Pixel count * 3 bytes per pixel (for BGR24) */ #define MAX_FRAME_SIZE(w, h) ((w) * (h) * 3) #define MAX_DATA_SIZE(w, h) (MAX_FRAME_SIZE(w, h) + sizeof(struct timeval)) /* Max size * 2 bytes per pixel average + safety margin */ #define MAX_RAW_DATA_SIZE(w, h) ((w) * (h) * 2) #define FATAL_ERROR(rc) ((rc) < 0 && (rc) != -EPERM) /********************************************************************** * Module Parameters * (See ov511.txt for detailed descriptions of these) **********************************************************************/ /* These variables (and all static globals) default to zero */ static int autobright = 1; static int autoexp = 1; #ifdef OVFX2_DEBUG static int debug; #endif static int dumppix; static int dump_bridge; static int lightfreq; static int bandingfilter; static int clockdiv = -1; static int framedrop = -1; static int fastset; static int force_palette; static int backlight; static int unit_video[OVFX2_MAX_UNIT_VIDEO]; static int mirror; MODULE_PARM(autobright, "i"); MODULE_PARM_DESC(autobright, "Sensor automatically changes brightness"); MODULE_PARM(autoexp, "i"); MODULE_PARM_DESC(autoexp, "Sensor automatically changes exposure"); #ifdef OVFX2_DEBUG MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug level: 0=none, 1=inits, 2=warning, 3=config, 4=functions, 5=max"); #endif MODULE_PARM(dumppix, "i"); MODULE_PARM_DESC(dumppix, "Dump raw pixel data"); MODULE_PARM(dump_bridge, "i"); MODULE_PARM_DESC(dump_bridge, "Dump the bridge registers"); MODULE_PARM(lightfreq, "i"); MODULE_PARM_DESC(lightfreq, "Light frequency. Set to 50 or 60 Hz, or zero for default settings"); MODULE_PARM(bandingfilter, "i"); MODULE_PARM_DESC(bandingfilter, "Enable banding filter (to reduce effects of fluorescent lighting)"); MODULE_PARM(clockdiv, "i"); MODULE_PARM_DESC(clockdiv, "Force pixel clock divisor to a specific value"); MODULE_PARM(framedrop, "i"); MODULE_PARM_DESC(framedrop, "Force a specific frame drop register setting"); MODULE_PARM(fastset, "i"); MODULE_PARM_DESC(fastset, "Allows picture settings to take effect immediately"); MODULE_PARM(force_palette, "i"); MODULE_PARM_DESC(force_palette, "Force the palette to a specific value"); MODULE_PARM(backlight, "i"); MODULE_PARM_DESC(backlight, "For objects that are lit from behind"); MODULE_PARM(unit_video, "1-" __MODULE_STRING(OVFX2_MAX_UNIT_VIDEO) "i"); MODULE_PARM_DESC(unit_video, "Force use of specific minor number(s). 0 is not allowed."); MODULE_PARM(mirror, "i"); MODULE_PARM_DESC(mirror, "Reverse image horizontally"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); #if defined(MODULE_LICENSE) /* Introduced in ~2.4.10 */ MODULE_LICENSE("GPL"); #endif /********************************************************************** * Miscellaneous Globals **********************************************************************/ static struct usb_driver ovfx2_driver; /* Prevents double-free of ov struct */ static struct semaphore ov_free_lock; /* * Omnivision provided a reference design and firmware connecting an FX2 * to many of their I2C-aware sensor arrays. Different products using * this design could have different sensors. */ static struct usb_device_id device_table [] = { /* * Orange Micro "iBOT2" uses an ov7620. * * HIGH SPEED: * * T: Bus=04 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 * D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 * P: Vendor=0b62 ProdID=0059 Rev= 1.00 * S: Manufacturer=Orange Micro * S: Product=iBOT2 Camera * C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=400mA * I: If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=(none) * E: Ad=81(I) Atr=03(Int.) MxPS= 4 Ivl= 4ms * E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl= 1ms * * FULL SPEED: * * T: Bus=02 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 3 Spd=12 MxCh= 0 * D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 * P: Vendor=0b62 ProdID=0059 Rev= 1.00 * S: Manufacturer=Orange Micro * S: Product=iBOT2 Camera * C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=400mA * I: If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=(none) * E: Ad=81(I) Atr=03(Int.) MxPS= 4 Ivl= 1ms * E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl= 1ms * */ { USB_DEVICE (VEND_ORANGE_MICRO, PROD_IBOT2), .driver_info = (unsigned long) "Orange Micro iBOT2", }, { } }; MODULE_DEVICE_TABLE (usb, device_table); /********************************************************************** * Symbolic Names **********************************************************************/ /* Video4Linux1 Palettes */ static struct symbolic_list v4l1_plist[] = { { VIDEO_PALETTE_GREY, "GREY" }, { VIDEO_PALETTE_HI240, "HI240" }, { VIDEO_PALETTE_RGB565, "RGB565" }, { VIDEO_PALETTE_RGB24, "RGB24" }, { VIDEO_PALETTE_RGB32, "RGB32" }, { VIDEO_PALETTE_RGB555, "RGB555" }, { VIDEO_PALETTE_YUV422, "YUV422" }, { VIDEO_PALETTE_YUYV, "YUYV" }, { VIDEO_PALETTE_UYVY, "UYVY" }, { VIDEO_PALETTE_YUV420, "YUV420" }, { VIDEO_PALETTE_YUV411, "YUV411" }, { VIDEO_PALETTE_RAW, "RAW" }, { VIDEO_PALETTE_YUV422P,"YUV422P" }, { VIDEO_PALETTE_YUV411P,"YUV411P" }, { VIDEO_PALETTE_YUV420P,"YUV420P" }, { VIDEO_PALETTE_YUV410P,"YUV410P" }, { -1, NULL } }; #if defined(CONFIG_VIDEO_PROC_FS) static struct symbolic_list senlist[] = { { CC_OV76BE, "OV76BE" }, { CC_OV7610, "OV7610" }, { CC_OV7620, "OV7620" }, { CC_OV7620AE, "OV7620AE" }, { CC_OV6620, "OV6620" }, { CC_OV6630, "OV6630" }, { CC_OV6630AE, "OV6630AE" }, { CC_OV6630AF, "OV6630AF" }, { -1, NULL } }; #endif /* URB error codes: */ static struct symbolic_list urb_errlist[] = { { -ENOSR, "Buffer error (overrun)" }, { -EPIPE, "Stalled (device not responding)" }, { -EOVERFLOW, "Babble (bad cable?)" }, { -EPROTO, "Bit-stuff error (bad cable?)" }, { -EILSEQ, "CRC/Timeout" }, { -ETIMEDOUT, "NAK (device does not respond)" }, { -1, NULL } }; static const char *v4l1_ioctls[] = { "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", "SMICROCODE", "GVBIFMT", "SVBIFMT" }; #define NUM_V4L1_IOCTLS (sizeof(v4l1_ioctls)/sizeof(char*)) /********************************************************************** * Prototypes **********************************************************************/ static int ov7xx0_configure(struct usb_ovfx2 *); /********************************************************************** * Memory management **********************************************************************/ /* Here we want the physical address of the memory. * This is used when initializing the contents of the area. */ static inline unsigned long kvirt_to_pa(unsigned long adr) { unsigned long kva, ret; kva = (unsigned long) page_address(vmalloc_to_page((void *)adr)); kva |= adr & (PAGE_SIZE-1); /* restore the offset */ ret = __pa(kva); return ret; } 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) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 68) SetPageReserved(vmalloc_to_page((void *)adr)); #else mem_map_reserve(vmalloc_to_page((void *)adr)); #endif 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) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 68) ClearPageReserved(vmalloc_to_page((void *)adr)); #else mem_map_unreserve(vmalloc_to_page((void *)adr)); #endif adr += PAGE_SIZE; size -= PAGE_SIZE; } vfree(mem); } /********************************************************************** * /proc interface * Based on the CPiA driver version 0.7.4 -claudio **********************************************************************/ #if defined(CONFIG_VIDEO_PROC_FS) static struct proc_dir_entry *ovfx2_proc_entry = NULL; extern struct proc_dir_entry *video_proc_entry; /* Prototypes */ static int sensor_get_picture(struct usb_ovfx2 *, struct video_picture *); static int sensor_get_control(struct usb_ovfx2 *, int, int *); #define YES_NO(x) ((x) ? "yes" : "no") /* /proc/video/ovfx2//info */ static int ovfx2_read_proc_info(char *page, char **start, off_t off, int count, int *eof, void *data) { char *out = page; int i, len; struct usb_ovfx2 *ov = data; struct video_picture p; int exp = 0; if (!ov || !ov->dev) return -ENODEV; sensor_get_picture(ov, &p); sensor_get_control(ov, OVCAMCHIP_CID_EXP, &exp); /* IMPORTANT: This output MUST be kept under PAGE_SIZE * or we need to get more sophisticated. */ out += sprintf(out, "driver_version : %s\n", DRIVER_VERSION); out += sprintf(out, "model : %s\n", ov->desc); out += sprintf(out, "streaming : %s\n", YES_NO(ov->streaming)); out += sprintf(out, "grabbing : %s\n", YES_NO(ov->grabbing)); out += sprintf(out, "subcapture : %s\n", YES_NO(ov->sub_flag)); out += sprintf(out, "sub_size : %d %d %d %d\n", ov->subx, ov->suby, ov->subw, ov->subh); out += sprintf(out, "brightness : %d\n", p.brightness >> 8); out += sprintf(out, "colour : %d\n", p.colour >> 8); out += sprintf(out, "contrast : %d\n", p.contrast >> 8); out += sprintf(out, "hue : %d\n", p.hue >> 8); out += sprintf(out, "exposure : %d\n", exp); out += sprintf(out, "num_frames : %d\n", OVFX2_NUMFRAMES); for (i = 0; i < OVFX2_NUMFRAMES; i++) { out += sprintf(out, "frame : %d\n", i); out += sprintf(out, " size : %d %d\n", ov->frame[i].width, ov->frame[i].height); out += sprintf(out, " format : %s\n", symbolic(v4l1_plist, ov->frame[i].format)); } out += sprintf(out, "sensor : %s\n", symbolic(senlist, ov->sensor)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 20) out += sprintf(out, "topology : %s\n", ov->usb_path); #else out += sprintf(out, "usb_bus : %d\n", ov->dev->bus->busnum); out += sprintf(out, "usb_device : %d\n", ov->dev->devnum); #endif out += sprintf(out, "i2c_adap_id : %d\n", i2c_adapter_id(&ov->i2c_adap)); len = out - page; len -= off; if (len < count) { *eof = 1; if (len <= 0) return 0; } else len = count; *start = page + off; return len; } static void create_proc_ovfx2_cam(struct usb_ovfx2 *ov) { char dirname[10]; if (!ovfx2_proc_entry || !ov) return; /* Create per-device directory */ snprintf(dirname, 10, "%d", ov->vdev.minor); ov->proc_devdir = create_proc_entry(dirname, S_IFDIR, ovfx2_proc_entry); if (!ov->proc_devdir) return; ov->proc_devdir->owner = THIS_MODULE; /* Create "info" entry (human readable device information) */ ov->proc_info = create_proc_read_entry("info", S_IFREG|S_IRUGO|S_IWUSR, ov->proc_devdir, ovfx2_read_proc_info, ov); if (!ov->proc_info) return; ov->proc_info->owner = THIS_MODULE; } static void destroy_proc_ovfx2_cam(struct usb_ovfx2 *ov) { char dirname[10]; if (!ov || !ov->proc_devdir) return; snprintf(dirname, 10, "%d", ov->vdev.minor); /* Destroy "info" entry */ if (ov->proc_info) { remove_proc_entry("info", ov->proc_devdir); ov->proc_info = NULL; } /* Destroy per-device directory */ remove_proc_entry(dirname, ovfx2_proc_entry); ov->proc_devdir = NULL; } static void proc_ovfx2_create(void) { if (video_proc_entry == NULL) { err("Error: /proc/video/ does not exist"); return; } ovfx2_proc_entry = create_proc_entry("ovfx2", S_IFDIR, video_proc_entry); if (ovfx2_proc_entry) ovfx2_proc_entry->owner = THIS_MODULE; else err("Unable to create /proc/video/ovfx2"); } static void proc_ovfx2_destroy(void) { if (ovfx2_proc_entry == NULL) return; remove_proc_entry("ovfx2", video_proc_entry); } #else static inline void create_proc_ovfx2_cam(struct usb_ovfx2 *ov) { } static inline void destroy_proc_ovfx2_cam(struct usb_ovfx2 *ov) { } static inline void proc_ovfx2_create(void) { } static inline void proc_ovfx2_destroy(void) { } #endif /* #ifdef CONFIG_VIDEO_PROC_FS */ /********************************************************************** * * Register I/O * **********************************************************************/ /* Write an OVFX2 register */ static int reg_w(struct usb_ovfx2 *ov, unsigned char reg, unsigned char value) { int rc; PDEBUG(5, "0x%02X:0x%02X", reg, value); /* We don't use cbuf here, but the lock ensures we're done before * disconnect() completes */ down(&ov->cbuf_lock); if (!ov->cbuf) { up(&ov->cbuf_lock); return -ENODEV; } rc = usb_control_msg(ov->dev, usb_sndctrlpipe(ov->dev, 0), OVFX2_REQ_REG_WRITE, USB_TYPE_VENDOR | USB_RECIP_DEVICE, (__u16)value, (__u16)reg, NULL, 0, HZ); up(&ov->cbuf_lock); if (rc < 0) err("reg write: error %d: %s", rc, symbolic(urb_errlist, rc)); return rc; } /* Read from an OVFX2 register */ /* returns: negative is error, pos or zero is data */ static int reg_r(struct usb_ovfx2 *ov, unsigned char reg) { int rc; down(&ov->cbuf_lock); if (!ov->cbuf) { up(&ov->cbuf_lock); return -ENODEV; } rc = usb_control_msg(ov->dev, usb_rcvctrlpipe(ov->dev, 0), OVFX2_REQ_REG_READ, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 0, (__u16)reg, &ov->cbuf[0], 1, HZ); if (rc < 0) { err("reg read: error %d: %s", rc, symbolic(urb_errlist, rc)); } else { rc = ov->cbuf[0]; PDEBUG(5, "0x%02X:0x%02X", reg, ov->cbuf[0]); } up(&ov->cbuf_lock); return rc; } /* * Writes bits at positions specified by mask to an OVFX2 reg. Bits that are in * the same position as 1's in "mask" are cleared and set to "value". Bits * that are in the same position as 0's in "mask" are preserved, regardless * of their respective state in "value". */ static int reg_w_mask(struct usb_ovfx2 *ov, unsigned char reg, unsigned char value, unsigned char mask) { int ret; unsigned char oldval, newval; if (mask == 0xff) { newval = value; } else { ret = reg_r(ov, reg); if (ret < 0) return ret; oldval = (unsigned char) ret; oldval &= (~mask); /* Clear the masked bits */ value &= mask; /* Enforce mask on value */ newval = oldval | value; /* Set the desired bits */ } return (reg_w(ov, reg, newval)); } static int i2c_w(struct usb_ovfx2 *ov, unsigned char reg, unsigned char value) { return i2c_smbus_write_byte_data(&ov->internal_client, reg, value); } static int i2c_w_mask(struct usb_ovfx2 *ov, unsigned char reg, unsigned char value, unsigned char mask) { int rc; unsigned char oldval, newval; if (mask == 0xff) { newval = value; } else { rc = i2c_smbus_read_byte_data(&ov->internal_client, reg); if (rc < 0) return rc; oldval = (unsigned char) rc; oldval &= (~mask); /* Clear the masked bits */ value &= mask; /* Enforce mask on value */ newval = oldval | value; /* Set the desired bits */ } return i2c_smbus_write_byte_data(&ov->internal_client, reg, newval); } /********************************************************************** * * Low-level I2C I/O functions * **********************************************************************/ /* NOTE: Do not call this function directly! * Sets I2C read and write slave IDs, if they have changed. * Returns <0 for error */ static int ovfx2_i2c_adap_set_slave(struct usb_ovfx2 *ov, unsigned char slave) { int rc; if (ov->last_slave == slave) return 0; /* invalidate slave */ ov->last_slave = 0; rc = reg_w(ov, REG_I2C_ADDR, (slave<<1)); if (rc < 0) return rc; /* validate slave */ ov->last_slave = slave; return 0; } static int ovfx2_i2c_adap_read_byte_data(struct usb_ovfx2 *ov, unsigned char addr, unsigned char subaddr, unsigned char *val) { int rc; /* Set slave addresses */ rc = ovfx2_i2c_adap_set_slave(ov, addr); if (rc < 0) return rc; down(&ov->cbuf_lock); if (!ov->cbuf) { up(&ov->cbuf_lock); return -ENODEV; } rc = usb_control_msg(ov->dev, usb_rcvctrlpipe(ov->dev, 0), OVFX2_REQ_I2C_READ, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 0, (__u16)subaddr, &ov->cbuf[0], 1, HZ); if (rc < 0) { PDEBUG(5, "error %d: %s", rc, symbolic(urb_errlist, rc)); } else { *val = ov->cbuf[0]; PDEBUG(5, "0x%02X:0x%02X", subaddr, ov->cbuf[0]); } up(&ov->cbuf_lock); return rc; } static int ovfx2_i2c_adap_write_byte_data(struct usb_ovfx2 *ov, unsigned char addr, unsigned char subaddr, unsigned char val) { int rc; PDEBUG(5, "(0x%02X) 0x%02X:0x%02X", addr<<1, subaddr, val); /* Set slave addresses */ rc = ovfx2_i2c_adap_set_slave(ov, addr); if (rc < 0) return rc; /* We don't use cbuf here, but the lock ensures we're done before * disconnect() completes */ down(&ov->cbuf_lock); if (!ov->cbuf) { up(&ov->cbuf_lock); return -ENODEV; } rc = usb_control_msg(ov->dev, usb_sndctrlpipe(ov->dev, 0), OVFX2_REQ_I2C_WRITE, USB_TYPE_VENDOR | USB_RECIP_DEVICE, (__u16)val, (__u16)subaddr, NULL, 0, HZ); up(&ov->cbuf_lock); if (rc < 0) PDEBUG(5, "error %d: %s", rc, symbolic(urb_errlist, rc)); return rc; } static int write_regvals(struct usb_ovfx2 *ov, struct regval *regvals) { int rc; while (regvals->mask != 0) { rc = reg_w_mask(ov, regvals->reg, regvals->val, regvals->mask); if (rc < 0) return rc; regvals++; } return 0; } #ifdef OVFX2_DEBUG static void dump_reg_range(struct usb_ovfx2 *ov, int reg1, int regn) { int i, rc; for (i = reg1; i <= regn; i++) { rc = reg_r(ov, i); info("OVFX2[0x%02X] = 0x%02X", i, rc); } } static void ovfx2_dump_regs(struct usb_ovfx2 *ov) { dump_reg_range(ov, 0x00, 0xff); } #endif /********************************************************************** * * Kernel I2C Interface * **********************************************************************/ static int ovfx2_i2c_validate_addr(struct usb_ovfx2 *ov, u16 addr) { switch (addr) { case OV7xx0_SID: return 0; default: PDEBUG(4, "Rejected slave ID 0x%04X", addr); return -EINVAL; } } static inline int sensor_cmd(struct usb_ovfx2 *ov, unsigned int cmd, void *arg) { struct i2c_client *c = ov->sensor_client; if (c && c->driver->command) return c->driver->command(ov->sensor_client, cmd, arg); else return -ENODEV; } #if 0 static void call_i2c_clients(struct usb_ovfx2 *ov, unsigned int cmd, void *arg) { struct i2c_client *client; int i; for (i = 0; i < I2C_CLIENT_MAX; i++) { client = ov->i2c_adap.clients[i]; /* Sensor_client is not called from here for now, since it has different enumeration for cmd */ if (client == ov->sensor_client) continue; if (client && client->driver->command) client->driver->command(client, cmd, arg); } } #endif static int ovfx2_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) { struct usb_ovfx2 *ov = i2c_get_adapdata(adapter); int rc = -ENOSYS; if (size == I2C_SMBUS_QUICK) { PDEBUG(4, "Got probed at addr 0x%04X", addr); rc = ovfx2_i2c_validate_addr(ov, addr); } else if (size == I2C_SMBUS_BYTE_DATA) { if (read_write == I2C_SMBUS_WRITE) { rc = ovfx2_i2c_adap_write_byte_data(ov, addr, command, data->byte); } else if (read_write == I2C_SMBUS_READ) { rc = ovfx2_i2c_adap_read_byte_data(ov, addr, command, &data->byte); } } else { warn("Unsupported I2C transfer mode (%d)", size); return -EINVAL; } /* This works around a bug in the I2C core */ if (rc > 0) rc = 0; return rc; } static u32 ovfx2_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE_DATA; } static int i2c_attach_inform(struct i2c_client *client) { struct usb_ovfx2 *ov = i2c_get_adapdata(client->adapter); int id = client->driver->id; if (id == I2C_DRIVERID_OVCAMCHIP) { int rc, mono = 0; ov->sensor_client = client; rc = sensor_cmd(ov, OVCAMCHIP_CMD_INITIALIZE, &mono); if (rc < 0) { err("ERROR: Sensor init failed (rc=%d)", rc); ov->sensor_client = NULL; return rc; } down(&ov->lock); if (sensor_cmd(ov, OVCAMCHIP_CMD_Q_SUBTYPE, &ov->sensor) < 0) rc = -EIO; else if (client->addr == OV7xx0_SID) rc = ov7xx0_configure(ov); else rc = -EINVAL; up(&ov->lock); if (rc) { ov->sensor_client = NULL; return rc; } } else { PDEBUG(1, "Rejected client [%s] with [%s]", i2c_clientname(client), client->driver->name); return -1; } PDEBUG(1, "i2c attach client [%s] with [%s]", i2c_clientname(client), client->driver->name); return 0; } static int i2c_detach_inform(struct i2c_client *client) { struct usb_ovfx2 *ov = i2c_get_adapdata(client->adapter); if (ov->sensor_client == client) { ov->sensor_client = NULL; } PDEBUG(1, "i2c detach [%s]", i2c_clientname(client)); return 0; } static int ovfx2_i2c_control(struct i2c_adapter *adapter, unsigned int cmd, unsigned long arg) { return 0; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) static void ovfx2_i2c_inc_use(struct i2c_adapter *adap) { MOD_INC_USE_COUNT; } static void ovfx2_i2c_dec_use(struct i2c_adapter *adap) { MOD_DEC_USE_COUNT; } #endif static struct i2c_algorithm ovfx2_i2c_algo = { .name = "OVFX2 algorithm", .id = I2C_ALGO_SMBUS, .smbus_xfer = ovfx2_smbus_xfer, .algo_control = ovfx2_i2c_control, .functionality = ovfx2_i2c_func, }; static struct i2c_adapter i2c_adap_template = { I2C_DEVNAME("(unset)"), .id = I2C_ALGO_SMBUS, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 70) .class = I2C_ADAP_CLASS_CAM_DIGITAL, #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) .inc_use = ovfx2_i2c_inc_use, .dec_use = ovfx2_i2c_dec_use, #else .owner = THIS_MODULE, #endif .client_register = i2c_attach_inform, .client_unregister = i2c_detach_inform, }; static int ovfx2_init_i2c(struct usb_ovfx2 *ov) { memcpy(&ov->i2c_adap, &i2c_adap_template, sizeof(struct i2c_adapter)); sprintf(i2c_adapname(&ov->i2c_adap), "OVFX2 #%d", ov->vdev.minor); i2c_set_adapdata(&ov->i2c_adap, ov); ov->i2c_adap.algo = &ovfx2_i2c_algo; ov->i2c_adap.id |= I2C_HW_SMBUS_OVFX2; ov->internal_client.adapter = &ov->i2c_adap; PDEBUG(4, "Registering I2C bus with kernel"); return i2c_add_adapter(&ov->i2c_adap); } /*****************************************************************************/ /* Pause video streaming */ static inline int ovfx2_pause(struct usb_ovfx2 *ov) { PDEBUG(4, "pausing stream"); ov->stopped = 1; return reg_w_mask(ov, 0x0f, 0x00, 0x02); } /* Resume video streaming if it was stopped. */ static inline int ovfx2_resume(struct usb_ovfx2 *ov) { if (ov->stopped) { PDEBUG(4, "resuming stream"); ov->stopped = 0; return reg_w_mask(ov, 0x0f, 0x02, 0x02); } return 0; } /* Returns 1 if image streaming needs to be stopped while setting the specified * control, and returns 0 if not */ static int sensor_needs_stop(struct usb_ovfx2 *ov, int cid) { if (!ov->stop_during_set) return 0; /* FIXME: I don't know whether the FX2 needs to be stopped yet. Don't * do it yet, since it might not be safe to pause the stream while bulk * requests are active */ return 0; switch (cid) { case OVCAMCHIP_CID_CONT: case OVCAMCHIP_CID_BRIGHT: case OVCAMCHIP_CID_SAT: case OVCAMCHIP_CID_HUE: case OVCAMCHIP_CID_EXP: return 1; } return 0; } static int sensor_set_control(struct usb_ovfx2 *ov, int cid, int val) { struct ovcamchip_control ctl; int rc; if (sensor_needs_stop(ov, cid)) if (ovfx2_pause(ov) < 0) return -EIO; ctl.id = cid; ctl.value = val; rc = sensor_cmd(ov, OVCAMCHIP_CMD_S_CTRL, &ctl); if (ovfx2_resume(ov) < 0) return -EIO; return rc; } static int sensor_get_control(struct usb_ovfx2 *ov, int cid, int *val) { struct ovcamchip_control ctl; int rc; ctl.id = cid; rc = sensor_cmd(ov, OVCAMCHIP_CMD_G_CTRL, &ctl); if (rc >= 0) *val = ctl.value; return rc; } static int sensor_set_picture(struct usb_ovfx2 *ov, struct video_picture *p) { int rc; PDEBUG(4, "sensor_set_picture"); rc = reg_w(ov, REG_CNTR, p->contrast >> 8); if (rc < 0) return rc; rc = reg_w(ov, REG_BRIGHT, (p->brightness >> 8) - 127); if (rc < 0) return rc; rc = reg_w(ov, REG_SAT, p->colour >> 8); if (rc < 0) return rc; rc = reg_w(ov, REG_HUE, (p->hue >> 8) - 127); if (rc < 0) return rc; return 0; } static int sensor_get_picture(struct usb_ovfx2 *ov, struct video_picture *p) { int rc; PDEBUG(4, "sensor_get_picture"); /* Don't return error if a setting is unsupported, or rest of settings * will not be performed */ rc = reg_r(ov, REG_CNTR); if (rc < 0) return rc; p->contrast = rc << 8; rc = reg_r(ov, REG_BRIGHT); if (rc < 0) return rc; p->brightness = (rc + 127) << 8; rc = reg_r(ov, REG_SAT); if (rc < 0) return rc; p->colour = rc << 8; rc = reg_r(ov, REG_HUE); if (rc < 0) return rc; p->hue = (rc + 127) << 8; p->whiteness = 105 << 8; return 0; } /* Matches the sensor's internal frame rate to the lighting frequency. * Valid frequencies are: * 50 - 50Hz, for European and Asian lighting * 60 - 60Hz, for American lighting * * Returns: 0 for success */ static int sensor_set_light_freq(struct usb_ovfx2 *ov, int freq) { if (freq != 50 && freq != 60) { err("Invalid light freq (%d Hz)", freq); return -EINVAL; } return sensor_set_control(ov, OVCAMCHIP_CID_FREQ, freq); } /* Returns number of bits per pixel (regardless of where they are located; * planar or not), or zero for unsupported format. */ static inline int get_depth(int palette) { switch (palette) { case VIDEO_PALETTE_RGB24: return 24; /* Planar */ default: return 0; /* Invalid format */ } } /* Bytes per frame. Used by read(). Return of 0 indicates error */ static inline long int get_frame_length(struct ovfx2_frame *frame) { if (!frame) return 0; else return ((frame->width * frame->height * get_depth(frame->format)) >> 3); } static int set_ov_sensor_window(struct usb_ovfx2 *ov, int width, int height, int mode, int sub_flag) { struct ovcamchip_window win; int rc; win.format = mode; /* Unless subcapture is enabled, center the image window and downsample * if possible to increase the field of view */ if (sub_flag) { win.x = ov->subx; win.y = ov->suby; win.width = ov->subw; win.height = ov->subh; win.quarter = 0; } else { win.x = 0; win.y = 0; win.width = ov->maxwidth; win.height = ov->maxheight; win.quarter = 0; } if (clockdiv >= 0) win.clockdiv = clockdiv; else win.clockdiv = 0x01; /* Always use the max frame rate */ rc = sensor_cmd(ov, OVCAMCHIP_CMD_S_MODE, &win); if (rc < 0) return rc; if (framedrop >= 0) i2c_w(ov, 0x16, framedrop); return 0; } /* Sets up the OVFX2 with the given image parameters */ static int ovfx2_mode_init_regs(struct usb_ovfx2 *ov, int width, int height, int mode, int sub_flag) { if (sub_flag) { width = ov->subw; height = ov->subh; } PDEBUG(3, "width:%d, height:%d, mode:%d, sub:%d", width, height, mode, sub_flag); if (width < ov->minwidth || height < ov->minheight) { err("Requested dimensions are too small"); return -EINVAL; } /* Set palette */ /* Set bus width */ /* Set resolution */ /* Set framerate/clock */ reg_w_mask(ov, 0x0f, 0x08, 0x08); /* Reinitialize? */ return 0; } /* This is a wrapper around the FX2 and sensor specific functions */ static int mode_init_regs(struct usb_ovfx2 *ov, int width, int height, int mode, int sub_flag) { int rc = 0; rc = ovfx2_mode_init_regs(ov, width, height, mode, sub_flag); if (FATAL_ERROR(rc)) return rc; rc = set_ov_sensor_window(ov, width, height, mode, sub_flag); if (FATAL_ERROR(rc)) return rc; /********* The following settings are specific to this camera ********/ i2c_w(ov, 0x03, 0x80); /* Hardcoded saturation value */ i2c_w_mask(ov, 0x12, 0x48, 0x48); /* Enable h-mirror and raw output */ i2c_w_mask(ov, 0x27, 0x10, 0x10); /* Bypass RGB matrix */ i2c_w_mask(ov, 0x28, 0x04, 0x04); /* G/BR (YG) mode */ i2c_w_mask(ov, 0x2a, 0x00, 0x10); /* No UV 2-pixel delay */ i2c_w_mask(ov, 0x2d, 0x40, 0x40); /* QVGA 60 FPS (redundant?) */ i2c_w(ov, 0x67, 0x00); /* Disable YUV postprocess */ i2c_w_mask(ov, 0x2d, 0x00, 0x0f); /* Disable anti-alias */ i2c_w(ov, 0x24, 0x20); /* Use QVGA white pixel ratio */ i2c_w(ov, 0x25, 0x30); /* Use QVGA black pixel ratio */ i2c_w(ov, 0x60, 0x26); /* Unknown */ i2c_w(ov, 0x74, 0x20); /* AGC max gain = 4x */ i2c_w(ov, 0x06, 0x68); /* Hardcoded brightness value */ i2c_w(ov, 0x14, 0x04); /* */ return 0; } /* Set up the camera chip with the options provided by the module params */ static int camchip_init_settings(struct usb_ovfx2 *ov) { int rc; rc = sensor_set_control(ov, OVCAMCHIP_CID_AUTOBRIGHT, autobright); if (FATAL_ERROR(rc)) return rc; rc = sensor_set_control(ov, OVCAMCHIP_CID_AUTOEXP, autoexp); if (FATAL_ERROR(rc)) return rc; rc = sensor_set_control(ov, OVCAMCHIP_CID_BANDFILT, bandingfilter); if (FATAL_ERROR(rc)) return rc; if (lightfreq) { rc = sensor_set_light_freq(ov, lightfreq); if (FATAL_ERROR(rc)) return rc; } rc = sensor_set_control(ov, OVCAMCHIP_CID_BACKLIGHT, backlight); if (FATAL_ERROR(rc)) return rc; rc = sensor_set_control(ov, OVCAMCHIP_CID_MIRROR, mirror); if (FATAL_ERROR(rc)) return rc; return 0; } /* This sets the default image parameters. This is useful for apps that use * read() and do not set these. */ static int ovfx2_set_default_params(struct usb_ovfx2 *ov) { int i; /* Set default sizes in case IOCTL (VIDIOCMCAPTURE) is not used * (using read() instead). */ for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].width = ov->maxwidth; ov->frame[i].height = ov->maxheight; ov->frame[i].bytes_read = 0; if (force_palette) ov->frame[i].format = force_palette; else ov->frame[i].format = VIDEO_PALETTE_RGB24; ov->frame[i].depth = get_depth(ov->frame[i].format); } PDEBUG(3, "%dx%d, %s", ov->maxwidth, ov->maxheight, symbolic(v4l1_plist, ov->frame[0].format)); /* Initialize to max width/height, default palette */ if (mode_init_regs(ov, ov->maxwidth, ov->maxheight, ov->frame[0].format, 0) < 0) return -EINVAL; return 0; } /********************************************************************** * * Frame buffering * **********************************************************************/ /* Sleeps until no frames are active. Returns !0 if got signal or unplugged */ static int ov51x_wait_frames_inactive(struct usb_ovfx2 *ov) { int rc; rc = wait_event_interruptible(ov->wq, ov->curframe < 0 || !ov->present); if (rc) return rc; if (ov->present) return 0; else return -ENODEV; } /********************************************************************** * * Raw data parsing * **********************************************************************/ static void gbgr_to_bgr24(struct ovfx2_frame *frame, unsigned char *pIn, unsigned char *pOut) { const unsigned int a = frame->rawwidth * frame->rawheight / 2; int i; for (i = 0; i < a; i++) { pOut[0] = pIn[1]; pOut[1] = pIn[0]; pOut[2] = pIn[3]; pOut[3] = pIn[1]; pOut[4] = pIn[2]; pOut[5] = pIn[3]; pIn += 4; pOut += 6; } } /* Flip image vertically */ static void vert_mirror(unsigned char *pIn, unsigned char *pOut, unsigned int width, unsigned int height, unsigned int depth) { const unsigned int linesize = width * depth; int i; pOut += height * linesize; for (i = 0; i < height; i++) { pOut -= linesize; memcpy(pOut, pIn, linesize); pIn += linesize; } } /********************************************************************** * * Image processing * **********************************************************************/ static void ovfx2_postprocess(struct usb_ovfx2 *ov, struct ovfx2_frame *frame) { if (dumppix == 1) { /* Dump with color decoding */ memset(frame->data, 0, MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); PDEBUG(4, "Dumping %d bytes", frame->bytes_recvd); gbgr_to_bgr24(frame, frame->rawdata, frame->data); } else if (dumppix == 2) { /* Dump raw data */ memset(frame->data, 0, MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); PDEBUG(4, "Dumping %d bytes", frame->bytes_recvd); memcpy(frame->data, frame->rawdata, frame->bytes_recvd); } else { /* FIXME: Remove this once decoding is stable */ memset(frame->data, 0, MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); memset(frame->tempdata, 0, MAX_RAW_DATA_SIZE(ov->maxwidth, ov->maxheight)); vert_mirror(frame->rawdata, frame->tempdata, frame->rawwidth, frame->rawheight, 2); gbgr_to_bgr24(frame, frame->tempdata, frame->data); } } /********************************************************************** * * OV51x data transfer, IRQ handler * **********************************************************************/ static inline void ovfx2_move_data(struct usb_ovfx2 *ov, unsigned char *in, int n) { int max_raw = MAX_RAW_DATA_SIZE(ov->maxwidth, ov->maxheight); struct ovfx2_frame *frame = &ov->frame[ov->curframe]; struct timeval *ts; // info("bytes_recvd = %d; n = %d", frame->bytes_recvd, n); if (frame->scanstate == STATE_LOCKED) { PDEBUG(4, "Starting capture on frame %d", frame->framenum); frame->scanstate = STATE_RECEIVING; frame->bytes_recvd = 0; } if (n) { int b = n; /* Number of image data bytes */ #if 1 /* The first 2560 bytes of data (first 2 lines?) are invalid */ if (frame->bytes_recvd == 0) { in += 2560; b -= 2560; } #endif frame->bytes_recvd += b; if (frame->bytes_recvd <= max_raw) memcpy(frame->rawdata + frame->bytes_recvd - b, in, b); else PDEBUG(5, "Raw data buffer overrun!! (%d)", frame->bytes_recvd - max_raw); } else { PDEBUG(5, "Empty bulk packet"); } /* Short packets indicate EOF */ if (n < OVFX2_BULK_SIZE) { int nextf; PDEBUG(4, "Frame end, curframe = %d, recvd=%d", ov->curframe, frame->bytes_recvd); if (frame->bytes_recvd < max_raw) { PDEBUG(3, "Frame was short; discarding"); frame->grabstate = FRAME_ERROR; wake_up_interruptible(&frame->wq); return; } /* Don't allow byte count to exceed buffer size */ RESTRICT_TO_RANGE(frame->bytes_recvd, 8, max_raw); ts = (struct timeval *)(frame->data + MAX_FRAME_SIZE(ov->maxwidth, ov->maxheight)); do_gettimeofday(ts); // FIXME: Since we don't know the header formats yet, // there is no way to know what the actual image size is frame->rawwidth = frame->width; frame->rawheight = frame->height; /* Don't wake up user for same frame more than once */ if (frame->grabstate != FRAME_DONE) { frame->grabstate = FRAME_DONE; wake_up_interruptible(&frame->wq); } /* If next frame is ready or grabbing, point to it */ nextf = (ov->curframe + 1) % OVFX2_NUMFRAMES; if (ov->frame[nextf].grabstate == FRAME_READY || ov->frame[nextf].grabstate == FRAME_GRABBING) { ov->curframe = nextf; ov->frame[nextf].scanstate = STATE_LOCKED; } else { if (ov->frame[nextf].grabstate == FRAME_DONE) { PDEBUG(4, "No empty frames left"); } else { PDEBUG(4, "Frame not ready? state = %d", ov->frame[nextf].grabstate); } ov->curframe = -1; } } } static void #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 51) ovfx2_bulk_irq(struct urb *urb, struct pt_regs *regs) #else ovfx2_bulk_irq(struct urb *urb) #endif { int i; int actlen = urb->actual_length; struct usb_ovfx2 *ov; struct ovfx2_sbuf *sbuf; if (!urb->context) { PDEBUG(4, "no context"); return; } sbuf = urb->context; ov = sbuf->ov; if (!ov || !ov->dev || !ov->user) { PDEBUG(4, "no device, or not open"); return; } if (!ov->streaming) { PDEBUG(4, "hmmm... not streaming, but got interrupt"); return; } if (urb->status == -ENOENT || urb->status == -ECONNRESET) { PDEBUG(4, "URB unlinked"); return; } if (urb->status != -EINPROGRESS && urb->status != 0) { err("ERROR: urb->status=%d: %s", urb->status, symbolic(urb_errlist, urb->status)); } PDEBUG(5, "sbuf[%d]: Got bulk completion: actlen=%d", sbuf->n, actlen); /* Warning: Don't copy data if no frame active! */ if (ov->curframe >= 0) { ovfx2_move_data(ov, urb->transfer_buffer, actlen); } else if (waitqueue_active(&ov->wq)) { wake_up_interruptible(&ov->wq); } /* Resubmit this URB */ urb->status = 0; // FIXME: Is this necessary? What about w/ OV511/OV518? urb->dev = ov->dev; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 4) if ((i = usb_submit_urb(urb, GFP_ATOMIC)) != 0) #else if ((i = usb_submit_urb(urb)) != 0) #endif err("usb_submit_urb() ret %d", i); return; } /**************************************************************************** * * Stream initialization and termination * ***************************************************************************/ static int ovfx2_start_stream(struct usb_ovfx2 *ov) { struct urb *urb; int err, n, size; PDEBUG(3, "*** Starting video stream ***"); /* Start stream */ err = reg_w_mask(ov, 0x0f, 0x02, 0x02); if (err) return err; ov->curframe = -1; size = OVFX2_BULK_SIZE; for (n = 0; n < OVFX2_NUMSBUF; n++) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 5) urb = usb_alloc_urb(0, GFP_KERNEL); #else urb = usb_alloc_urb(0); #endif if (!urb) { err("start stream: usb_alloc_urb ret. NULL"); return -ENOMEM; } usb_fill_bulk_urb(urb, ov->dev, usb_rcvbulkpipe(ov->dev, OVFX2_BULK_ENDPOINT_ADDR), ov->sbuf[n].data, size, ovfx2_bulk_irq, &ov->sbuf[n]); ov->sbuf[n].urb = urb; } ov->streaming = 1; for (n = 0; n < OVFX2_NUMSBUF; n++) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 4) err = usb_submit_urb(ov->sbuf[n].urb, GFP_KERNEL); #else err = usb_submit_urb(ov->sbuf[n].urb); #endif if (err) { err("start stream: usb_submit_urb(%d) ret %d", n, err); return err; } } return 0; } static void ovfx2_unlink_bulk(struct usb_ovfx2 *ov) { int n; /* Unschedule all of the bulk td's */ for (n = OVFX2_NUMSBUF - 1; n >= 0; n--) { if (ov->sbuf[n].urb) { usb_unlink_urb(ov->sbuf[n].urb); usb_free_urb(ov->sbuf[n].urb); ov->sbuf[n].urb = NULL; } } } static int ovfx2_stop_stream(struct usb_ovfx2 *ov) { if (!ov->streaming) return -ENODEV; PDEBUG(3, "*** Stopping capture ***"); ov->streaming = 0; ovfx2_unlink_bulk(ov); /* Stop stream */ return reg_w_mask(ov, 0x0f, 0x00, 0x02); } static int ovfx2_new_frame(struct usb_ovfx2 *ov, int framenum) { struct ovfx2_frame *frame; int newnum; PDEBUG(4, "ov->curframe = %d, framenum = %d", ov->curframe, 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 (ov->curframe == -1) { newnum = (framenum - 1 + OVFX2_NUMFRAMES) % OVFX2_NUMFRAMES; if (ov->frame[newnum].grabstate == FRAME_READY) framenum = newnum; } else if (ov->frame[framenum].grabstate == FRAME_ERROR) { PDEBUG(4, "restoring broken frame #%d", framenum); } else return 0; frame = &ov->frame[framenum]; PDEBUG(4, "framenum = %d, width = %d, height = %d", framenum, frame->width, frame->height); frame->grabstate = FRAME_GRABBING; frame->scanstate = STATE_SCANNING; ov->curframe = framenum; /* Make sure it's not too big */ if (frame->width > ov->maxwidth) frame->width = ov->maxwidth; frame->width &= ~7L; /* Multiple of 8 */ if (frame->height > ov->maxheight) frame->height = ov->maxheight; frame->height &= ~3L; /* Multiple of 4 */ return 0; } /**************************************************************************** * * Buffer management * ***************************************************************************/ /* * - You must acquire buf_lock before entering this function. * - Because this code will free any non-null pointer, you must be sure to null * them if you explicitly free them somewhere else! */ static void ovfx2_do_dealloc(struct usb_ovfx2 *ov) { int i; PDEBUG(4, "entered"); if (ov->fbuf) { rvfree(ov->fbuf, OVFX2_NUMFRAMES * MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); ov->fbuf = NULL; } if (ov->rawfbuf) { vfree(ov->rawfbuf); ov->rawfbuf = NULL; } if (ov->tempfbuf) { vfree(ov->tempfbuf); ov->tempfbuf = NULL; } for (i = 0; i < OVFX2_NUMSBUF; i++) { if (ov->sbuf[i].data) { kfree(ov->sbuf[i].data); ov->sbuf[i].data = NULL; } } for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].data = NULL; ov->frame[i].rawdata = NULL; ov->frame[i].tempdata = NULL; } PDEBUG(4, "buffer memory deallocated"); ov->buf_state = BUF_NOT_ALLOCATED; PDEBUG(4, "leaving"); } static int ovfx2_alloc(struct usb_ovfx2 *ov) { int i; const int w = ov->maxwidth; const int h = ov->maxheight; const int data_bufsize = OVFX2_NUMFRAMES * MAX_DATA_SIZE(w, h); const int raw_bufsize = OVFX2_NUMFRAMES * MAX_RAW_DATA_SIZE(w, h); PDEBUG(4, "entered"); down(&ov->buf_lock); if (ov->buf_state == BUF_ALLOCATED) goto out; ov->fbuf = rvmalloc(data_bufsize); if (!ov->fbuf) goto error; ov->rawfbuf = vmalloc(raw_bufsize); if (!ov->rawfbuf) goto error; memset(ov->rawfbuf, 0, raw_bufsize); ov->tempfbuf = vmalloc(raw_bufsize); if (!ov->tempfbuf) goto error; memset(ov->tempfbuf, 0, raw_bufsize); for (i = 0; i < OVFX2_NUMSBUF; i++) { ov->sbuf[i].data = kmalloc(OVFX2_BULK_SIZE, GFP_KERNEL); if (!ov->sbuf[i].data) goto error; PDEBUG(4, "sbuf[%d] @ %p", i, ov->sbuf[i].data); } for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].data = ov->fbuf + i * MAX_DATA_SIZE(w, h); ov->frame[i].rawdata = ov->rawfbuf + i * MAX_RAW_DATA_SIZE(w, h); ov->frame[i].tempdata = ov->tempfbuf + i * MAX_RAW_DATA_SIZE(w, h); PDEBUG(4, "frame[%d] @ %p", i, ov->frame[i].data); } ov->buf_state = BUF_ALLOCATED; out: up(&ov->buf_lock); PDEBUG(4, "leaving"); return 0; error: ovfx2_do_dealloc(ov); up(&ov->buf_lock); PDEBUG(4, "errored"); return -ENOMEM; } static void ovfx2_dealloc(struct usb_ovfx2 *ov) { PDEBUG(4, "entered"); down(&ov->buf_lock); ovfx2_do_dealloc(ov); up(&ov->buf_lock); PDEBUG(4, "leaving"); } /**************************************************************************** * * V4L API * ***************************************************************************/ static int ovfx2_open(struct inode *inode, struct file *file) { struct video_device *vdev = video_devdata(file); struct usb_ovfx2 *ov = vdev->priv; int err, i; PDEBUG(4, "opening"); down(&ov->lock); err = -ENODEV; if (!ov->dev) goto out; if (ov->sensor == CC_UNKNOWN) { err("No sensor is detected yet"); goto out; } err = -EBUSY; if (ov->user) goto out; ov->sub_flag = 0; /* In case app doesn't set them... */ err = ovfx2_set_default_params(ov); if (err < 0) goto out; /* Make sure frames are reset */ for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].grabstate = FRAME_UNUSED; ov->frame[i].bytes_read = 0; } err = ovfx2_alloc(ov); if (err < 0) goto out; err = ovfx2_start_stream(ov); if (err) { ovfx2_dealloc(ov); goto out; } ov->user++; file->private_data = vdev; out: up(&ov->lock); return err; } static int ovfx2_release(struct inode *inode, struct file *file) { struct video_device *vdev = file->private_data; struct usb_ovfx2 *ov = vdev->priv; PDEBUG(4, "close"); down(&ov->lock); ov->user--; ovfx2_stop_stream(ov); ovfx2_dealloc(ov); up(&ov->lock); /* Device unplugged while open. Only a minimum of unregistration is done * here; the disconnect callback already did the rest. */ down(&ov_free_lock); if (!ov->dev) { kfree(ov); ov = NULL; } up(&ov_free_lock); file->private_data = NULL; return 0; } /* Do not call this function directly! */ static int ovfx2_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg) { struct video_device *vdev = file->private_data; struct usb_ovfx2 *ov = vdev->priv; switch (_IOC_TYPE(cmd)) { case 'v': PDEBUG(4, "ioctl 0x%x (v4l1, VIDIOC%s)", cmd, (_IOC_NR(cmd) < NUM_V4L1_IOCTLS) ? v4l1_ioctls[_IOC_NR(cmd)] : "???"); break; #if defined(HAVE_V4L2) case 'V': PDEBUG(4, "ioctl 0x%x (v4l2, %s)", cmd, v4l2_ioctl_names[_IOC_NR(cmd)]); break; #endif default: PDEBUG(4, "ioctl 0x%x (?)", cmd); } if (!ov->dev) return -EIO; switch (cmd) { case VIDIOCGCAP: { struct video_capability *b = arg; memset(b, 0, sizeof(struct video_capability)); sprintf(b->name, "OVFX2 USB Camera"); b->type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE; b->channels = 1; b->maxwidth = ov->maxwidth; b->maxheight = ov->maxheight; b->minwidth = ov->minwidth; b->minheight = ov->minheight; return 0; } case VIDIOCGCHAN: { struct video_channel *v = arg; if ((unsigned)(v->channel) > 0) return -EINVAL; v->norm = 0; v->type = VIDEO_TYPE_CAMERA; v->flags = 0; v->tuners = 0; sprintf(v->name, "%s", "Camera"); return 0; } case VIDIOCSCHAN: { struct video_channel *v = arg; return (v->channel == 0) ? 0 : -EINVAL; } case VIDIOCGPICT: { struct video_picture *p = arg; memset(p, 0, sizeof(struct video_picture)); if (sensor_get_picture(ov, p)) return -EIO; /* Can we get these from frame[0]? -claudio? */ p->depth = ov->frame[0].depth; p->palette = ov->frame[0].format; return 0; } case VIDIOCSPICT: { struct video_picture *p = arg; int i, rc; if (!get_depth(p->palette)) return -EINVAL; if (sensor_set_picture(ov, p)) return -EIO; if (force_palette && p->palette != force_palette) { info("SPICT: Palette rejected (%s)", symbolic(v4l1_plist, p->palette)); return -EINVAL; } // FIXME: Format should be independent of frames if (p->palette != ov->frame[0].format) { PDEBUG(4, "SPICT: Detected format change"); rc = ov51x_wait_frames_inactive(ov); if (rc) return rc; mode_init_regs(ov, ov->frame[0].width, ov->frame[0].height, p->palette, ov->sub_flag); } PDEBUG(4, "SPICT: Setting depth=%d, palette=%s", p->depth, symbolic(v4l1_plist, p->palette)); for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].depth = p->depth; ov->frame[i].format = p->palette; } return 0; } case VIDIOCGCAPTURE: { int *vf = arg; ov->sub_flag = *vf; return 0; } case VIDIOCSCAPTURE: { struct video_capture *vc = arg; if (vc->flags || vc->decimation) return -EINVAL; vc->x &= ~3L; vc->y &= ~1L; vc->y &= ~31L; if (vc->width == 0) vc->width = 32; vc->height /= 16; vc->height *= 16; if (vc->height == 0) vc->height = 16; ov->subx = vc->x; ov->suby = vc->y; ov->subw = vc->width; ov->subh = vc->height; return 0; } case VIDIOCSWIN: { struct video_window *vw = arg; int i, rc; PDEBUG(4, "Set window: %dx%d", vw->width, vw->height); /* This code breaks a few apps (Gnomemeeting, possibly others). Disable it until a solution can be found */ #if 0 if (vw->flags || vw->clipcount) return -EINVAL; #endif rc = ov51x_wait_frames_inactive(ov); if (rc) return rc; rc = mode_init_regs(ov, vw->width, vw->height, ov->frame[0].format, ov->sub_flag); if (rc < 0) return rc; for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].width = vw->width; ov->frame[i].height = vw->height; } return 0; } case VIDIOCGWIN: { struct video_window *vw = arg; memset(vw, 0, sizeof(struct video_window)); vw->x = 0; /* FIXME */ vw->y = 0; vw->width = ov->frame[0].width; vw->height = ov->frame[0].height; vw->flags = 30; PDEBUG(4, "Get window: %dx%d", vw->width, vw->height); return 0; } case VIDIOCGMBUF: { struct video_mbuf *vm = arg; int i; memset(vm, 0, sizeof(struct video_mbuf)); vm->size = OVFX2_NUMFRAMES * MAX_DATA_SIZE(ov->maxwidth, ov->maxheight); vm->frames = OVFX2_NUMFRAMES; vm->offsets[0] = 0; for (i = 1; i < OVFX2_NUMFRAMES; i++) { vm->offsets[i] = vm->offsets[i-1] + MAX_DATA_SIZE(ov->maxwidth, ov->maxheight); } return 0; } case VIDIOCMCAPTURE: { struct video_mmap *vm = arg; int rc, depth; unsigned int f = vm->frame; PDEBUG(4, "MCAPTURE: frame: %d, %dx%d, %s", f, vm->width, vm->height, symbolic(v4l1_plist, vm->format)); depth = get_depth(vm->format); if (!depth) { PDEBUG(2, "MCAPTURE: invalid format (%s)", symbolic(v4l1_plist, vm->format)); return -EINVAL; } if (f >= OVFX2_NUMFRAMES) { err("MCAPTURE: invalid frame (%d)", f); return -EINVAL; } if (vm->width > ov->maxwidth || vm->height > ov->maxheight) { err("MCAPTURE: requested dimensions too big"); return -EINVAL; } if (ov->frame[f].grabstate == FRAME_GRABBING) { PDEBUG(4, "MCAPTURE: already grabbing"); return -EBUSY; } if (force_palette && (vm->format != force_palette)) { PDEBUG(2, "MCAPTURE: palette rejected (%s)", symbolic(v4l1_plist, vm->format)); return -EINVAL; } if ((ov->frame[f].width != vm->width) || (ov->frame[f].height != vm->height) || (ov->frame[f].format != vm->format) || (ov->frame[f].sub_flag != ov->sub_flag) || (ov->frame[f].depth != depth)) { PDEBUG(4, "MCAPTURE: change in image parameters"); rc = ov51x_wait_frames_inactive(ov); if (rc) return rc; rc = mode_init_regs(ov, vm->width, vm->height, vm->format, ov->sub_flag); #if 0 if (rc < 0) { PDEBUG(1, "Got error while initializing regs "); return rc; } #endif ov->frame[f].width = vm->width; ov->frame[f].height = vm->height; ov->frame[f].format = vm->format; ov->frame[f].sub_flag = ov->sub_flag; ov->frame[f].depth = depth; } /* Mark it as ready */ ov->frame[f].grabstate = FRAME_READY; return ovfx2_new_frame(ov, f); } case VIDIOCSYNC: { unsigned int fnum = *((unsigned int *) arg); struct ovfx2_frame *frame; int rc; if (fnum >= OVFX2_NUMFRAMES) { err("SYNC: invalid frame (%d)", fnum); return -EINVAL; } frame = &ov->frame[fnum]; PDEBUG(4, "syncing to frame %d, grabstate = %d", fnum, frame->grabstate); switch (frame->grabstate) { case FRAME_UNUSED: return -EINVAL; case FRAME_READY: case FRAME_GRABBING: case FRAME_ERROR: redo: rc = wait_event_interruptible(frame->wq, (frame->grabstate == FRAME_DONE) || (frame->grabstate == FRAME_ERROR) || !ov->present); if (rc) return rc; if (!ov->present) return -ENODEV; if (frame->grabstate == FRAME_ERROR) { if ((rc = ovfx2_new_frame(ov, fnum)) < 0) return rc; goto redo; } /* Fall through */ case FRAME_DONE: PDEBUG(4, "SYNC: frame %d is DONE", fnum); frame->grabstate = FRAME_UNUSED; /* Decompression, format conversion, etc... */ ovfx2_postprocess(ov, frame); break; } /* end switch */ return 0; } case VIDIOCGFBUF: { struct video_buffer *vb = arg; memset(vb, 0, sizeof(struct video_buffer)); return 0; } case VIDIOCGUNIT: { struct video_unit *vu = arg; memset(vu, 0, sizeof(struct video_unit)); vu->video = ov->vdev.minor; vu->vbi = VIDEO_NO_UNIT; vu->radio = VIDEO_NO_UNIT; vu->audio = VIDEO_NO_UNIT; vu->teletext = VIDEO_NO_UNIT; return 0; } default: PDEBUG(3, "Unsupported IOCtl: 0x%X", cmd); return -ENOIOCTLCMD; /* V4L layer handles this */ } /* end switch */ return 0; } static int ovfx2_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct video_device *vdev = file->private_data; struct usb_ovfx2 *ov = vdev->priv; int rc; if (down_interruptible(&ov->lock)) return -EINTR; rc = video_usercopy(inode, file, cmd, arg, ovfx2_do_ioctl); up(&ov->lock); return rc; } static int ovfx2_read(struct file *file, char *buf, size_t cnt, loff_t *ppos) { struct video_device *vdev = file->private_data; int noblock = file->f_flags&O_NONBLOCK; unsigned long count = cnt; struct usb_ovfx2 *ov = vdev->priv; int i, rc = 0, frmx = -1; struct ovfx2_frame *frame; if (down_interruptible(&ov->lock)) return -EINTR; PDEBUG(4, "%ld bytes, noblock=%d", count, noblock); if (!vdev || !buf) { rc = -EFAULT; goto error; } if (!ov->dev) { rc = -ENODEV; goto error; } // FIXME: Only supports two frames /* See if a frame is completed, then use it. */ if (ov->frame[0].grabstate >= FRAME_DONE) /* _DONE or _ERROR */ frmx = 0; else if (ov->frame[1].grabstate >= FRAME_DONE)/* _DONE or _ERROR */ frmx = 1; /* If nonblocking we return immediately */ if (noblock && (frmx == -1)) { rc = -EAGAIN; goto error; } /* If no FRAME_DONE, look for a FRAME_GRABBING state. */ /* See if a frame is in process (grabbing), then use it. */ if (frmx == -1) { if (ov->frame[0].grabstate == FRAME_GRABBING) frmx = 0; else if (ov->frame[1].grabstate == FRAME_GRABBING) frmx = 1; } /* If no frame is active, start one. */ if (frmx == -1) { if ((rc = ovfx2_new_frame(ov, frmx = 0))) { err("read: ovfx2_new_frame error"); goto error; } } frame = &ov->frame[frmx]; restart: /* Wait while we're grabbing the image */ PDEBUG(4, "Waiting image grabbing"); rc = wait_event_interruptible(frame->wq, (frame->grabstate == FRAME_DONE) || (frame->grabstate == FRAME_ERROR) || !ov->present); if (rc) goto error; if (!ov->present) { rc = -ENODEV; goto error; } PDEBUG(4, "Got image, frame->grabstate = %d", frame->grabstate); PDEBUG(4, "bytes_recvd = %d", frame->bytes_recvd); if (frame->grabstate == FRAME_ERROR) { frame->bytes_read = 0; err("** ick! ** Errored frame %d", ov->curframe); if (ovfx2_new_frame(ov, frmx)) { err("read: ovfx2_new_frame error"); goto error; } goto restart; } /* Decompression, format conversion, etc... */ ovfx2_postprocess(ov, frame); PDEBUG(4, "frmx=%d, bytes_read=%ld, length=%ld", frmx, frame->bytes_read, get_frame_length(frame)); /* copy bytes to user space; we allow for partials reads */ // if ((count + frame->bytes_read) // > get_frame_length((struct ovfx2_frame *)frame)) // count = frame->scanlength - frame->bytes_read; /* FIXME - count hardwired to be one frame... */ count = get_frame_length(frame); PDEBUG(4, "Copy to user space: %ld bytes", count); if ((i = copy_to_user(buf, frame->data + frame->bytes_read, count))) { PDEBUG(4, "Copy failed! %d bytes not copied", i); rc = -EFAULT; goto error; } frame->bytes_read += count; PDEBUG(4, "{copy} count used=%ld, new bytes_read=%ld", count, frame->bytes_read); /* If all data have been read... */ if (frame->bytes_read >= get_frame_length(frame)) { frame->bytes_read = 0; // FIXME: Only supports two frames /* Mark it as available to be used again. */ ov->frame[frmx].grabstate = FRAME_UNUSED; if ((rc = ovfx2_new_frame(ov, !frmx))) { err("ovfx2_new_frame returned error"); goto error; } } PDEBUG(4, "read finished, returning %ld (sweet)", count); up(&ov->lock); return count; error: up(&ov->lock); return rc; } static int ovfx2_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *vdev = file->private_data; unsigned long start = vma->vm_start; unsigned long size = vma->vm_end - vma->vm_start; struct usb_ovfx2 *ov = vdev->priv; unsigned long page, pos; PDEBUG(4, "mmap: %ld (%lX) bytes", size, size); if (size > (((OVFX2_NUMFRAMES * MAX_DATA_SIZE(ov->maxwidth, ov->maxheight) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)))) return -EINVAL; if (down_interruptible(&ov->lock)) return -EINTR; if (!ov->dev) { up(&ov->lock); return -ENODEV; } pos = (unsigned long)ov->fbuf; while (size > 0) { page = kvirt_to_pa(pos); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 3) || defined(RH9_REMAP) if (remap_page_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { #else if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) { #endif up(&ov->lock); return -EAGAIN; } start += PAGE_SIZE; pos += PAGE_SIZE; if (size > PAGE_SIZE) size -= PAGE_SIZE; else size = 0; } up(&ov->lock); return 0; } static struct file_operations ovfx2_fops = { .owner = THIS_MODULE, .open = ovfx2_open, .release = ovfx2_release, .read = ovfx2_read, .mmap = ovfx2_mmap, .ioctl = ovfx2_ioctl, .llseek = no_llseek, }; static struct video_device vdev_template = { .owner = THIS_MODULE, .name = "OVFX2 USB Camera", .type = VID_TYPE_CAPTURE, .hardware = VID_HARDWARE_OV511, /* FIXME */ .fops = &ovfx2_fops, }; /**************************************************************************** * * FX2 and sensor configuration * ***************************************************************************/ /* This is called when an OV7610, OV7620, or OV76BE is detected. */ static int ov7xx0_configure(struct usb_ovfx2 *ov) { if (ov->sensor != CC_OV7620) return -ENODEV; ov->internal_client.addr = OV7xx0_SID; ov->maxwidth = 640; ov->maxheight = 480; #if 0 ov->minwidth = 64; ov->minheight = 48; #else /* Can't do less than max res yet */ ov->minwidth = ov->maxwidth; ov->minheight = ov->maxheight; #endif return camchip_init_settings(ov); } /* This initializes the FX2 and the sensor */ static int ovfx2_configure(struct usb_ovfx2 *ov) { static struct regval regvals_init[] = { /* Reset */ { 0x0f, 0x01, 0xff }, { 0x0f, 0x03, 0xff }, { 0x0f, 0x01, 0xff }, { 0x0f, 0x08, 0x08 }, /* Default camera interface settings */ { 0xee, 0x80, 0xff }, { 0x0f, 0x04, 0x04 }, { 0xe0, 0x46, 0xff }, { 0xe1, 0xf8, 0xff }, { 0xe2, 0x02, 0xff }, { 0xe3, 0x07, 0xff }, { 0xe4, 0x49, 0xff }, { 0xe5, 0xf0, 0xff }, { 0xe6, 0x0c, 0xff }, { 0xe7, 0xf4, 0xff }, { 0xe8, 0x3f, 0xff }, { 0x0f, 0x80, 0x80 }, /* Default picture settings */ { REG_SAT, 187, 0xff }, { REG_HUE, 0x00, 0xff }, { REG_CNTR, 0x80, 0xff }, { REG_BRIGHT, 0x14, 0xff }, { REG_SHARP, 88, 0xff }, { 0x00, 0x00, 0x00 }, /* end */ }; PDEBUG(4, ""); if (write_regvals(ov, regvals_init)) goto error; ovfx2_init_i2c(ov); return 0; error: err("OVFX2 Config failed"); return -EBUSY; } /**************************************************************************** * * USB routines * ***************************************************************************/ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 36) static int ovfx2_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); #else static void * ovfx2_probe(struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) { #endif struct usb_ovfx2 *ov; int i; int registered = 0; PDEBUG(1, "probing for device..."); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 20) /* Since code below may sleep, we use this as a lock */ MOD_INC_USE_COUNT; #endif if ((ov = kmalloc(sizeof(*ov), GFP_KERNEL)) == NULL) { err("couldn't kmalloc ov struct"); goto error_out; } memset(ov, 0, sizeof(*ov)); ov->dev = dev; ov->stop_during_set = !fastset; ov->sensor = CC_UNKNOWN; /* FIXME: Determine this from the VID/PID */ ov->bridge = BRG_2800; /* iBot2 */ init_waitqueue_head(&ov->wq); init_MUTEX(&ov->lock); /* to 1 == available */ init_MUTEX(&ov->buf_lock); init_MUTEX(&ov->cbuf_lock); ov->buf_state = BUF_NOT_ALLOCATED; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 20) if (usb_make_path(dev, ov->usb_path, OVFX2_USB_PATH_LEN) < 0) { err("usb_make_path error"); goto error; } #else snprintf(ov->usb_path, OVFX2_USB_PATH_LEN, "dev:%d/bus:%d", dev->bus->busnum, dev->devnum); #endif /* Allocate control transfer buffer. */ /* Must be kmalloc()'ed, for DMA compatibility */ ov->cbuf = kmalloc(OVFX2_CBUF_SIZE, GFP_KERNEL); if (!ov->cbuf) goto error; if (ovfx2_configure(ov) < 0) goto error; for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].framenum = i; init_waitqueue_head(&ov->frame[i].wq); } for (i = 0; i < OVFX2_NUMSBUF; i++) { ov->sbuf[i].ov = ov; spin_lock_init(&ov->sbuf[i].lock); ov->sbuf[i].n = i; } #ifdef OVFX2_DEBUG if (dump_bridge) ovfx2_dump_regs(ov); #endif memcpy(&ov->vdev, &vdev_template, sizeof(vdev_template)); ov->vdev.priv = ov; for (i = 0; i < OVFX2_MAX_UNIT_VIDEO; i++) { /* Minor 0 cannot be specified; assume user wants autodetect */ if (unit_video[i] == 0) break; if (video_register_device(&ov->vdev, VFL_TYPE_GRABBER, unit_video[i]) >= 0) { registered = 1; break; } } /* Use the next available one */ if (!registered && video_register_device(&ov->vdev, VFL_TYPE_GRABBER, -1) < 0) { err("video_register_device failed"); goto error; } info("%s at %s registered to minor %d (%s speed)", (char *) id->driver_info, ov->usb_path, ov->vdev.minor, (dev->speed == USB_SPEED_HIGH) ? "high" : "full"); create_proc_ovfx2_cam(ov); ov->present = 1; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 20) MOD_DEC_USE_COUNT; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 36) dev_set_drvdata(&intf->dev, ov); return 0; #else return ov; #endif error: if (ov->cbuf) { down(&ov->cbuf_lock); kfree(ov->cbuf); ov->cbuf = NULL; up(&ov->cbuf_lock); } if (ov) { kfree(ov); ov = NULL; } error_out: #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 20) MOD_DEC_USE_COUNT; #endif err("Camera initialization failed"); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 36) return -EIO; #else return NULL; #endif } static void #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 36) ovfx2_disconnect(struct usb_interface *intf) { struct usb_ovfx2 *ov = dev_get_drvdata(&intf->dev); #else ovfx2_disconnect(struct usb_device *dev, void *ptr) { struct usb_ovfx2 *ov = (struct usb_ovfx2 *) ptr; #endif int n; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 20) MOD_INC_USE_COUNT; #endif PDEBUG(3, ""); ov->present = 0; /* Ensure that no synchronous control requests * are active before disconnect() returns */ down(&ov->cbuf_lock); kfree(ov->cbuf); ov->cbuf = NULL; up(&ov->cbuf_lock); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 36) dev_set_drvdata(&intf->dev, NULL); #endif if (!ov) return; video_unregister_device(&ov->vdev); if (ov->user) PDEBUG(3, "Device open...deferring video_unregister_device"); for (n = 0; n < OVFX2_NUMFRAMES; n++) ov->frame[n].grabstate = FRAME_ERROR; ov->curframe = -1; /* This will cause the process to request another frame */ for (n = 0; n < OVFX2_NUMFRAMES; n++) wake_up_interruptible(&ov->frame[n].wq); wake_up_interruptible(&ov->wq); /* Can't take lock earlier since it would deadlock * read() or ioctl() if open and sleeping */ down(&ov->lock); ov->streaming = 0; ovfx2_unlink_bulk(ov); destroy_proc_ovfx2_cam(ov); i2c_del_adapter(&ov->i2c_adap); ov->dev = NULL; up(&ov->lock); down(&ov_free_lock); if (ov && !ov->user) { kfree(ov); ov = NULL; } up(&ov_free_lock); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 20) MOD_DEC_USE_COUNT; #endif PDEBUG(3, "Disconnect complete"); } static struct usb_driver ovfx2_driver = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 20) .owner = THIS_MODULE, #endif .name = "ovfx2", .id_table = device_table, .probe = ovfx2_probe, .disconnect = ovfx2_disconnect }; /**************************************************************************** * * Module routines * ***************************************************************************/ static int __init usb_ovfx2_init(void) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) EXPORT_NO_SYMBOLS; #endif init_MUTEX(&ov_free_lock); proc_ovfx2_create(); if (usb_register(&ovfx2_driver) < 0) return -1; info(DRIVER_VERSION " : " DRIVER_DESC); return 0; } static void __exit usb_ovfx2_exit(void) { usb_deregister(&ovfx2_driver); info("driver deregistered"); proc_ovfx2_destroy(); } module_init(usb_ovfx2_init); module_exit(usb_ovfx2_exit);