/* -*- linux-c -*- * vicam.c - ViCAM/3Com HomeConnect USB Camera Driver * * Copyright (c) 2002 John Tyner * * This driver is for the 3Com HomeConnect USB Camera and * as far as I know, the Vista Imaging ViCAM Camera. The * code is based on the driver created by the team at * homeconnectusb.sourceforge.net. They deserve the credit * for the decoding algorithm and for figuring out what values * and messages need to be sent to the camera and what data * is actually coming back. * * This file is woefully underdocumented and doesn't print * any information. I apologize for that, but the driver is * working (for me), and doesn't need them. *duck* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vicam.h" int reverse_rgb = 0; int shutter_speed = 30; static int vicam_send_control_msg( struct vicam_v4l_data *vicam_v4l_priv, u8 request, u16 value, u16 size ) { struct usb_device *usb_dev = vicam_v4l_priv->usb_dev; int ret; /* for some reason we can't pass data directly * to usb_control_msg. therefore, we assume that * the caller has already placed their data into * the cntrl_buf. */ ret = usb_control_msg( usb_dev, usb_sndctrlpipe( usb_dev, 0 ), request, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, 0, vicam_v4l_priv->cntrl_buf, size, HZ ); return min( ret, 0 ); } static void vicam_urb_complete( struct urb *vicam_v4l_urb ) { struct vicam_v4l_data *vicam_v4l_priv; vicam_v4l_priv = vicam_v4l_urb->context; if ( !vicam_v4l_urb->status ) { set_bit( vicam_v4l_priv->current_frame, &vicam_v4l_priv->decode_frames ); tasklet_schedule( &vicam_v4l_priv->decode ); } up( &vicam_v4l_priv->busy_mutex ); } static int vicam_capture_image( struct vicam_v4l_data *vicam_v4l_priv, unsigned long frame_num ) { u8 * const request = vicam_v4l_priv->cntrl_buf; int err; if ( vicam_v4l_priv->speed < 2 || vicam_v4l_priv->speed > 60 ) { return -EINVAL; } memset( request, 0, 16 ); request[0] = vicam_v4l_priv->gain; if ( vicam_v4l_priv->width <= 256 ) { request[1] |= ( vicam_v4l_priv->width > 128 ) ? 0x01 : 0x03; } if ( vicam_v4l_priv->height <= 180 ) { request[1] |= ( vicam_v4l_priv->height > 121 ) ? 0x20 : 0x30; } request[2] = 0x90; request[3] = 0x07; *( ( u16 * )( &request[6] ) ) = cpu_to_le16( 15600 / vicam_v4l_priv->speed - 1 ); err = down_interruptible( &vicam_v4l_priv->busy_mutex ); if ( err ) { return err; } vicam_v4l_priv->current_frame = frame_num; err = vicam_send_control_msg( vicam_v4l_priv, VICAM_REQ_CAPTURE, 0x80, 16 ); if ( err ) { goto done; } usb_fill_bulk_urb( vicam_v4l_priv->urb, vicam_v4l_priv->usb_dev, usb_rcvbulkpipe( vicam_v4l_priv->usb_dev, vicam_v4l_priv->bulk_endpoint ), vicam_v4l_priv->input_buf[frame_num], vicam_v4l_priv->width * vicam_v4l_priv->height + VICAM_HEADER_SIZE + VICAM_FOOTER_SIZE, vicam_urb_complete, vicam_v4l_priv ); err = usb_submit_urb( vicam_v4l_priv->urb ); done: if ( err ) { up( &vicam_v4l_priv->busy_mutex ); } return err; } static void vicam_decode_frame( struct vicam_v4l_data *vicam_v4l_priv, unsigned long frame_num ) { const u32 width = vicam_v4l_priv->width, height = vicam_v4l_priv->height; u8 *ibuf, *obuf; int y; ibuf = &vicam_v4l_priv->input_buf[frame_num][VICAM_HEADER_SIZE]; obuf = vicam_v4l_priv->frame_buf + ( VICAM_FRAME_SIZE * frame_num ); for ( y = 0; y < height; y++, ibuf += width ) { int dy = width; int x_out; if ( unlikely( y >= height - 1 ) ) { dy = -dy; } for ( x_out = 0; x_out < 320; x_out++ ) { int dx, cx, cy, luma, x, i; int rgbc[3]; u8 sp[4]; x = ( x_out * width ) / 320; dx = 1; if ( unlikely( x >= width - 1 ) ) { dx = -dx; } i = ( y & 1 ) | ( ( x & 1 ) << 1 ); sp[i ^ 0] = ibuf[x]; sp[i ^ 1] = ibuf[x + dy]; sp[i ^ 2] = ibuf[x + dx]; sp[i ^ 3] = ibuf[x + dy + dx]; cx = sp[3] - sp[1]; cy = sp[2] - sp[0]; rgbc[0] = cy + ( cx / 2 ); cy /= -2; cx *= 13; cx /= 15; rgbc[1] = cy + cx; rgbc[2] = cy - cx; luma = ( int )( ibuf[x] + ibuf[x + dx] ) >> 1; for ( i = 0; i < 3; i++, obuf++ ) { int j = i; if ( reverse_rgb ) { j = ( 2 - i ); } *obuf = clamp( luma + rgbc[j], 0, 255 ); } } } up( &vicam_v4l_priv->decode_mutex[frame_num] ); } static void vicam_bh_handler( unsigned long _vicam_v4l_priv ) { struct vicam_v4l_data *vicam_v4l_priv = ( struct vicam_v4l_data * )( _vicam_v4l_priv ); int i; do { i = ffs( vicam_v4l_priv->decode_frames ); vicam_decode_frame( vicam_v4l_priv, i - 1 ); clear_bit( i - 1, &vicam_v4l_priv->decode_frames ); } while ( vicam_v4l_priv->decode_frames ); } static int vicam_v4l_open( struct video_device *dev, int mode ) { struct vicam_v4l_data *vicam_v4l_priv = dev->priv; int err; err = down_interruptible( &vicam_v4l_priv->dev_mutex ); if ( err ) { return err; } err = vicam_send_control_msg( vicam_v4l_priv, VICAM_REQ_POWER, 1, 0 ); vicam_send_control_msg( vicam_v4l_priv, VICAM_REQ_LED, 3, 0 ); if ( err ) { up( &vicam_v4l_priv->dev_mutex ); } return err; } static void vicam_v4l_close( struct video_device *dev ) { struct vicam_v4l_data *vicam_v4l_priv = dev->priv; vicam_send_control_msg( vicam_v4l_priv, VICAM_REQ_POWER, 0, 0 ); up( &vicam_v4l_priv->dev_mutex ); } static long vicam_v4l_write( struct video_device *dev, const char *buf, unsigned long count, int noblock ) { return -EPERM; } static int vicam_v4l_ioctl( struct video_device *dev, unsigned int cmd, void *arg ) { struct vicam_v4l_data *vicam_v4l_priv; struct video_capability *vcap; struct video_channel *vchan; struct video_mbuf *vmbuf; struct video_picture *vpic; struct video_mmap *vmmap; struct video_window *vwin; void *buf; int buf_size, err, i; vicam_v4l_priv = dev->priv; err = 0; buf_size = max_t( int, sizeof( *vcap ), max_t( int, sizeof( *vchan ), max_t( int, sizeof( *vmbuf ), max_t( int, sizeof( *vpic ), max_t( int, sizeof( *vmmap ), sizeof( *vwin ) ) ) ) ) ); if ( unlikely( buf_size > VICAM_CNTRL_BUF_SIZE ) ) { return -ENOMEM; } buf = vicam_v4l_priv->cntrl_buf; buf_size = 0; vcap = buf; vchan = buf; vmbuf = buf; vpic = buf; vmmap = buf; vwin = buf; switch ( cmd ) { case VIDIOCGCAP: buf_size = sizeof( *vcap ); memset( buf, 0, buf_size ); strcpy( vcap->name, dev->name ); vcap->type = dev->type; vcap->channels = 1; vcap->audios = 0; vcap->maxwidth = 320; vcap->maxheight = 242; vcap->minwidth = 320; vcap->minheight = 242; break; case VIDIOCGCHAN: if ( copy_from_user( vchan, arg, sizeof( *vchan ) ) ) { err = -EFAULT; break; } if ( vchan->channel ) { err = -EINVAL; break; } strcpy( vchan->name, dev->name ); vchan->flags = 0; vchan->tuners = 0; vchan->type = VIDEO_TYPE_CAMERA; vchan->norm = 0; buf_size = sizeof( *vchan ); break; case VIDIOCSCHAN: if ( copy_from_user( vchan, arg, sizeof( *vchan ) ) ) { err = -EFAULT; break; } if ( vchan->channel ) { err = -EINVAL; break; } break; case VIDIOCGPICT: buf_size = sizeof( *vpic ); memset( vpic, 0, buf_size ); vpic->brightness = vicam_v4l_priv->gain << 8; vpic->depth = 24; vpic->palette = VIDEO_PALETTE_RGB24; break; case VIDIOCSPICT: if ( copy_from_user( vpic, arg, sizeof( *vpic ) ) ) { err = -EFAULT; break; } if ( vpic->depth != 24 || vpic->palette != VIDEO_PALETTE_RGB24 ) { err = -EINVAL; } vicam_v4l_priv->gain = vpic->brightness >> 8; break; case VIDIOCGWIN: buf_size = sizeof( *vwin ); memset( vwin, 0, buf_size ); vwin->x = 0; vwin->y = 0; vwin->width = 320; vwin->height = vicam_v4l_priv->height; vwin->chromakey = 0; vwin->flags = 0; vwin->clips = NULL; vwin->clipcount = 0; break; case VIDIOCSWIN: if ( vwin->x != 0 || vwin->y != 0 ) { err = -EINVAL; } if ( vwin->width != 320 || vwin->height != vicam_v4l_priv->height ) { err = -EINVAL; } break; case VIDIOCGMBUF: buf_size = sizeof( *vmbuf ); memset( vmbuf, 0, buf_size ); vmbuf->frames = min( VICAM_NUM_FRAMES, VIDEO_MAX_FRAME ); vmbuf->size = VICAM_FRAME_SIZE * vmbuf->frames; for ( i = 0; i < vmbuf->frames; i++ ) { vmbuf->offsets[i] = VICAM_FRAME_SIZE * i; } break; case VIDIOCMCAPTURE: if ( copy_from_user( vmmap, arg, sizeof( *vmmap ) ) ) { err = -EFAULT; break; } if ( vmmap->frame > VICAM_NUM_FRAMES || vmmap->format != VIDEO_PALETTE_RGB24 ) { err = -EINVAL; break; } err = vicam_capture_image( vicam_v4l_priv, vmmap->frame ); break; case VIDIOCSYNC: if ( copy_from_user( &i, arg, sizeof( i ) ) ) { err = -EFAULT; break; } if ( i > VICAM_NUM_FRAMES ) { err = -EINVAL; break; } err = down_interruptible( &vicam_v4l_priv->decode_mutex[i] ); break; default: err = -ENOIOCTLCMD; break; } if ( !err && buf_size ) { err = copy_to_user( arg, buf, buf_size ) ? -EFAULT : 0; } return err; } static int vicam_v4l_mmap( struct video_device *dev, const char *addr, unsigned long size ) { struct vicam_v4l_data *vicam_v4l_priv = dev->priv; unsigned long start = ( unsigned long )( addr ); unsigned long pos; if ( size > VICAM_FRAME_BUF_SIZE ) { return -EINVAL; } pos = ( unsigned long )( vicam_v4l_priv->frame_buf ); while ( size > 0 ) { unsigned long page = kvirt_to_pa( pos ); if ( remap_page_range( start, page, PAGE_SIZE, PAGE_SHARED ) ){ return -EAGAIN; } start += PAGE_SIZE; pos += PAGE_SIZE; /* in case size isn't a multiple of PAGE_SIZE */ size -= min( size, PAGE_SIZE ); } return 0; } static int __devinit vicam_v4l_initialize( struct video_device *dev ) { struct vicam_v4l_data *vicam_v4l_priv; int err; int i; vicam_v4l_priv = kmalloc( sizeof( *vicam_v4l_priv ), GFP_KERNEL ); if ( !vicam_v4l_priv ) { return -ENOMEM; } memset( vicam_v4l_priv, 0, sizeof( *vicam_v4l_priv ) ); err = -ENOMEM; vicam_v4l_priv->urb = usb_alloc_urb( 0 ); if ( !vicam_v4l_priv->urb ) { goto done; } vicam_v4l_priv->frame_buf = rvmalloc( VICAM_FRAME_BUF_SIZE ); if ( !vicam_v4l_priv->frame_buf ) { goto done; } vicam_v4l_priv->cntrl_buf = kmalloc( VICAM_CNTRL_BUF_SIZE, GFP_KERNEL ); if ( !vicam_v4l_priv->cntrl_buf ) { goto done; } for ( i = 0; i < VICAM_NUM_FRAMES; i++ ) { vicam_v4l_priv->input_buf[i] = kmalloc( VICAM_INPUT_SIZE, GFP_KERNEL ); if ( !vicam_v4l_priv->input_buf[i] ) { goto done; } } /* the usb_device was passed to us from the vicam_usb_probe * function through our dev->priv pointer. */ vicam_v4l_priv->usb_dev = dev->priv; dev->priv = vicam_v4l_priv; tasklet_init( &vicam_v4l_priv->decode, vicam_bh_handler, ( unsigned long )( vicam_v4l_priv ) ); init_MUTEX( &vicam_v4l_priv->dev_mutex ); init_MUTEX( &vicam_v4l_priv->busy_mutex ); for ( i = 0; i < VICAM_NUM_FRAMES; i++ ) { init_MUTEX_LOCKED( &vicam_v4l_priv->decode_mutex[i] ); } vicam_v4l_priv->width = 512; vicam_v4l_priv->height = 242; vicam_v4l_priv->speed = shutter_speed; for ( i = 0, err = 0; setup[i].firmware && !err; i++ ) { memcpy( vicam_v4l_priv->cntrl_buf, setup[i].firmware, setup[i].size ); err = vicam_send_control_msg( vicam_v4l_priv, VICAM_REQ_VENDOR, 0, setup[i].size ); } done: if ( err ) { for ( i = 0; i < VICAM_NUM_FRAMES; i++ ) { if ( vicam_v4l_priv->input_buf[i] ) { kfree( vicam_v4l_priv->input_buf[i] ); } } if ( vicam_v4l_priv->cntrl_buf ) { kfree( vicam_v4l_priv->cntrl_buf ); } if ( vicam_v4l_priv->frame_buf ) { rvfree( vicam_v4l_priv->frame_buf, VICAM_FRAME_BUF_SIZE ); } if ( vicam_v4l_priv->urb ) { usb_free_urb( vicam_v4l_priv->urb ); } kfree( vicam_v4l_priv ); } return err; } static void * __devinit vicam_usb_probe( struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *devid ) { struct vicam_usb_data *vicam_usb_priv; const struct usb_interface *usb_iface; int err; u8 endpt_addr, endpt_attr; vicam_usb_priv = NULL; usb_iface = usb_ifnum_to_if( dev, ifnum ); if ( usb_iface->num_altsetting != 1 ) { return NULL; } endpt_addr = usb_iface->altsetting[0].endpoint[0].bEndpointAddress; endpt_attr = usb_iface->altsetting[0].endpoint[0].bmAttributes; if ( !( endpt_addr & USB_ENDPOINT_DIR_MASK ) || ( endpt_attr & USB_ENDPOINT_XFERTYPE_MASK ) != USB_ENDPOINT_XFER_BULK ) { return NULL; } vicam_usb_priv = kmalloc( sizeof( *vicam_usb_priv ), GFP_KERNEL ); if ( !vicam_usb_priv ) { return NULL; } memset( vicam_usb_priv, 0, sizeof( *vicam_usb_priv ) ); usb_string( dev, dev->descriptor.iProduct, vicam_usb_priv->vicam_v4l_dev.name, 32 ); vicam_usb_priv->vicam_v4l_dev.type = VID_TYPE_CAPTURE; vicam_usb_priv->vicam_v4l_dev.hardware = 0; /* need own id */ vicam_usb_priv->vicam_v4l_dev.initialize = vicam_v4l_initialize; vicam_usb_priv->vicam_v4l_dev.open = vicam_v4l_open; vicam_usb_priv->vicam_v4l_dev.close = vicam_v4l_close; vicam_usb_priv->vicam_v4l_dev.write = vicam_v4l_write; vicam_usb_priv->vicam_v4l_dev.ioctl = vicam_v4l_ioctl; vicam_usb_priv->vicam_v4l_dev.mmap = vicam_v4l_mmap; vicam_usb_priv->vicam_v4l_dev.owner = THIS_MODULE; /* we need to get the usb_device struct to the * vicam_v4l_initialize function [preferably] * without using globals. */ vicam_usb_priv->vicam_v4l_dev.priv = dev; err = video_register_device ( &vicam_usb_priv->vicam_v4l_dev, VFL_TYPE_GRABBER, -1 ); if ( err ) { kfree( vicam_usb_priv ); vicam_usb_priv = NULL; } else { struct vicam_v4l_data *vicam_v4l_priv = vicam_usb_priv->vicam_v4l_dev.priv; vicam_v4l_priv->bulk_endpoint = endpt_addr; } return vicam_usb_priv; } static void __devexit vicam_usb_disconnect( struct usb_device *dev, void *data ) { struct vicam_usb_data *vicam_usb_priv; struct vicam_v4l_data *vicam_v4l_priv; int i; vicam_usb_priv = data; vicam_v4l_priv = vicam_usb_priv->vicam_v4l_dev.priv; /* what happens if a disconnect occurs while the device is open? */ video_unregister_device( &vicam_usb_priv->vicam_v4l_dev ); tasklet_kill( &vicam_v4l_priv->decode ); usb_free_urb( vicam_v4l_priv->urb ); rvfree( vicam_v4l_priv->frame_buf, VICAM_FRAME_BUF_SIZE ); kfree( vicam_v4l_priv->cntrl_buf ); for ( i = 0; i < VICAM_NUM_FRAMES; i++ ) { if ( vicam_v4l_priv->input_buf[i] ) { kfree( vicam_v4l_priv->input_buf[i] ); } } kfree( vicam_v4l_priv ); kfree( vicam_usb_priv ); } static struct usb_device_id __devinitdata vicam_usb_table[] = { { USB_DEVICE( USB_3COMHC_VENDOR_ID, USB_3COMHC_PRODUCT_ID ) }, /* That's all folks */ { .match_flags = 0 } }; static struct usb_driver vicam_usb_drv = { .name = "vicam_usb", .probe = vicam_usb_probe, .disconnect = __devexit_p( vicam_usb_disconnect ), .id_table = vicam_usb_table }; static int __init vicam_usb_init( void ) { reverse_rgb = clamp( reverse_rgb, 0, 1 ); shutter_speed = clamp( shutter_speed, 2, 60 ); return usb_register( &vicam_usb_drv ); } static void __exit vicam_usb_exit( void ) { usb_deregister( &vicam_usb_drv ); } module_init( vicam_usb_init ); module_exit( vicam_usb_exit ); MODULE_DEVICE_TABLE( usb, vicam_usb_table ); MODULE_PARM( reverse_rgb, "i" ); MODULE_PARM_DESC( reverse_rgb, "Perform RGB to BGR conversion" ); MODULE_PARM( shutter_speed, "i" ); MODULE_LICENSE( "GPL" ); MODULE_AUTHOR( "John Tyner " ); MODULE_DESCRIPTION( "ViCAM USB/V4L Driver" );