Logo Search packages:      
Sourcecode: nw802 version File versions

nw802.c

/*
 * 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.
 */

//
// Kernel module for NW80x based webcams
//
//  - Website : nw802.sourceforge.net
//  - Contact : Munaut Sylvain <tnt@246tNt.com>
//  - Mailing list : nw802-main@lists.sourceforge.net
//
//  [ sources bestview with tabstop=4 ]
//
 
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>

#include "usbvideo.h"
#include "nw8xx_jpgl.h"

// ============================================================================
// Module Description
// ============================================================================

MODULE_AUTHOR("Munaut Sylvain <tnt@246tNt.com>");
MODULE_DESCRIPTION("Driver for the NW80x webcam chip");
MODULE_LICENSE("GPL");


// ============================================================================
// Typedefs, struct, defines, ...
// ============================================================================

// Constants

#define ENABLE_DEBUG

#define     MAX_NW802CAMS     1
#define MAX_VEIO_LEN    64


// Macros

#define INFO(fmt,args...)     printk( KERN_INFO "nw802.c: " fmt "\n", ## args )
#define ERR(fmt,args...)      printk( KERN_ERR "nw802.c: " fmt "\n" , ## args )
#ifdef ENABLE_DEBUG
#define DEBUG(level, fmt, args...)                                \
      do {                                                  \
            if ( debug >= level )                                 \
                  printk( KERN_DEBUG "nw802.c: " fmt "\n", ## args );   \
      } while(0)
#else
#define DEBUG(level, fmt, args...)
#endif

#define LIMIT_0_255(x)  (((x) < 0) ? 0 : (((x) > 255) ? 255 : (x)))

#define NW802_T(uvd) ((nw802_t *)((uvd)->user_data))


// Structs

typedef struct    // This structure lives in struct uvd->user field.
{
      unsigned char veio_buf[MAX_VEIO_LEN];     // Buffer for vendor usb I/O
      int type;                                             // Type of the cam
} nw802_t;

typedef struct    // Represent a supported device
{
      unsigned short int idVendor;
      unsigned short int idProduct;
      enum
      {
            NW800 = 0,
            NW801 = 1,
            NW802 = 2
      } model;
      char *name;
} supportedDevice_t;

typedef struct
{
      unsigned short index;
      unsigned short value;
      unsigned short len;
      unsigned char  data[MAX_VEIO_LEN];
} initURB_t;

// Enums
enum
{
      SIZE_160x120 = 0,
      SIZE_176x144,
      SIZE_320x240,
      SIZE_352x288,
      SIZE_640x480,
      SIZE_END
};

// ============================================================================
// Supported camera lists
// ============================================================================

// TODO Data must be repeated twice ... Once in one of our structure, the other
// in a kernel standard structure ... I should find a way to 'unify' this !

static __devinitdata struct usb_device_id nw802_usb_ids[] =
      {
            { USB_DEVICE( 0x046d, 0xd001 ) },   // Logitech Quickam Pro USB
                                                                  //  (dark focus ring)

            { USB_DEVICE( 0x052b, 0xd001 ) },   // Ezonics EZCam Pro USB

            { USB_DEVICE( 0x055f, 0xd001 ) },   // PCLine PCL-W300
                                                                  // Mustek WCam 300

            { USB_DEVICE( 0x06a5, 0xd001 ) },   // Generic NW802
 
            { USB_DEVICE( 0x06a5, 0x0000 ) },   // Generic NW800

            {}    // End entry
      };

MODULE_DEVICE_TABLE( usb, nw802_usb_ids );

static supportedDevice_t nw802_supported_devs [] =
      {
            { 0x046d, 0xd001, NW801, "Logitech Quickam Pro USB (dark focus ring)" },
            { 0x052b, 0xd001, NW802, "Ezonics EZCam Pro USB" },
            { 0x055f, 0xd001, NW802, "Mustek WCam 300 / PCLine PCL-W300" },
            { 0x06a5, 0xd001, NW802, "Generic DivIO NW802" },
            { 0x06a5, 0x0000, NW800, "Generic DivIO NW800" },
            {} // End entry ( a null name indicate the end )
      };


// ============================================================================
// Global vars
// ============================================================================

// Module parameters
#ifdef ENABLE_DEBUG
static int debug = 0;         // The debugging level of our module
static int debug_uv = 0;      // The debugging level of USB video
#endif

static int size = SIZE_END;


// Internal vars
static struct usbvideo *nw802_cams = NULL;

static int canvasX = 0;
static int canvasY = 0;


// ============================================================================
// Module parameters, options
// ============================================================================

#ifdef ENABLE_DEBUG
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug level: 0-5 (default=0)");
MODULE_PARM(debug_uv, "i");
MODULE_PARM_DESC(debug_uv, "Debug level of USB Video: 0-2 (default=0)");
#endif

MODULE_PARM(size, "i");
MODULE_PARM_DESC(size, "Video size: 0=160x120 1=176x144 2=320x240 3=352x288 4=640x480 (default=2)" );


// ============================================================================
// Module options related funcs
// ============================================================================

static void nw802_validate_params( int cam_type )
{
      #ifdef ENABLE_DEBUG
      RESTRICT_TO_RANGE( debug, 0, 5 );
      RESTRICT_TO_RANGE( debug_uv, 0, 2 );
      #endif


      if ( size == SIZE_END )
            size = nw802_supported_devs[cam_type].model == NW800 ? 
                  SIZE_352x288 : SIZE_320x240;
      else
            RESTRICT_TO_RANGE( size, 0, SIZE_END - 1 );
}

static int nw802_size_to_videosize( int size )
{
      switch (size)
      {
            case SIZE_160x120:
                  return VIDEOSIZE( 160, 120 );
            case SIZE_176x144:
                  return VIDEOSIZE( 176, 144 );
            case SIZE_320x240:
                  return VIDEOSIZE( 320, 240 );
            case SIZE_352x288:
                  return VIDEOSIZE( 352, 288 );
            case SIZE_640x480:
                  return VIDEOSIZE( 640, 480 );
            default:
                  ERR( "Invalid video size ! Taking cam default" );
                  return VIDEOSIZE( 320, 240 );
      }
}


// ============================================================================
// USB Video driver stuff
// ============================================================================

// Other functions

static int nw802_vendor_send( struct uvd *uvd, const initURB_t *vu )
{
      int rv, len;
      
      len = vu->len;
      RESTRICT_TO_RANGE( len, 0, MAX_VEIO_LEN );
      memcpy( (void*)NW802_T(uvd)->veio_buf, vu->data, len ); // Data must be in kernel space ...
      
      rv = usb_control_msg( uvd->dev,
                        usb_sndctrlpipe( uvd->dev, 0 ),
                        0,
                        USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                        vu->value,
                        vu->index,
                        (void*)NW802_T(uvd)->veio_buf,
                        len,
                        0.1 * HZ );

      DEBUG( 2, "Vendor send report : index=%i value=%i rv=%i", vu->index, vu->value, rv );
      
      return rv;
}

static int nw802_vendor_read( struct uvd *uvd, int idx, void *buf, int len )
{
      int rv;

      RESTRICT_TO_RANGE( len, 0, MAX_VEIO_LEN );

      rv = usb_control_msg( uvd->dev,
                  usb_rcvctrlpipe( uvd->dev, 0 ),
                  0,
                  USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                  0,
                  idx,
                  (void*)NW802_T(uvd)->veio_buf,
                  len,
                  HZ );

      DEBUG( 2, "Vendor read report : index=%i rv=%i", idx, rv );

      memcpy( buf, (void*)NW802_T(uvd)->veio_buf, len );

      return rv;
}


static int nw802_init_camera( struct uvd *uvd )
{
      // TODO Need to rewrite this & understand what it means ...
      int i;

      // The init sequences of the two camera models
      #define NW802_INIT_LEN 31
      static const
      initURB_t nw802_init[NW802_INIT_LEN] = {

            #include "nw802.init"   // Too big, in a separate file
            
      };

      #define NW801_INIT_LEN 38
      static const
      initURB_t nw801_init[NW801_INIT_LEN] = {
            
            #include "nw801.init"   // Too big, in a separate file
            
      };
      
      #define NW800_INIT_LEN 65
      static const
      initURB_t nw800_init[NW800_INIT_LEN] = {
      
            #include "nw800.init"   // Too big, in a separate file
            
      };
      
      // Select alternate setting to the active one
      usb_set_interface( uvd->dev, 0x00, uvd->ifaceAltActive );

      // Send all the packets 
      switch ( nw802_supported_devs[NW802_T(uvd)->type].model ) {

            case NW800:
                  for ( i=0 ; i < NW800_INIT_LEN ; i++ )
                        if ( nw802_vendor_send( uvd, &nw800_init[i] ) < 0 )
                              return -1;
                  break;

            case NW801:
                  for ( i=0 ; i < NW801_INIT_LEN ; i++ )
                        if ( nw802_vendor_send( uvd, &nw801_init[i] ) < 0 )
                              return -1;
                  break;
                  
            case NW802:
                  for ( i=0 ; i < NW802_INIT_LEN ; i++ )
                        if ( nw802_vendor_send( uvd, &nw802_init[i] ) < 0 )
                        return -1;
                  break;

      }

      return 0;
}

static void nw802_configure_video( struct uvd *uvd )
{
      if ( !uvd )
            return;

      // Picture settings ( FIXME: Take them as args ??? )
      memset( &uvd->vpic, 0x00, sizeof(uvd->vpic) );
      memset( &uvd->vpic_old, 0x00, sizeof(uvd->vpic_old) );
      
      uvd->vpic.colour = 128 << 8;
      uvd->vpic.hue = 128 << 8;
      uvd->vpic.brightness = 128 << 8;
      uvd->vpic.contrast = 128 << 8;
      uvd->vpic.whiteness = 128 << 8;
      uvd->vpic.depth = 24;
      uvd->vpic.palette = VIDEO_PALETTE_RGB24;

      // Video capabilities & channel setting
      memset( &uvd->vcap, 0, sizeof(uvd->vcap) );
      strcpy( uvd->vcap.name, nw802_supported_devs[NW802_T(uvd)->type].name );
              
      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 = 4;       // FIXME Don't really know
      uvd->vcap.minheight = 4;      // what these values means ...
                                                

      memset( &uvd->vchan, 0, sizeof(uvd->vchan) );
      strcpy( uvd->vchan.name, "Camera view" );

      uvd->vchan.flags = 0;
      uvd->vchan.tuners = 0;
      uvd->vchan.channel = 0;
      uvd->vchan.type = VIDEO_TYPE_CAMERA;
}


// Call backs

static void nw802_processIsocData(struct uvd *uvd, struct usbvideo_frame *frame)
{
      int rv;
      
      DEBUG( 5, "nw802_processIsocData" );


      // Try to find first header
      rv = jpgl_findHeader( &uvd->dp, canvasX, canvasY, 0 );
            
      // Remove junk data preceding the header, if any
      if ( rv < 0 )
            return;     // None found
      else if ( rv > 0 )
            RING_QUEUE_DEQUEUE_BYTES(&uvd->dp, rv);
            
      // Try to find another header
      rv = jpgl_findHeader( &uvd->dp, canvasX, canvasY, 8 );
      if ( rv > 0 )
      {
            // Ok, we found two headers, so between them, there is
            // a frame waiting for decoding
            DEBUG( 4, "Frame ready for decoding" );
                  
            if ( !jpgl_processFrame(&uvd->dp, frame->data) )
            {
                  // Frame processing was sucessful
                  frame->frameState = FrameState_Done;
                  uvd->curframe = -1;
                  uvd->stats.frame_num++;
                  
                  // Overlay stats
                  #ifdef ENABLE_DEBUG
                  if ( debug_uv > 0 )
                        usbvideo_OverlayStats(uvd, frame);
                  #endif
            }
            else
                  DEBUG(3, "Invalid frame detected !");
      }
}

static int nw802_setupOnOpen(struct uvd *uvd)
{
      DEBUG( 1, "nw802_setupOnOpen(...)" );
      
      // TODO : Nothing more todo here ???
      // nw802_init_camera( uvd );
      
      return 0;
}

static void nw802_videoStart(struct uvd *uvd)
{
      DEBUG( 1, "nw802_videoStart(...)" );
      
      // TODO : Nothing more todo here ???
      nw802_init_camera( uvd );
}

static void nw802_videoStop(struct uvd *uvd)
{
      DEBUG( 1, "nw802_videoStop(...)" );

      // I don't know how to stop it ...
}

static void *nw802_probe( struct usb_device *dev,
                    unsigned int ifnum,
                    const struct usb_device_id *devid )
{
      struct uvd *uvd = NULL;
      int nas, i, type;
      int actSetting = -1;
      int inactSetting = -1;
      int maxPS = 0;
      unsigned char video_ep = 0;
      
      DEBUG( 1, "nw802_probe(...)" );

      // We don't want multiple configuration camera
      if ( dev->descriptor.bNumConfigurations != 1 )
            return NULL;
      
      // Check Vendor & Product ID
      for ( i=0 ; nw802_supported_devs[i].name ; i++ )
            if ( ( dev->descriptor.idVendor == nw802_supported_devs[i].idVendor ) &&
                 ( dev->descriptor.idProduct == nw802_supported_devs[i].idProduct ) )
             break;
            
      if ( ! nw802_supported_devs[i].name )
            return NULL;
      
      // Ok it's a supported cam ( at least seems to )
      type = i;
      INFO( "Compatible DivIO NW80x based webcam found ! [%s]", nw802_supported_devs[type].name );
      
      // Let's find endpoint, altsettings, ... and validate all this !
      nas = dev->actconfig->interface[ifnum].num_altsetting;
      DEBUG( 2, "Number of alternate settings : %d", nas );
      
      for ( i=0 ; i<nas ; i++ )
      {
            const struct usb_interface_descriptor *interface;
            const struct usb_endpoint_descriptor *endpoint;
            
            interface = &dev->actconfig->interface[ifnum].altsetting[i];
            if ( interface->bNumEndpoints != 3 )
            {
                  ERR( "Interface %u Alt %i has %i endpoints!", 
                       ifnum, i, interface->bNumEndpoints );
                  return NULL;
            }
            
            endpoint = &interface->endpoint[1];
            
            if ( video_ep == 0 )
                  video_ep = endpoint->bEndpointAddress;
            else if ( video_ep != endpoint->bEndpointAddress )
            {
                  ERR( "Alternate settings have different endpoint addresses!");
                  return NULL;      
            }           
            
            if ( ( endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK ) !=
                 USB_ENDPOINT_XFER_ISOC )
            {
                  ERR( "Interface %u Alt %i has non-ISO endpoint 0!", ifnum, i );
                  return NULL;      
            }
            
            if ( ( endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK ) == USB_DIR_OUT )
            {
                  ERR( "Interface %u Alt %i has ISO OUT endpoint 0!", ifnum, i );
                  return NULL;      
            }

            if ( endpoint->wMaxPacketSize == 0 )
            {
                  if ( inactSetting < 0 )
                        inactSetting = i;
                  else
                  {
                        ERR( "More thant one inactive alt. setting!" );
                        return NULL;      
                  }
            }
            else
            {
                  if ( actSetting < 0 )
                  {
                        actSetting = i;
                        maxPS = endpoint->wMaxPacketSize;
                        DEBUG( 2, "Active setting=%i maxPS=%i", i, maxPS );
                  }
                  else if ( maxPS < endpoint->wMaxPacketSize )
                  {
                        // This one is better
                        actSetting = i;   
                        maxPS = endpoint->wMaxPacketSize;
                        DEBUG( 2, "Better active setting=%i maxPS=%i", i, maxPS );
                  }
            }           
      }
      
      if ( ( maxPS == 0 ) || ( actSetting < 0 ) || ( inactSetting < 0 ) )
      {
            ERR( "No suitable endpoints! Failed to recognize camera!" );
            return NULL;
      }
      
      // Check the module options
      nw802_validate_params( type );

      // All is Ok, let's register a video device
      MOD_INC_USE_COUNT;      // Code below may sleep, use this as a lock
      
      uvd = usbvideo_AllocateDevice(nw802_cams);
      if ( uvd )
      {
            // Let's setup our freshly allocated usb video driver
            #ifdef ENABLE_DEBUG
            uvd->flags = ( debug >= 3 ) ? FLAGS_DISPLAY_HINTS | FLAGS_OVERLAY_STATS : 0;
            uvd->debug = debug_uv;
            #else
            uvd->flags = 0;
            uvd->debug = 0;
            #endif
            
            uvd->dev = dev;
            uvd->iface = ifnum;
            uvd->ifaceAltInactive = inactSetting;
            uvd->ifaceAltActive = actSetting;
            uvd->video_endp = video_ep;
            uvd->iso_packet_len = maxPS;
            uvd->paletteBits = 1L << VIDEO_PALETTE_RGB24;
            uvd->defaultPalette = VIDEO_PALETTE_RGB24;
            uvd->canvas = nw802_size_to_videosize( size );
            uvd->videosize = uvd->canvas;
            
            // Init the nw802 specific part of uvd & global var
            canvasX = VIDEOSIZE_X( uvd->canvas );
            canvasY = VIDEOSIZE_Y( uvd->canvas );
            NW802_T(uvd)->type = type;
            
            // Configure video & register video device
            nw802_configure_video( uvd );

            if ( usbvideo_RegisterVideoDevice(uvd) )
            {
                  ERR( "Failed to register video device!" );
                  uvd = NULL;
            }
      }
      else
            ERR( "Failed to allocate usbvideo device!" );
            
      MOD_DEC_USE_COUNT;      // Release the 'lock'
      
      return uvd;
}

static unsigned int reg_addr;
static unsigned char reg_val;

static int nw8xx_procfs_read(char *page,char **start,off_t off,int count,int *eof,void *data) {
    char *out = page;
    int len;

      /* Read the register */
      nw802_vendor_read( data, reg_addr, &reg_val, 1 ); 
                  
    /* Stay under PAGE_SIZE or else */
    out += sprintf(out, "Register %04X = %02X\n", reg_addr, reg_val );
    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 nw8xx_procfs_write(struct file *file,const char *buffer,unsigned long count,void *data) {

      char mybuf[16];
      initURB_t urb;

      // Copy in a string
      if ( count > 15 )
            return -EINVAL;

      memcpy( mybuf, buffer, count );

      // Scan it
      if ( mybuf[4] == '=' ) {
            // Write request
            sscanf(mybuf,"%04x=%02x", &urb.index, &urb.data[0]);
            urb.len = 1;
            urb.value = 0;
            nw802_vendor_send( data, &urb);
      } else {
            // Change monitored reg
            sscanf(mybuf,"%04x", &reg_addr);
      }           

      return count;
}
 


// ============================================================================
// Kernel Module init & exit
// ============================================================================

static int __init nw802_init()
{
      int rv;
      
      // Setup callbacks
      struct usbvideo_cb cbTbl;
      memset( &cbTbl, 0, sizeof(cbTbl) );

      cbTbl.probe = nw802_probe;
      cbTbl.setupOnOpen = nw802_setupOnOpen;
      cbTbl.videoStart = nw802_videoStart;
      cbTbl.videoStop = nw802_videoStop;
      cbTbl.processData = nw802_processIsocData;
      cbTbl.procfs_read = nw8xx_procfs_read;
      cbTbl.procfs_write = nw8xx_procfs_write;
      
      // Register usbvideo driver
      rv = usbvideo_register( &nw802_cams,
                        MAX_NW802CAMS,
                        sizeof(nw802_t),
                        "nw802",
                        &cbTbl,
                        THIS_MODULE,
                        nw802_usb_ids );

      // JPGL Decoder tables init
      jpgl_initDecoder();

      // Status
      if ( !rv )
            INFO( "Module loaded" );
      else
            ERR( "Error loading module. Errcode = %i", rv );
            
      return rv;
}

static void __exit nw802_exit()
{
      usbvideo_Deregister( &nw802_cams );
      INFO( "Module unloaded" );
}

module_init( nw802_init );
module_exit( nw802_exit );

      
//
// gcc -O2 -D__KERNEL__ -DMODULE -Wall -DMODVERSIONS -nostdinc -I /usr/src/linux/include -I /usr/lib/gcc-lib/i386-slackware-linux/3.0.4/include/ -include /usr/src/linux/include/linux/modversions.h -c -o nw802.o nw802.c
//

Generated by  Doxygen 1.6.0   Back to index