/* sane - Scanner Access Now Easy.

   Copyright (C) 2005-2007 Gerard Klaver  <gerard at gkall dot hobby dot nl>
   The teco2 and gl646 backend (Frank Zago) are used as a template for 
   this backend.
   At this moment some parts for the usb commands, bayer decoding, add_text, 
   parts of stv680 kernel module, pencam and libgphoto2 program are used/included.
   For the sn9c101 & pas106b & tas5110c1b parts of the Sweex 0.03 and the sonix.sf.net
   code is used and some of sn9c102 kernel module
   For the sn9c102 & pas202b parts of the sonix.sf.net
   code is used and some of sn9c102 kernel module

   This file is part of the SANE package.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.
   
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.
   
   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.
   
   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.
   
   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.
   
   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.
   
   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice. 
*/

/*  $Id: sn9c10x.c,v 1.3 2007/05/22 07:56:33 gerard Exp $

   sn9c10x vidcam  driver Gerard Klaver
*/

/*SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach vidcams
   . - sane_get_devices() : query list of vidcam devices
   . - sane_open() : open a particular vidcam device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get vidcam fd
   . . - sane_get_option_descriptor() : get option information
   . . - sane_control_option() : change option values
   . .
   . . - sane_start() : start image acquisition
   . .   - sane_get_parameters() : returns actual scan parameters
   . .   - sane_read() : read image data (from pipe)
   . .     (sane_read called multiple times; 
   . .      after sane_read returns EOF)
   . .     go back to sane_start() if more frames desired
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened vidcam device
   - sane_exit() : terminate use of backend
*/
/*--------------------------------------------------------------------------*/

#define BUILD 1			/* 2005/01/20  update 03-09-2005 */
#define BACKEND_NAME sn9c10x
#define SN9C10X_CONFIG_FILE "sn9c10x.conf"

/* --------------------- SANE INTERNATIONALISATION ------------------------ */

/* must be first include */
#include "../include/sane/config.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_debug.h"
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"
#include "../include/lassert.h"

/* for add-text routine  */
#include <time.h>
#include "../include/font_6x11.h"
/*-----------------------*/

#include "sn9c10x.h"

#define TIMEOUT 1000

#ifndef PATH_MAX
#define PATH_MAX (1024)
#endif

/*--------------------------------------------------------------------------*/
/* Lists of possible scan modes. */
static SANE_String_Const scan_mode_list[] = {
  COLOR_RAW_STR,
  COLOR_RGB_STR,
  COLOR_RGB_TEXT_STR,
  SANE_VALUE_SCAN_MODE_COLOR,
  COLOR_RGB_USB_ISO_STR,

  NULL
};

/*-----------------------------------------minium, maximum, quantization----*/
static const SANE_Range brightness_range = { -128, 128, 1 };

static const SANE_Range contrast_range = { 0, 255, 1};

static const SANE_Range hue_range = { 0, 15, 1};

static const SANE_Range red_level_range = { -32, 32, 1 };

static const SANE_Range green_level_range = { -32, 32, 1 };

static const SANE_Range blue_level_range = { -32, 32, 1 };

/* List of text options. */
static SANE_String_Const text_list[] = {
  "No text",
  "Text",
  NULL
};

static const int text_val[] = {
  0x00,
  0x01
};

/* List of decomp options. */
static SANE_String_Const decomp_list[] = {
  "RGB24",
  "I420",
  NULL
};

static const int decomp_val[] = {
  0x00,
  0x01
};

/* List of read_format options. */
static SANE_String_Const read_format_list[] = {
  ".BMP",
  ".JPG",
  NULL
};

static const int read_format_val[] = {
  0x00,
  0x01
};



/*--------------------------------------------------------------------------*/

static const struct dpi_color_adjust sn9c101_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B */
  {352, 288, 0, 1, 2},		/* CIF         */
  {320, 240, 0, 1, 2},		/* QVGA (SIF)  */
  {176, 144, 0, 1, 2},		/* QCIF        */
  {160, 120, 0, 1, 2},		/* QSIF        */
  /* must be the last entry */
  {0, 0, 0, 0, 0}
};

static const struct dpi_color_adjust sn9c102_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B */
  {640, 480, 0, 1, 2},		/* VGA         */
  {352, 288, 0, 1, 2},		/* CIF         */
  {320, 240, 0, 1, 2},		/* QVGA (SIF)  */
  {176, 144, 0, 1, 2},		/* QCIF        */
  {160, 120, 0, 1, 2},		/* QSIF        */
  /* must be the last entry */
  {0, 0, 0, 0, 0}
};

static const struct vidcam_hardware vidcams[] = {

  {0x0c45, 0x6005, USB_CLASS_VENDOR_SPEC,
   "Microdia Sonix", "sn9c101 & tas5110c1b",
   sn9c101_dpi_color_adjust},

  {0x0c45, 0x6009, USB_CLASS_VENDOR_SPEC,
   "Sonix", "sn9c101 & pas106b",
   sn9c101_dpi_color_adjust},
  
  {0x0c45, 0x6028, USB_CLASS_VENDOR_SPEC,
   "Microdia Sonix", "sn9c102 & pas202b",
   sn9c102_dpi_color_adjust},

  {0x0c45, 0x6029, USB_CLASS_VENDOR_SPEC,
   "Microdia Sonix", "sn9c101 & pas106b",
   sn9c101_dpi_color_adjust}
};

/* List of vidcams attached. */
static Sn9c10x_Vidcam *first_dev = NULL;
static int num_devices = 0;
/* used by sane_get_devices */
static const SANE_Device **devlist = NULL;

/*----------------------------------------------------------- */

/* Local functions. */

/* Display a buffer in the log. Display by lines of 16 bytes. */
static void
hexdump (int level, const char *comment, unsigned char *buf, const int length)
{
  int i;
  char line[128];
  char *ptr;
  char asc_buf[17];
  char *asc_ptr;

  DBG (level, "  %s\n", comment);

  i = 0;
  goto start;

  do
    {
      if (i < length)
	{
	  ptr += sprintf (ptr, " %2.2x", *buf);

	  if (*buf >= 32 && *buf <= 127)
	    {
	      asc_ptr += sprintf (asc_ptr, "%c", *buf);
	    }
	  else
	    {
	      asc_ptr += sprintf (asc_ptr, ".");
	    }
	}
      else
	{
	  /* After the length; do nothing. */
	  ptr += sprintf (ptr, "   ");
	}

      i++;
      buf++;

      if ((i % 16) == 0)
	{
	  /* It's a new line */
	  DBG (level, "  %s    %s\n", line, asc_buf);

	start:
	  ptr = line;
	  *ptr = '\0';
	  asc_ptr = asc_buf;
	  *asc_ptr = '\0';

	  ptr += sprintf (ptr, "  %3.3d:", i);
	}
    }
  while (i < ((length + 15) & ~15));
}

/* Returns the length of the longest string, including the terminating
 * character. */
static size_t
max_string_size (SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;

  for (i = 0; strings[i]; ++i)
    {
      size = strlen (strings[i]) + 1;
      if (size > max_size)
	{
	  max_size = size;
	}
    }
  return max_size;
}

/* Initialize a vidcam entry. Return an allocated vidcam with some
 *  */
static Sn9c10x_Vidcam *
sn9c10x_init (void)
{
  Sn9c10x_Vidcam *dev;

  DBG (DBG_proc, "sn9c10x_init: enter\n");

  /* Allocate a new vidcam entry. */
  dev = malloc (sizeof (Sn9c10x_Vidcam));
  if (dev == NULL)
    {
      return NULL;
    }
  memset (dev, 0, sizeof (Sn9c10x_Vidcam));

/* Allocate the windoww buffer*/
  dev->windoww_size = 0x20;
  dev->windoww = malloc (dev->windoww_size);
  if (dev->windoww == NULL)
    {
      free (dev);
      return NULL;
    }

/* Allocate the windowr buffer*/
  dev->windowr_size = 0x20;
  dev->windowr = malloc (dev->windowr_size);
  if (dev->windowr == NULL)
    {
      free (dev);
      free (dev->windoww);
      return NULL;
    }

  dev->fd = -1;

  DBG (DBG_proc, "sn9c10x_init: exit\n");

  return (dev);
}

static SANE_Status
sn9c10x_init_2 (Sn9c10x_Vidcam *dev)
{
  SANE_Status status;

  DBG (DBG_proc, "sn9c10x_init_2: enter\n");

  /* Allocate the buffer used to transfer the USB data */
  /* Check for max. format image size so buffer size can
   * be adjusted                                        */
  if (dev->CIF)
    dev->buffer_size = 352 * 288 + dev->header_length;
  if (dev->VGA)
    dev->buffer_size = 640 * 480 + dev->header_length;

  DBG (DBG_proc, "sn9c10x_init_2: dev->bufffer = 0x%lx\n", (unsigned long) (size_t) dev->buffer_size);

  dev->buffer = malloc (dev->buffer_size);

  if (dev->buffer == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      return SANE_STATUS_NO_MEM;
    }

  /* Allocate the output buffer used for bayer conversion */
  dev->output_size = dev->buffer_size * 3 -(2 * dev->header_length);

  dev->output = malloc (dev->output_size);
  if (dev->output == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      free (dev->buffer);
      return SANE_STATUS_NO_MEM;
    }
  dev->image_size = dev->buffer_size;
  
  dev->image = malloc (dev->image_size);
  if (dev->image == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      free (dev->buffer);
      free (dev->output);
    
       return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_proc, "sn9c10x_init_2: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

/* Closes an open vidcams. */
static void
sn9c10x_close (Sn9c10x_Vidcam * dev)
{
  DBG (DBG_proc, "sn9c10x_close: enter \n");

  if (dev->fd != -1)
    {

      DBG (DBG_proc, "sn9c10x_close: fd !=-1 \n");
      sanei_usb_close (dev->fd);
      dev->fd = -1;
    }

  DBG (DBG_proc, "sn9c10x_close: exit\n");
}

/* Frees the memory used by a vidcam. */
static void
sn9c10x_free (Sn9c10x_Vidcam * dev)
{
  int i;

  DBG (DBG_proc, "sn9c10x_free: enter\n");

  if (dev == NULL)
    return;

  sn9c10x_close (dev);
  if (dev->devicename)
    {
      free (dev->devicename);
    }
  if (dev->buffer)
    {
      free (dev->buffer);
    }
  if (dev->output)
    {
      free (dev->output);
    }
  if (dev->image)
    {
      free (dev->image);
    }
  if (dev->windoww)
    {
      free (dev->windoww);
    }
  if (dev->windowr)
    {
      free (dev->windowr);
    }
  for (i = 1; i < OPT_NUM_OPTIONS; i++)
    {
      if (dev->opt[i].type == SANE_TYPE_STRING && dev->val[i].s)
	{
	  free (dev->val[i].s);
	}
    }
  if (dev->resolutions_list)
    free (dev->resolutions_list);

  free (dev);

  DBG (DBG_proc, "sn9c10x_free: exit\n");
}

static SANE_Status
sn9c10x_set_config (Sn9c10x_Vidcam * dev, int configuration, int interface,
		    int alternate)
{
  SANE_Status status;

DBG (DBG_proc, "sn9c10x_set_config: open\n");

/*disable problems with it */
/* status = sanei_usb_set_configuration (dev->fd, configuration);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to set configuration %d\n",
	   configuration);
      return status;
    }
*/
  status = sanei_usb_claim_interface (dev->fd, interface);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to claim interface\n");
      return status;
    }

  status = sanei_usb_set_altinterface (dev->fd, alternate);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to set alternate interface %d\n",
	   alternate);
      return status;
    }
  DBG (DBG_proc,
       "sn9c10x_vidcam_init: configuration=%d, interface=%d, alternate=%d\n",
       configuration, interface, alternate);

  DBG (DBG_proc, "sn9c10x_set_config: exit\n");
  return status;
}

/*-------------------------------------------------------*/

/* from sn9c102_core.c */
/* NOTE: reading some registers always returns 0 */
static int
sn9c10x_read_reg (Sn9c10x_Vidcam * dev, unsigned int reg_num)
{
  SANE_Status status;
  /* char buf[8];  */
  size_t sizer;
  DBG (DBG_proc, "sn9c10x_read_reg: enter\n");
  /* if (usb_control_msg(dev->fd, 0xc1, 0x00, reg_num, 0, buf, 1, TIMEOUT) < 0) */
  sizer = 0x01;
  status =
    sanei_usb_control_msg (dev->fd, 0xc1, 0x00, reg_num, 0, sizer,
			   dev->windowr);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_read_reg: Can't read reg=%d\n", reg_num);
      return SANE_FALSE;
    }
  DBG (DBG_proc, "sn9c10x_read_reg: reg_num=0x%x, value=0x%x exit\n", reg_num, dev->windowr[0]);
  return SANE_STATUS_GOOD;
}
static int
sn9c10x_write_reg (Sn9c10x_Vidcam * dev, unsigned int reg_num, int value)
{
  SANE_Status status;
  size_t sizew;

  DBG (DBG_proc, "sn9c10x_write_reg: enter\n");
  dev->windoww[0] = value;
  /* if (usb_control_msg(dev->fd, 0x41, 0x08, reg_num, 0, buf, 1, TIMEOUT) < 0) */
  sizew = 0x01;
  status =
    sanei_usb_control_msg (dev->fd, 0x41, 0x08, reg_num, 0, sizew,
			   dev->windoww);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x, Can't write reg=[%d]=%d\n", reg_num, value);
      return SANE_FALSE;
    }
  DBG (DBG_proc, "sn9c10x_write_reg: reg_num=0x%x, value=0x%x, exit\n", reg_num, dev->windoww[0]);
  return SANE_STATUS_GOOD;
}

/*
** i2c (sensor conguration)
*/
static SANE_Status
sn9c10x_i2c_wait (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  int i, r;

  DBG (DBG_proc, "sn9c10x_i2c_wait: enter\n");
  for (i = 1; i <= 5; i++)
    {
      status = sn9c10x_read_reg (dev, 0x08);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_proc, "sn9c10x_i2c_wait: sn9c10x_read: error\n");
	  return SANE_FALSE;
	}
      /*if (status == SANE_STATUS_GOOD) & 0x04)
         return SANE_STATUS_GOOD; */
      if (dev->frequency != 0x01)
	usleep ((u_long) 5 * 8);	/* 400 KHz */
      else
	usleep ((u_long) 16 * 8);	/* 100 KHz */
      DBG (DBG_proc, "sn9c10x_i2c_wait: sn9c10x_read_reg: loop i=%d\n", i);
    }
  DBG (DBG_proc, "sn9c10x_i2c_wait: exit\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
sn9c10x_i2c_error (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "sn9c10x_i2c_error: enter\n");
  status = sn9c10x_read_reg (dev, 0x08);
  DBG (DBG_proc, "sn9c10x_i2c_error: exit\n");

  /*return (r < 0 || (r >= 0 && !(r & 0x08))) ? -EIO : 0; */
  /* not yet placed, a check for 0x08 ??? */
  return status;
}

static SANE_Status
sn9c10x_i2c_read (Sn9c10x_Vidcam * dev, SANE_Byte address)
{
  SANE_Status status;
  /* u_char buf[8] = { 0xX1, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 }; */
  size_t sizew;
  size_t sizer;

  DBG (DBG_proc, "sn9c10x_i2c_read: enter\n");

  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  /* Write cycle - address */
  /* buf[2] = address; */
  /* if ((res = usb_control_msg(dev->fd, 0x41, 0x08, 0x08, 0, buf, 8, TIMEOUT)) < 0)  */

  dev->windoww[0] = (dev->interface_i2c_wires | dev->frequency | 0x30);
  dev->windoww[1] = dev->slave_id;
  dev->windoww[2] = address;
  dev->windoww[3] = 0x00;
  dev->windoww[4] = 0x00;
  dev->windoww[5] = 0x00;
  dev->windoww[6] = 0x00;
  dev->windoww[7] = 0x10;
  sizew = 0x08;
  status =
    sanei_usb_control_msg (dev->fd, 0x41, 0x08, 0x08, 0, sizew, dev->windoww);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_read 1, Can't write \n");
      return SANE_FALSE;
    }
  hexdump (DBG_proc, "sn9c10x_i2c_read, write string", dev->windoww, sizew);

  status = sn9c10x_i2c_wait (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_read, Can't wait \n");
      return SANE_FALSE;
    }

  /* Read cycle - 1 byte */
  /* 0x93 old i2c type 400 KHz, 2WIRES + 0x02 */
  dev->windoww[0] = (dev->interface_i2c_wires | dev->frequency | 0x10 | 0x02);
  dev->windoww[1] = dev->slave_id;	/* slave read_id */
  dev->windoww[7] = 0x10;
/*	if ((res = usb_control_msg(dev->fd, 0x41, 0x08, 0x08, 0, buf, 8, TIMEOUT)) < 0) */
  sizew = 0x08;
  status =
    sanei_usb_control_msg (dev->fd, 0x41, 0x08, 0x08, 0, sizew, dev->windoww);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_read 2, Can't write \n");
      return SANE_FALSE;
    }
  hexdump (DBG_proc, "sn9c10x_i2c_read: read cycle 1 byte", dev->windoww,
	   sizew);

  status = sn9c10x_i2c_wait (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_read, Can't wait \n");
      return SANE_FALSE;
    }

  /* The read byte will be placed in buf[4] */
  /* if ((res = usb_control_msg(dev->fd, 0xc1, 0x00, 0x0a, 0, buf, 5, TIMEOUT)) < 0) */
  sizer = 0x05;
  status =
    sanei_usb_control_msg (dev->fd, 0xc1, 0x00, 0x0a, 0, sizer, dev->windowr);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_read, Can't write\n");
      return SANE_FALSE;
    }
  hexdump (DBG_proc, "sn9c10x_read_reg", dev->windowr, sizer);
  status = sn9c10x_i2c_error (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_read, Can't error\n");
      return SANE_FALSE;
    }

/*	return err ? -1 : (int)buf[4]; */
  if (dev->windowr[4] >= 0)
    return SANE_STATUS_GOOD;
  else
    return SANE_FALSE;
  DBG (DBG_proc, "sn9c10x_i2c_read: exit\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
sn9c10x_i2c_write (Sn9c10x_Vidcam * dev, SANE_Byte address, SANE_Byte value)
{
  SANE_Status status;
  size_t sizew;
  DBG (DBG_proc, "sn9c10x_i2c_write: enter\n");
  /*     u_char buf[8] = { 0xA1, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 }; */
  dev->windoww[0] = (dev->interface_i2c_wires | dev->frequency | 0x20);

  dev->windoww[1] = dev->slave_id;
  dev->windoww[2] = address;
  dev->windoww[3] = value;
  dev->windoww[4] = 0x00;
  dev->windoww[5] = 0x00;
  dev->windoww[6] = 0x00;
  dev->windoww[7] = 0x14;

  sizew = 0x08;
/*	if ((res = usb_control_msg(dev->fd, 0x41, 0x08, 0x08, 0, buf, 8, TIMEOUT)) < 0) */
  status =
    sanei_usb_control_msg (dev->fd, 0x41, 0x08, 0x08, 0, sizew, dev->windoww);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_write, Can't write \n");
    }
  hexdump (DBG_proc, "sn9c10x_i2c_write", dev->windoww, sizew);

  status = sn9c10x_i2c_wait (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_write: sn9c10x_i2c_wait: Can't wait \n");
    }

  status = sn9c10x_i2c_error (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "sn9c10x_i2c_write: sn9c10x_i2c_error: Can't error \n");
    }

  /* return err ? -1 : 0; */
  DBG (DBG_proc, "sn9c10x_i2c_write: exit\n");
  return SANE_STATUS_GOOD;
}

/*
static SANE_Status
sn9c10x_i2c_write(Sn9c10x_Vidcam * dev, SANE_Byte address, SANE_Byte value)
{
	struct sn9c102_sensor* sensor = cam->sensor;
	sn9c102_i2c_try_raw_write(cam, sensor, 3, 
	                                 sensor->slave_id, address,
	                                 value, 0, 0, 0);

	return SANE_STATUS_GOOD;
}
*/


/*
** sensor CIF CMOS sensor TAS5110C1B
*/
static SANE_Status
tas5110c1b_probe (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  int r0, r1;
  unsigned int pid = 0;

  DBG (DBG_proc, "tas5110c1b_probe: enter\n");

  /*sn9c10x_write_reg (dev, 0x01, 0x01); */
  /* sensor power down */
  /*sn9c10x_write_reg (dev, 0x01, 0x00);	*/
  /* sensor power on */
  /* sn9c10x_write_reg (dev, 0x17, 0x64); */
  /* sensor clock at 24 MHz */

  /*if (status != SANE_STATUS_GOOD)
     return SANE_FALSE; */
  DBG (DBG_proc, "tas5110c1b_probe: exit\n");
  return SANE_STATUS_GOOD;
}

/*
** sensor CIF CMOS sensor PAS106B
*/
static SANE_Status
pas106b_probe (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  int r0, r1;
  unsigned int pid = 0;

  DBG (DBG_proc, "pas106b_probe: enter\n");
  /*
     Minimal initialization to enable the I2C communication
     NOTE: do NOT change the values!
   */
  status = SANE_STATUS_GOOD;

  sn9c10x_write_reg (dev, 0x01, 0x01);	/* sensor power down */
  sn9c10x_write_reg (dev, 0x01, 0x00);	/* sensor power on */
  sn9c10x_write_reg (dev, 0x17, 0x28);	/* sensor clock at 24 MHz */


  r0 = sn9c10x_i2c_read (dev, 0x00);
  r1 = sn9c10x_i2c_read (dev, 0x01);

  if (r0 < 0 || r1 < 0)
    return SANE_FALSE;

  pid = (r0 << 11) | ((r1 & 0xf0) >> 4);
  if (pid != 0x007)
    return SANE_FALSE;
  DBG (DBG_proc, "pas106b_probe: exit\n");
  return SANE_STATUS_GOOD;
}

/*
** sensor CIF CMOS sensor PAS202B
*/
static SANE_Status
pas202b_probe (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  int r0, r1;
  unsigned int pid = 0;

  DBG (DBG_proc, "pas202b_probe: enter\n");
  /*
     Minimal initialization to enable the I2C communication
     NOTE: do NOT change the values!
   */
  status = SANE_STATUS_GOOD;

  /*status += */
  sn9c10x_write_reg (dev, 0x01, 0x01);	/* sensor power down */
  /*status += */
  sn9c10x_write_reg (dev, 0x01, 0x40);	/* sensor power on */
  /*status += */
  sn9c10x_write_reg (dev, 0x17, 0x28);	/* sensor clock at 24 MHz */

  /*if (status != SANE_STATUS_GOOD)
     return SANE_FALSE; */

  r0 = sn9c10x_i2c_read (dev, 0x00);
  r1 = sn9c10x_i2c_read (dev, 0x01);

  if (r0 < 0 || r1 < 0)
    return SANE_FALSE;

  pid = (r0 << 4) | ((r1 & 0xf0) >> 4);
  if (pid != 0x017)
    return SANE_FALSE;
  DBG (DBG_proc, "pas202b_probe: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

/* data better placed in for example
 * webcam-sensor.h so it can be used
 * by different webcam chips in the
 * future
 * */
static SANE_Status
pas106b_init (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "pas106b_init: enter\n");

  sn9c10x_write_reg (dev, 0x10, 0x00);
  sn9c10x_write_reg (dev, 0x11, 0x00);
  sn9c10x_write_reg (dev, 0x14, 0x00);
  sn9c10x_write_reg (dev, 0x17, 0x20);
  sn9c10x_write_reg (dev, 0x19, 0x20);
  sn9c10x_write_reg (dev, 0x18, (0x09 | dev->video_mode));
  
  sn9c10x_i2c_write (dev, 0x02, 0x0c);
  sn9c10x_i2c_write (dev, 0x03, 0x12);
  sn9c10x_i2c_write (dev, 0x04, 0x05);
  sn9c10x_i2c_write (dev, 0x05, 0x5a);
  sn9c10x_i2c_write (dev, 0x06, 0x88);
  sn9c10x_i2c_write (dev, 0x07, 0x80);
  sn9c10x_i2c_write (dev, 0x08, 0x01);
  sn9c10x_i2c_write (dev, 0x0a, 0x01);
  sn9c10x_i2c_write (dev, 0x0b, 0x00);
  sn9c10x_i2c_write (dev, 0x10, 0x06);
  sn9c10x_i2c_write (dev, 0x11, 0x06);
  sn9c10x_i2c_write (dev, 0x12, 0x00);
  sn9c10x_i2c_write (dev, 0x14, 0x02);
  sn9c10x_i2c_write (dev, 0x13, 0x01);

  usleep (400 * 1000);

  DBG (DBG_proc, "pas106b_init: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

/* data better placed in for example
 * webcam-sensor.h so it can be used
 * by different webcam chips in the
 * future
 * */
static SANE_Status
pas202b_init (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "pas202b_init: enter\n");

  sn9c10x_write_reg (dev, 0x10, 0x00);
  sn9c10x_write_reg (dev, 0x11, 0x00);
  sn9c10x_write_reg (dev, 0x14, 0x00);
  sn9c10x_write_reg (dev, 0x17, 0x20);
  sn9c10x_write_reg (dev, 0x19, 0x30);
  sn9c10x_write_reg (dev, 0x18, (0x09 | dev->video_mode));
  
/*  sn9c10x_write_reg (dev, 0x15, 0x28);
  sn9c10x_write_reg (dev, 0x16, 0x1e);
    in sn9c10x_size routine */
 /* sn9c10x_write_reg (dev, 0x1a, 0x14);
  sn9c10x_write_reg (dev, 0x1B, 0x0f);
  */
  sn9c10x_write_reg (dev, 0x1c, 5);
  sn9c10x_write_reg (dev, 0x1d, 15);
  sn9c10x_write_reg (dev, 0x1e, 4);
  sn9c10x_write_reg (dev, 0x1f, 11);

  if (dev->scan_mode == SN9C10X_COLOR) {  /* from br. */
  sn9c10x_i2c_write (dev, 0x02, 0x02);
  sn9c10x_i2c_write (dev, 0x03, 0x40);
  sn9c10x_i2c_write (dev, 0x04, 0x12);
  sn9c10x_i2c_write (dev, 0x05, 0x04);
  sn9c10x_i2c_write (dev, 0x0b, 0x01);
  sn9c10x_i2c_write (dev, 0x0c, 0x2f);
  sn9c10x_i2c_write (dev, 0x0d, 0x7e);
  sn9c10x_i2c_write (dev, 0x0e, 0x01);
  sn9c10x_i2c_write (dev, 0x0f, 0xdc);
  sn9c10x_i2c_write (dev, 0x13, 0x63);
  sn9c10x_i2c_write (dev, 0x15, 0x70);
  sn9c10x_i2c_write (dev, 0x11, 0x01);
  }
  else {
  sn9c10x_i2c_write (dev, 0x02, 0x14);
  sn9c10x_i2c_write (dev, 0x03, 0x40);
  sn9c10x_i2c_write (dev, 0x0d, 0x2c);
  sn9c10x_i2c_write (dev, 0x0e, 0x01);
  sn9c10x_i2c_write (dev, 0x0f, 0xa9);
  sn9c10x_i2c_write (dev, 0x10, 0x08);
  sn9c10x_i2c_write (dev, 0x13, 0x63);
  sn9c10x_i2c_write (dev, 0x15, 0x70);
  sn9c10x_i2c_write (dev, 0x11, 0x01);
  }

  usleep (400 * 1000);
  status = SANE_STATUS_GOOD;

  DBG (DBG_proc, "pas202b_init: exit\n");
  return status;
}

static SANE_Status
tas5110c1b_init (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  /* struct sn9c102_device *cam = (struct sn9c102_device *)uvd->user_data; */
  SANE_Byte h_start, v_start;
  DBG (DBG_proc, "tas5110c1b_init: enter\n");

  sn9c10x_write_reg (dev, 0x01, 0x01);	/* control: power down sensor */
  sn9c10x_write_reg (dev, 0x01, 0x44);	/* control: 24 MHz system clock, transfer enable */
  sn9c10x_write_reg (dev, 0x10, 0x00);	/* R_GAIN/B_GAIN */
  sn9c10x_write_reg (dev, 0x11, 0x00);	/* G-GAIN */
  sn9c10x_write_reg (dev, 0x14, 0x0a);	/* OFFSET for image data */
  sn9c10x_write_reg (dev, 0x17, 0x60);	/* 12 MHz sensor clock  64?*/
  sn9c10x_write_reg (dev, 0x18, 0x06);	/* VSYNC/HSYNC polarity */
  sn9c10x_write_reg (dev, 0x19, 0xfb);	/* pixel clock timing */

  DBG (DBG_proc, "tas5110c1b_init: sn9c10x_i2c_write enter\n");
  sn9c10x_i2c_write (dev, 0x80, 0xc0);

  DBG (DBG_proc, "tas5110c1b_init: sn9c10x_i2c_write exit\n");

	/*** set crop area ***/
  h_start = 69;
  v_start = 9;

  sn9c10x_write_reg (dev, 0x12, h_start);	/* H_START */
  sn9c10x_write_reg (dev, 0x13, v_start);	/* V_START */

  /* Don't change ! */
  sn9c10x_write_reg (dev, 0x1a, 0x14);	/* HO_SIZE */
  sn9c10x_write_reg (dev, 0x1b, 0x0a);	/* VO_SIZE */

  usleep (200 * 1000);
  
  DBG (DBG_proc, "tas5110c1b_init: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

static SANE_Status
sn9c10x_size (Sn9c10x_Vidcam * dev, int h_size, int v_size)
{
  SANE_Status status;
  DBG (DBG_proc, "sn9c10x_size: enter\n");
  sn9c10x_write_reg (dev, 0x15, (h_size / 16));
  sn9c10x_write_reg (dev, 0x16, (v_size / 16));
 if (dev->sensortype != 0x5110) {
  sn9c10x_write_reg (dev, 0x1a, (h_size / 32));
  sn9c10x_write_reg (dev, 0x1b, (v_size / 32));
 }
  DBG (DBG_proc, "sn9c10x_size(%d, %d) Ok!, exit\n", h_size, v_size);
  status = SANE_STATUS_GOOD;
  return status;
}

static SANE_Status
sn9c10x_hue (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  int hue;
  DBG (DBG_proc, "sn9c10x_hue: enter\n");

/* hue gain setting */
  
  hue = (dev->val[OPT_HUE].w);

  sn9c10x_write_reg (dev, 0x10, ((hue << 8) + hue));	/* R_GAIN/B_GAIN */
  sn9c10x_write_reg (dev, 0x11, hue);	/* G_GAIN */

  DBG (DBG_proc, "sn9c10x_hue (0x%x) Ok!, exit\n", hue);
  status = SANE_STATUS_GOOD;
  return status;
}

static SANE_Status
sn9c10x_contrast (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  int contrast;
  DBG (DBG_proc, "sn9c10x_contrast: enter\n");

/* contrast gain setting */
  
  contrast = (dev->val[OPT_CONTRAST].w);

 if (dev->sensortype = 0x5110) 
  sn9c10x_i2c_write (dev, 0x20, contrast); /* gain tas5110c1b */
 if (dev->sensortype = 0x0106)
	 sn9c10x_i2c_write (dev, 0x05, contrast);
 if (dev->sensortype = 0x0202) {
	 sn9c10x_i2c_write (dev, 0x10, (contrast>>3));
	 sn9c10x_i2c_write (dev, 0x11, 0x01); /* i2c_update_sync_flag[0] */
 }
  DBG (DBG_proc, "sn9c10x_contrast (0x%x) Ok!, exit\n", contrast);
  status = SANE_STATUS_GOOD;
  return status;
}

static SANE_Status
sn9c10x_video (Sn9c10x_Vidcam * dev, int onoff)
{
  SANE_Status status;
  DBG (DBG_proc, "sn9c10x_video: enter\n");

  sn9c10x_write_reg (dev, 0x01, (onoff ? 0x44 : 0x00));

  DBG (DBG_proc, "sn9c10x_video %s! exit\n", (onoff ? "started" : "stopped"));
  return SANE_STATUS_GOOD;
}

/*---------------------------------------------------------*/
/* Reset vidcam */
static SANE_Status
sn9c10x_reset_vidcam (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  size_t sizew;			/* significant size of window */
  size_t sizer;

  DBG (DBG_proc, "sn9c10x_reset_vidcam: enter\n");

  if (dev->framecount <= 1000)
    {
      sizew = dev->windoww_size;
      sizer = dev->windowr_size;

      memset (dev->windoww, 0, sizew);
      memset (dev->windowr, 0, sizer);

      sizew = 0x00;  /* was 0 */

      DBG (DBG_proc, "sn9c10x_reset_vidcam: CMDID_STOP_VIDEO end\n");

      status = sn9c10x_video (dev, 0);	/* stop video */
      if (status != SANE_STATUS_GOOD)
	{
	  return status;
	}
      DBG (DBG_proc, "sn9c10x_reset_vidcam: Camera reset to idle mode: end\n");
    }
  status = SANE_STATUS_GOOD;
  DBG (DBG_proc, "sn9c10x_reset_vidcam: exit\n");

  return status;
}

/* Inquiry a device and returns TRUE if is supported. */
static int
sn9c10x_identify_vidcam (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  SANE_Word vendor;
  SANE_Word product;
  int i;
  size_t sizer;

  DBG (DBG_info, "sn9c10x_identify_vidcam: open\n");

  status = sanei_usb_get_vendor_product (dev->fd, &vendor, &product);

  /* Loop through our list to make sure this scanner is supported. */
  for (i = 0; i < NELEMS (vidcams); i++)
    {
      if (vidcams[i].vendor == vendor && vidcams[i].product == product)
	{

	  DBG (DBG_info,
	       "sn9c10x_identify_vidcam: vidcam 0x%x:0x%x is in list\n",
	       vendor, product);

	  dev->hw = &(vidcams[i]);

	  sizer = dev->windowr_size;
	  memset (dev->windowr, 0, sizer);

	  /*  configuration = 1, interface = 0, alternate = 0 */
	    status = sn9c10x_set_config (dev, 1, 0, 0);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "sn9c10x_set_config: SN9C10X FAILED to set configure\n");
	      return status;
	    }
	  DBG (DBG_proc,
	       "sn9c10x_identify_vicdam: urb xx CMDID_PING, check if SN9C10X is present\n");
          dev->CIF = 0;
	  dev->VGA = 0;
	  /*----------------------------
           * check with reading ASIC ID ? 
	   *                             */
	  /* Sensor detection is based on USB pid/vid */
	  if (product == 0x6001 || product == 0x6005 || product == 0x60ab)
	    {
	      dev->sensortype = 0x5110;
	      dev->slave_id = 0x11;
	      dev->interface_i2c_wires = 0x00;	/* SN9C102_I2C_3WIRES */
	      dev->frequency = 0x01;	/* SN9C102_I2C_100KHZ */
	      dev->CIF = 1;
	    }
/*	.probe = tas5110c1b_probe,
	.init = tas5110c1b_init,
	.adjust_picture = tas5110c1b_adjust_picture,
	.post_process = NULL
*/
	  if (product == 0x6009 || product == 0x6029)
	    {
	      dev->sensortype = 0x0106;
	      dev->slave_id = 0x40;
	      dev->interface_i2c_wires = 0x80;	/* SN9C102_I2C_2WIRES */
	      dev->frequency = 0x01;	/* SN9C102_I2C_100KHZ */
	      dev->CIF = 1;
	    }
/*	.probe = pas106b_probe,
	.init = pas106b_init,
	.adjust_picture = pas106b_adjust_picture,
	.post_process = pas106b_post_process
*/
         if (product == 0x6028)
	    {
	      dev->sensortype = 0x0202;
	      dev->slave_id = 0x40;
	      dev->interface_i2c_wires = 0x80;	/* SN9C102_I2C_2WIRES */
	      dev->frequency = 0x01;	/* SN9C102_I2C_100KHZ */
	      dev->VGA = 1;
	    }
/*	.probe = pas202b_probe,
	.init = pas202b_init,
	.adjust_picture = pas202b_adjust_picture,
	.post_process = pas202b_post_process
*/

	  DBG (DBG_info, "sn9c10x_identify_vidcam: sensor = 0x%x\n",
	       dev->sensortype);

	  /*  configuration = 1, interface = 0, alternate = 1 */
	  status = sn9c10x_set_config (dev, 1, 0, 1);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "sn9c10x_set_config: SN9C10X FAILED to set configure\n");
	      return status;
	    }

	  DBG (DBG_info, "sn9c10x_identify_vidcam: exit vidcam supported\n");
	  return SANE_TRUE;
	}
    }
  dev->framecount = 0;
  DBG (DBG_error,
       "sn9c10x_identify_vidcam: exit this is not a SN9C10X exit\n");
  return SANE_FALSE;
}

static SANE_Status
sn9c10x_vidcam_init (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  DBG (DBG_proc, "sn9c10x_vidcam_init: open\n");

  if (dev->sensortype == 0x0106)
    status = pas106b_probe (dev);
  if (dev->sensortype == 0x5110) 
	  status = tas5110c1b_probe (dev);
  if (dev->sensortype == 0x0202)
	  status = pas202b_probe (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to probe sensortype 0x%x\n",
	   dev->sensortype);
      return status;
    }
  if (dev->sensortype == 0x0106) 
    status = pas106b_init (dev);
  if (dev->sensortype == 0x5110)
    status = tas5110c1b_init (dev);
  if (dev->sensortype == 0x0202)
     status = pas202b_init (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to init sensortype 0x%x\n",
	   dev->sensortype);
      return status;
    }
  if (dev->sensortype != 0x5110) {
  sn9c10x_size (dev, dev->x_resolution, dev->y_resolution);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to init frame size\n");
      return status;
    }
  }
  /*  configuration = 1, interface = 0, alternate = 1 */
  status = sn9c10x_set_config (dev, 1, 0, 1);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to set configure\n");
      return status;
    } 

  sn9c10x_hue (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to set hue value\n");
      return status;
    }
  
  sn9c10x_contrast (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "sn9c10x_vidcam_init: SN9C10X FAILED to set contrast value\n");
      return status;
    }
  
  status = sn9c10x_video (dev, 1);	/* start video */
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sn9c10x_vidcam_init: SN9C10X FAILED to start video\n");
      return status;
    }
  
  if (dev->scan_mode != SN9C10X_COLOR_RAW)
  {
  /* Turn on the led */
  sn9c10x_write_reg(dev, 0x1,0x00);

  /* Sending the magic init sequence */
  
  /* pixel number for sensor */
 /* buf[0]=0x16;buf[1]=0x12; */
  sn9c10x_write_reg(dev,0x15, 0x16);
  sn9c10x_write_reg(dev,0x16, 0x12);
  
  /* MCK_SIZE=2 */
  /*buf[0]=0x20;
  buf[0]=0x24; */
  sn9c10x_write_reg(dev,0x19,0x24);
  
  /* Compression & Synchro */
 /* buf[0]=0x09; */
  /*buf[0]=0x89; */
  /*buf[0]=0x89; */
  sn9c10x_write_reg(dev,0x18, 0x09);
   

  /* clock */
/*  buf[0]=0x28; */
  /* buf[0]=0x24; */
  sn9c10x_write_reg(dev,0x17, 0x24);

  /* H_start */
 /* buf[0]=0x04;buf[1]=0x03;buf[2]=0x00; */
  sn9c10x_write_reg(dev,0x12, 0x04);
  sn9c10x_write_reg(dev,0x13, 0x03);
  sn9c10x_write_reg(dev,0x14, 0x00);

  /* Active window */
/*  buf[0]=0x03;buf[1]=0x02;buf[2]=0x08;buf[3]=0x07; */
  sn9c10x_write_reg(dev,0x1c, 0x03);

  sn9c10x_write_reg(dev,0x1d, 0x02);
  sn9c10x_write_reg(dev,0x1e, 0x08);
  sn9c10x_write_reg(dev,0x1f, 0x07);
  
  /* Greenify  */
  /*buf[0]=0x07; */
  /*sn9c102_write_regs(cam,0x11,buf,1); */
  
/*	if (dev->sensor->init != NULL) {
		dev->sensor->init(uvd);
	}
  */  
  /*enable transfert */
  /*buf[0]=0x04; */
  sn9c10x_write_reg(dev,0x01, 0x04);
  }
  status = (SANE_STATUS_GOOD);
  DBG (DBG_proc, "sn9c10x_vidcam_init: exit, status=%d\n", status);
  return status;
}

/* Attach a vidcam to this backend. */
static SANE_Status
attach_vidcam (SANE_String_Const devicename, Sn9c10x_Vidcam ** devp)
{
  Sn9c10x_Vidcam *dev;
  int fd;
  SANE_Status status;

  DBG (DBG_proc, "attach_vidcam: %s\n", devicename);

  if (devp)
    *devp = NULL;

  /* Check if we know this device name. */
  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  if (devp)
	    {
	      *devp = dev;
	    }
	  DBG (DBG_info, "device is already known\n");
	  return SANE_STATUS_GOOD;
	}
    }

  /* Allocate a new vidcam entry. */
  dev = sn9c10x_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "sn9c10x_init ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_info, "attach_vidcam: opening USB device %s\n", devicename);

  if (sanei_usb_open (devicename, &fd) != 0)
    {
      DBG (DBG_error, "ERROR: attach_vidcam: open failed\n");
      sn9c10x_free (dev);
      return SANE_STATUS_INVAL;
    }
  /* Fill some scanner specific values. */
  dev->devicename = strdup (devicename);
  dev->fd = fd;

  /* Now, check that it is a vidcam we support. */

  if (sn9c10x_identify_vidcam (dev) == SANE_FALSE)
    {
      DBG (DBG_error, "ERROR: attach_vidcam: vidcam-identification failed\n");
      sn9c10x_free (dev);
      return SANE_STATUS_INVAL;
    }

  /* Allocate a buffer memory. */
  status = sn9c10x_init_2 (dev);
  if (status !=SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sn9c10x_initi_2, ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  sn9c10x_close (dev);

  DBG (DBG_info, "attach_vidcam: opening USB device %s\n", devicename);

  /* Build list of vidcam supported resolutions. */
  DBG (DBG_proc, "attach_vidcam: build resolution list\n");

  if (dev->hw->color_adjust[0].resolution_x != 0)
    {
      int num_entries;
      int i;
      num_entries = 0;

      while (dev->hw->color_adjust[num_entries].resolution_x != 0)
	num_entries++;

      dev->resolutions_list = malloc (sizeof (SANE_Word) * (num_entries + 1));

      if (dev->resolutions_list == NULL)
	{
	  DBG (DBG_error,
	       "ERROR: attach_vidcam: vidcam resolution list failed\n");
	  sn9c10x_free (dev);
	  return SANE_STATUS_NO_MEM;
	}
      /* for CIF or VGA sensor different resolutions ? */
      if (dev->CIF)
	num_entries = 4;
      if (dev->VGA)
	num_entries = 5;
      dev->resolutions_list[0] = num_entries;
      DBG (DBG_proc, "attach_vidcam: make color resolution table \n");
      for (i = 0; i < num_entries; i++)
	{
	  dev->resolutions_list[i + 1] =
	    dev->hw->color_adjust[i].resolution_x;
	}
    }
  else
    {
      dev->resolutions_list = NULL;
    }

  /* Set the default options for that vidcam. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = dev->hw->vendor_name;
  dev->sane.model = dev->hw->product_name;
  dev->sane.type = SANE_I18N ("vidcam/webcam");

  /* Link the vidcam with the others. */
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    {
      *devp = dev;
    }

  num_devices++;

  DBG (DBG_proc, "attach_vidcam: exit\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one (const char *dev)
{
  DBG (DBG_proc, "attach_one: open \n");
  attach_vidcam (dev, NULL);
  DBG (DBG_proc, "attach_one: exit \n");
  return SANE_STATUS_GOOD;
}

/* Reset the options for that vidcam. */
static void
sn9c10x_init_options (Sn9c10x_Vidcam * dev)
{
  int i;

  DBG (DBG_proc, "sn9c10x_init_options: open\n");

  /* Pre-initialize the options. */
  memset (dev->opt, 0, sizeof (dev->opt));
  memset (dev->val, 0, sizeof (dev->val));

  for (i = 0; i < OPT_NUM_OPTIONS; ++i)
    {
      dev->opt[i].size = sizeof (SANE_Word);
      dev->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
  DBG (DBG_proc,
       "sn9c10x_init_options: done loop opt_num_options=%d, i=%d \n",
       OPT_NUM_OPTIONS, i);
  /* Number of options. */
  dev->opt[OPT_NUM_OPTS].name = "";
  dev->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  dev->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  dev->val[OPT_NUM_OPTS].w = OPT_NUM_OPTIONS;

  /* Mode group */
  dev->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
  dev->opt[OPT_MODE_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_MODE_GROUP].cap = 0;
  dev->opt[OPT_MODE_GROUP].size = 0;
  dev->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Vidcam supported modes */
  dev->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  dev->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  dev->opt[OPT_MODE].type = SANE_TYPE_STRING;
  dev->opt[OPT_MODE].size = max_string_size (scan_mode_list);
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].constraint.string_list = scan_mode_list;
  dev->val[OPT_MODE].s = (SANE_Char *) strdup ("");	/* will be set later */

  /* X and Y resolution */
  dev->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  dev->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[1];
  /* dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;  */

  /* brightness   */
  dev->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  dev->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  dev->val[OPT_BRIGHTNESS].w = 0;	/* to get middle value */
  
  /* contrast     */
  dev->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  dev->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  dev->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  dev->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
  dev->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  dev->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_CONTRAST].constraint.range = &contrast_range;
  dev->val[OPT_CONTRAST].w = 80;	/* to get middle value */

  /* hue          */
  dev->opt[OPT_HUE].name = SANE_NAME_HUE;
  dev->opt[OPT_HUE].title = SANE_TITLE_HUE;
  dev->opt[OPT_HUE].desc = SANE_DESC_HUE;
  dev->opt[OPT_HUE].type = SANE_TYPE_INT;
  dev->opt[OPT_HUE].unit = SANE_UNIT_NONE;
  dev->opt[OPT_HUE].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_HUE].constraint.range = &hue_range;
  dev->val[OPT_HUE].w = 0x08;	/* to get middle value */

  /* Enhancement group */
  dev->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  dev->opt[OPT_ENHANCEMENT_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* red level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_R].name = SANE_NAME_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].title = SANE_TITLE_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].desc = SANE_DESC_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint.range = &red_level_range;
  dev->val[OPT_WHITE_LEVEL_R].w = 00;	/* to get middle value */

  /* green level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_G].name = SANE_NAME_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].title = SANE_TITLE_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].desc = SANE_DESC_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_G].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint.range = &green_level_range;
  dev->val[OPT_WHITE_LEVEL_G].w = 00;	/* to get middle value */

  /* blue level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_B].name = SANE_NAME_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].title = SANE_TITLE_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].desc = SANE_DESC_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_B].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint.range = &blue_level_range;
  dev->val[OPT_WHITE_LEVEL_B].w = 00;	/* to get middle value */

  /* Text line option */
  dev->opt[OPT_TEXT].name = "text";
  dev->opt[OPT_TEXT].title = SANE_I18N ("Text");
  dev->opt[OPT_TEXT].desc = SANE_I18N ("Text");
  dev->opt[OPT_TEXT].type = SANE_TYPE_STRING;
  dev->opt[OPT_TEXT].size = max_string_size (text_list);
  dev->opt[OPT_TEXT].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_TEXT].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_TEXT].constraint.string_list = text_list;
  dev->val[OPT_TEXT].s = strdup (text_list[0]);

  /* Decomp option */
  dev->opt[OPT_DECOMP].name = "decomp";
  dev->opt[OPT_DECOMP].title = SANE_I18N ("Decomp");
  dev->opt[OPT_DECOMP].desc = SANE_I18N ("Decomp");
  dev->opt[OPT_DECOMP].type = SANE_TYPE_STRING;
  dev->opt[OPT_DECOMP].size = max_string_size (decomp_list);
  dev->opt[OPT_DECOMP].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_DECOMP].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_DECOMP].constraint.string_list = decomp_list;
  dev->val[OPT_DECOMP].s = strdup (decomp_list[0]);

 /*  Read format option */
  dev->opt[OPT_READ_FORMAT].name = "read_format";
  dev->opt[OPT_READ_FORMAT].title = SANE_I18N ("Read_format");
  dev->opt[OPT_READ_FORMAT].desc = SANE_I18N ("Read_format");
  dev->opt[OPT_READ_FORMAT].type = SANE_TYPE_STRING;
  dev->opt[OPT_READ_FORMAT].size = max_string_size (read_format_list);
  dev->opt[OPT_READ_FORMAT].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_READ_FORMAT].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_READ_FORMAT].constraint.string_list = read_format_list;
  dev->val[OPT_READ_FORMAT].s = strdup (read_format_list[0]);


  DBG (DBG_proc, "sn9c10x_init_options: after blue level\n");

  /* Lastly, set the default scan mode. This might change some
   * values previously set here. */

  sane_control_option (dev, OPT_MODE, SANE_ACTION_SET_VALUE,
		       (SANE_String_Const *) scan_mode_list[0], NULL);
  DBG (DBG_proc, "sn9c10x_init_options: exit\n");
}

/* Read the image from the vidcam and fill the temporary buffer with it. */
static SANE_Status
sn9c10x_fill_image (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  size_t size;
  size_t bulk_size_read;

  assert (dev->image_begin == dev->image_end);
  assert (dev->real_bytes_left > 0);

  DBG (DBG_proc, "sn9c10x_fill_image: enter: real dev bytes left=0x%lx \n",
       (unsigned long) (size_t) dev->real_bytes_left);
  bulk_size_read = dev->real_bytes_left;
  
  usleep (5000);

  while (dev->real_bytes_left)
    {
      /* Try to read the maximum number of bytes. */
      DBG (DBG_proc,
	   "sn9c10x_fill_image: real dev bytes left, while loop=0x%lx \n",
	   (unsigned long) (size_t) dev->real_bytes_left);

      size = dev->real_bytes_left;
      if (size < bulk_size_read)
	{
	  size = bulk_size_read;	/* it seems size can not be smaller then read by bulk */
	}
      if (size == 0)
	{
	  /* Probably reached the end of the buffer. Check, just in case. */
	  assert (dev->image_end != 0);
	  return (SANE_STATUS_GOOD);
	}

      /* Do the transfer */

      DBG (DBG_proc,
	   "sn9c10x_fill_image: dev->real_bytes_left: 0x%lx size: 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left, (unsigned long) (size_t) size);
      usleep (2000);
      /* urb 44 first read bulk */

      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	{
	  return status;
	}

      DBG (DBG_info,
	   "sn9c10x_fill_image: size (read) = 0x%lx bytes (bpl=0x%lx)\n",
	   (unsigned long) (size_t) size, (unsigned long) (size_t) dev->params.bytes_per_line);

      memcpy (dev->image + dev->image_end, dev->buffer, size);
      
      /* Display first 0x40 bytes from frame */
      hexdump (DBG_proc, "sn9c10x_fill_image", dev->buffer, 0x40);
      
      dev->image_end += size;
      bulk_size_read = size;
      if (dev->real_bytes_left > size)
	dev->real_bytes_left -= size;
      else if (dev->real_bytes_left <= size)	/* last loop */
	dev->real_bytes_left = 0;
      DBG (DBG_info, "sn9c10x_fill_image: real bytes left = 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left);
    }

  DBG (DBG_proc, "sn9c10x_fill_image: exit\n");
  return (SANE_STATUS_GOOD);	/* unreachable */
}

/* Read the image from the vidcam and fill the temporary buffer with it.
 *  this one is for testing the isochronous mode webcam
 *  has to be edit to insert more iso stuff.*/
static SANE_Status
sn9c10x_fill_image_iso (Sn9c10x_Vidcam * dev)
{
  SANE_Status status;
  size_t size;
  size_t bulk_size_read;

  assert (dev->image_begin == dev->image_end);
  assert (dev->real_bytes_left > 0);

  DBG (DBG_proc, "sn9c10x_fill_image: enter\n");

  DBG (DBG_proc, "sn9c10x_fill_image: real dev bytes left=0x%lx \n",
       (unsigned long) (size_t) dev->real_bytes_left);
  bulk_size_read = dev->real_bytes_left;

  while (dev->real_bytes_left)
    {
      /* Try to read the maximum number of bytes. */
      DBG (DBG_proc,
	   "sn9c10x_fill_image: real dev bytes left, while loop=0x%lx \n",
	   (unsigned long) (size_t) dev->real_bytes_left);

      size = dev->real_bytes_left;
      if (size < bulk_size_read)
	{
	  size = bulk_size_read;	/* it seems size can not be smaller then read by bulk */
	}
      if (size == 0)
	{
	  /* Probably reached the end of the buffer. Check, just in case. */
	  assert (dev->image_end != 0);
	  return (SANE_STATUS_GOOD);
	}

      /* Do the transfer */

      DBG (DBG_proc,
	   "sn9c10x_fill_image: dev->real_bytes_left: 0x%lx size: 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left, (unsigned long) (size_t) size);
      usleep (2000);
      /* urb 44 first read bulk */

      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	{
	  return status;
	}

      DBG (DBG_info,
	   "sn9c10x_fill_image: size (read) = 0x%lx bytes (bpl=0x%lx)\n",
	   (unsigned long) (size_t) size, (unsigned long) (size_t) dev->params.bytes_per_line);

      memcpy (dev->image + dev->image_end, dev->buffer, size);

      dev->image_end += size;
      bulk_size_read = size;
      if (dev->real_bytes_left > size)
	dev->real_bytes_left -= size;
      else if (dev->real_bytes_left <= size)	/* last loop */
	dev->real_bytes_left = 0;
      DBG (DBG_info, "sn9c10x_fill_image: real bytes left = 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left);
    }

  DBG (DBG_proc, "sn9c10x_fill_image: exit\n");
  return (SANE_STATUS_GOOD);	/* unreachable */
}



/**********************************************************************
*
* The add_text routine and font_6x11.h file are taken from the (GPLed) 
* webcam.c file, part of xawtv,   (c) 1998-2002 Gerd Knorr.
* add_text was slightly modified for the pencam2 program.
* sn9c10x_add_text was taken from the pencam2 program and changed on
* some points
*
*********************************************************************/

#define MSG_MAXLEN   45
#define CHAR_HEIGHT  11
#define CHAR_WIDTH   6
#define CHAR_START   4

static SANE_Status
sn9c10x_add_text (SANE_Byte * image, int width, int height, char *txt)
{
  SANE_Status status;
  time_t t;
  struct tm *tm;
  char line[MSG_MAXLEN + 1];
  SANE_Byte *ptr;
  int i, x, y, f, len;
  char fmtstring[25] = " %Y-%m-%d %H:%M:%S";
  char fmttxt[46];

  DBG (DBG_proc, "sn9c10x_add_text: enter\n");
  time (&t);
  tm = localtime (&t);
  if (strlen (txt) > (MSG_MAXLEN - 23))
    strncpy (fmttxt, txt, (MSG_MAXLEN - 23));
  else
    strcpy (fmttxt, txt);
  strcat (fmttxt, fmtstring);

  len = strftime (line, MSG_MAXLEN, fmttxt, tm);

  for (y = 0; y < CHAR_HEIGHT; y++)
    {
      ptr = image + 3 * width * (height - CHAR_HEIGHT - 2 + y) + 12;

      for (x = 0; x < len; x++)
	{
	  f = fontdata[line[x] * CHAR_HEIGHT + y];
	  for (i = CHAR_WIDTH - 1; i >= 0; i--)
	    {
	      if (f & (CHAR_START << i))
		{
		  ptr[0] = 255;
		  ptr[1] = 255;
		  ptr[2] = 255;
		}
	      ptr += 3;
	    }			/* for i */
	}			/* for x */
    }				/* for y */

  DBG (DBG_proc, "sn9c10x_add_text: exit vw=%d, vh=%d\n", width, height);
  status = (SANE_STATUS_GOOD);
  return status;
}				/*  add_text end */

/* **************************  Video Decoding  *********************  */
static SANE_Status
sn9c10x_bayer_unshuffle (Sn9c10x_Vidcam * dev, SANE_Byte * buf, size_t * len)
{
  SANE_Status status;
  size_t size;
  long int x, y, z;
  unsigned int i = 0;
  int RED, GREEN, BLUE;
  int vw = dev->x_resolution;
  int vh = dev->y_resolution;
  int bright_red;
  int bright_green;
  int bright_blue;
  long int count;

  RED = dev->red;
  GREEN = dev->green;
  BLUE = dev->blue;

  DBG (DBG_proc, "sn9c10x_bayer_unshuffle: enter\n");

  DBG (DBG_proc,
       "sn9c10x_bayer_unshuffle: color read RED=%d, GREEN=%d, BLUE=%d\n", RED,
       GREEN, BLUE);
  vw = dev->x_resolution;
  vh = dev->y_resolution;

  DBG (DBG_proc, "sn9c10x_bayer_unshuffle: vw=%d, vh=%d, len=0x%lx\n",
       vw, vh, (unsigned long) (size_t) len);

  /* memset (dev->output, 0, 3 * vw * vh);       clear output matrix. */


/*--------------------------------------------------------------------*/
/*
** this code comes from snx101util-0.2/sonix.c
 and is patched*/
/*
 * Image data format is a raw RGB bayer pattern.
 * BGBGBG...
 * GRGRGR...
 */
  size = 0x40; /* show 40 bytes header */
  hexdump (DBG_proc, "sn9c10x_bayer_unshuffle 40 bytes", dev->image, size);
 /* 
  size = vw * vh * 3 + (dev->header_length - (dev->header_length - 0x10));
  for (i = 0; i < size; i++)
    *(dev->image + i) = *(dev->image + i + 0x10);
*/
  DBG (DBG_proc, "sn9c10x_bayer_unshuffle: remove header done\n");

  if (dev->scan_mode == SN9C10X_COLOR_RGB_USB_ISO)
    {
      size = (vw * vh);
     memcpy (dev->output, dev->image, size);

      DBG (DBG_proc, "sn9c10x_bayer_unshuffle: copy done from buffer image to output\n");
    }

  if ((dev->scan_mode == SN9C10X_COLOR_RGB) || (dev->scan_mode == SN9C10X_COLOR) || (dev->scan_mode == SN9C10X_COLOR_RGB_TEXT))
    {
      size = (vw * vh);
      for (i = 0; i < size; i++)
	{
	  if ((i / vw) % 2 == 0)
	    {
	      if ((i % 2) == 0)
		{
		  /* B */
		  if ((i > vw) && ((i % vw) > 0))
		    {
		      *(dev->output++) = (*(dev->image - vw - 1) + *(dev->image - vw + 1) + *(dev->image + vw - 1) + *(dev->image + vw + 1)) / 4;	/* R */
		      *(dev->output++) = (*(dev->image - 1) + *(dev->image + 1) + *(dev->image + vw) + *(dev->image - vw)) / 4;	/* G */
		      *(dev->output++) = *dev->image;	/* B */
		    }
		  else
		    {
		      /* first line or left column */
		      *(dev->output++) = *(dev->image + vw + 1);	/* R */
		      *(dev->output++) = (*(dev->image + 1) + *(dev->image + vw)) / 2;	/* G */
		      *(dev->output++) = *dev->image;	/* B */
		    }
		}
	      else
		{
		  /* (B)G */
		  if ((i > vw) && ((i % vw) < (vw - 1)))
		    {
		      *(dev->output++) = (*(dev->image + vw) + *(dev->image - vw)) / 2;	/* R */
		      *(dev->output++) = *dev->image;	/* G */
		      *(dev->output++) = (*(dev->image - 1) + *(dev->image + 1)) / 2;	/* B */
		    }
		  else
		    {
		      /* first line or right column */
		      *(dev->output++) = *(dev->image + vw);	/* R */
		      *(dev->output++) = *dev->image;	/* G */
		      *(dev->output++) = *(dev->image - 1);	/* B */
		    }
		}
	    }
	  else
	    {
	      if ((i % 2) == 0)
		{
		  /* G(R) */
		  if ((i < (vw * (vh - 1))) && ((i % vw) > 0))
		    {
		      *(dev->output++) = (*(dev->image - 1) + *(dev->image + 1)) / 2;	/* R */
		      *(dev->output++) = *dev->image;	/* G */
		      *(dev->output++) = (*(dev->image + vw) + *(dev->image - vw)) / 2;	/* B */
		    }
		  else
		    {
		      /* bottom line or left column */
		      *(dev->output++) = *(dev->image + 1);	/* R */
		      *(dev->output++) = *dev->image;	/* G */
		      *(dev->output++) = *(dev->image - vw);	/* B */
		    }
		}
	      else
		{
		  /* R */
		  if (i < (vw * (vh - 1)) && ((i % vw) < (vw - 1)))
		    {
		      *(dev->output++) = *dev->image;	/* R */
		      *(dev->output++) = (*(dev->image - 1) + *(dev->image + 1) + *(dev->image - vw) + *(dev->image + vw)) / 4;	/* G */
		      *(dev->output++) = (*(dev->image - vw - 1) + *(dev->image - vw + 1) + *(dev->image + vw - 1) + *(dev->image + vw + 1)) / 4;	/* B */
		    }
		  else
		    {
		      /* bottom line or right column */
		      *(dev->output++) = *dev->image;	/* R */
		      *(dev->output++) = (*(dev->image - 1) + *(dev->image - vw)) / 2;	/* G */
		      *(dev->output++) = *(dev->image - vw - 1);	/* B */
		    }
		}
	    }
	  dev->image++;
	}

      DBG (DBG_proc, "sn9c10x_bayer_unshuffle: RGB swap sn9c10x done\n");

    }
 if (dev->scan_mode == SN9C10X_COLOR_RGB_TEXT)
    {


  /* brightness adjustment */

  bright_red = (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_R].w);
  bright_green =
    (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_G].w);
  bright_blue =
    (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_B].w);

      DBG (DBG_proc, "sn9c10x_bayer_unshuffle: read bright value sn9c10x done\n");
  
  count = vw * vh * 3;
  x = 0;
  while (x < count)
    {
      if ((*(dev->output + x) + bright_red) >= 255)
	*(buf + x) = 255;

      else if ((*(dev->output + x) + bright_red) <= 0)
	*(buf + x) = 0;
      else
	*(buf + x) = (*(dev->output + x) + bright_red);

	x++;

    if ((*(dev->output + x) + bright_green) >= 255)
	*(buf + x) = 255;

      else if ((*(dev->output + x) + bright_green) <= 0)
	*(buf + x) = 0;
      else
	*(buf + x) = (*(dev->output + x) + bright_green);

      x++;

      if ((*(dev->output + x) + bright_blue) >= 255)
	*(buf + x) = 255;

      else if ((*(dev->output + x) + bright_blue) <= 0)
	*(buf + x) = 0;
      else
	*(buf + x) =(*(dev->output + x) + bright_blue);

      x++;

    DBG (DBG_proc, "sn9c10x_bayer_unshuffle: brightness x=%x, count=%x\n", x, count);
    }
    DBG (DBG_proc, "sn9c10x_bayer_unshuffle: brightness done\n");
}
  /* insert add text routine                 */
/**********************************************************************
*
* The add_text routine and font_6x11.h file are taken from the (GPLed) 
* webcam.c file, part of xawtv,   (c) 1998-2002 Gerd Knorr.
* add_text was slightly modified for this program.
*
*********************************************************************/
  if (dev->scan_mode == SN9C10X_COLOR_RGB_TEXT)
    {

      strcpy (dev->picmsg_ps, "Sonixcam ");

      status = sn9c10x_add_text (buf, vw, vh, dev->picmsg_ps);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sn9c10x_bayer_unshuffle status NOK\n");
	  return (status);
	}
      DBG (DBG_proc, "sn9c10x_bayer_unshuffle: text added done\n");
    }

  DBG (DBG_proc, "sn9c10x_bayer_unshuffle: exit vw=%d, vh=%d\n", vw, vh);
  status = (SANE_STATUS_GOOD);
  return status;
}

/* end routines from the decoding image routine bayer_unshuffle *********/

/* Sane entry points */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  FILE *fp;
  char line[PATH_MAX];
  size_t len;

  DBG_INIT ();

  DBG (DBG_sane_init, "sane_init\n");

  authorize = authorize;	/* silence gcc */

  DBG (DBG_error, "This is sane-sn9c10x version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2005-2007 by Gerard Klaver\n");

  if (version_code)
    {
      *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, BUILD);
    }

  sanei_usb_init ();

  fp = sanei_config_open (SN9C10X_CONFIG_FILE);
  if (!fp)
    {
      /* No default vidcam? */
      DBG (DBG_warning, "configuration file not found (%s)\n",
	   SN9C10X_CONFIG_FILE);

      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (line, sizeof (line), fp))
    {
      SANE_Word vendor;
      SANE_Word product;

      if (line[0] == '#')	/* ignore line comments */
	continue;
      len = strlen (line);

      if (!len)
	continue;		/* ignore empty lines */
      if (sscanf (line, "usb %i %i", &vendor, &product) == 2)
	{

	  sanei_usb_attach_matching_devices (line, attach_one);
	}
      else
	{
	  /* Garbage. Ignore. */
	  DBG (DBG_warning, "bad configuration line: \"%s\" - ignoring.\n",
	       line);
	}
    }

  fclose (fp);

  DBG (DBG_proc, "sane_init: leave\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  Sn9c10x_Vidcam *dev;
  int i;

  DBG (DBG_proc, "sane_get_devices: enter\n");

  local_only = local_only;	/* silence gcc */

  if (devlist)
    free (devlist);

  devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
  if (!devlist)
    return SANE_STATUS_NO_MEM;

  i = 0;
  for (dev = first_dev; i < num_devices; dev = dev->next)
    devlist[i++] = &dev->sane;
  devlist[i++] = 0;

  *device_list = devlist;

  DBG (DBG_proc, "sane_get_devices: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Sn9c10x_Vidcam *dev;
  SANE_Status status;

  DBG (DBG_proc, "sane_open: enter\n");

  /* search for devicename */
  if (devicename[0])
    {
      DBG (DBG_info, "sane_open: devicename=%s\n", devicename);

      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devicename) == 0)
	    {
	      break;
	    }
	}

      if (!dev)
	{
	  status = attach_vidcam (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return status;
	    }
	}
    }
  else
    {
      DBG (DBG_sane_info, "sane_open: no devicename, opening first device\n");
      dev = first_dev;		/* empty devicename -> use first device */
    }

  if (!dev)
    {
      DBG (DBG_error, "No vidcam found\n");

      return SANE_STATUS_INVAL;
    }

  sn9c10x_init_options (dev);

  *handle = dev;

  DBG (DBG_proc, "sane_open: exit\n");

  return SANE_STATUS_GOOD;
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Sn9c10x_Vidcam *dev = handle;

  DBG (DBG_proc, "sane_get_option_descriptor: enter, option %d\n", option);

  if ((unsigned) option >= OPT_NUM_OPTIONS)
    {
      return NULL;
    }

  DBG (DBG_proc, "sane_get_option_descriptor: exit\n");

  return dev->opt + option;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  Sn9c10x_Vidcam *dev = handle;
  SANE_Status status;
  SANE_Word cap;

  DBG (DBG_proc, "sane_control_option: enter, option %d, action %d\n",
       option, action);

  if (info)
    {
      *info = 0;
    }

  if (dev->scanning)
    {
      return SANE_STATUS_DEVICE_BUSY;
    }

  if (option < 0 || option >= OPT_NUM_OPTIONS)
    {
      return SANE_STATUS_INVAL;
    }

  cap = dev->opt[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      return SANE_STATUS_INVAL;
    }

  if (action == SANE_ACTION_GET_VALUE)
    {

      switch (option)
	{
	  /* word options */
	case OPT_NUM_OPTS:
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_HUE:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;
	case OPT_MODE:
	case OPT_TEXT:
	case OPT_DECOMP:
	case OPT_READ_FORMAT:
	  strcpy (val, dev->val[option].s);
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (DBG_error, "could not set option, not settable\n");
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (dev->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "could not set option, invalid value\n");
	  return status;
	}

      switch (option)
	{

	  /* Numeric side-effect options */
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_HUE:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

        /* String side-effect options */
	case OPT_TEXT:
	  free (dev->val[option].s);
	  dev->val[option].s = (SANE_String) strdup (val);
	  return SANE_STATUS_GOOD;
	case OPT_DECOMP:
	  free (dev->val[option].s);
	  dev->val[option].s = (SANE_String) strdup (val);
	  return SANE_STATUS_GOOD;
	case OPT_READ_FORMAT:
	  free (dev->val[option].s);
	  dev->val[option].s = (SANE_String) strdup (val);
	  return SANE_STATUS_GOOD;
	  /* String side-effect options */
	case OPT_MODE:
	  if (strcmp (dev->val[option].s, val) == 0)
	    return SANE_STATUS_GOOD;

	  free (dev->val[OPT_MODE].s);
	  dev->val[OPT_MODE].s = (SANE_Char *) strdup (val);

	  dev->opt[OPT_TEXT].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_DECOMP].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_READ_FORMAT].cap |= SANE_CAP_INACTIVE;


	  dev->opt[OPT_WHITE_LEVEL_R].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_G].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_B].cap |= SANE_CAP_INACTIVE;

	  if (strcmp (dev->val[OPT_MODE].s, COLOR_RAW_STR) == 0)
	    {
	    dev->scan_mode = SN9C10X_COLOR_RAW;
	    dev->depth = 8;
	    dev->opt[OPT_DECOMP].cap &= ~SANE_CAP_INACTIVE;
	    dev->opt[OPT_READ_FORMAT].cap &= ~SANE_CAP_INACTIVE;
            }
	  else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_STR) == 0)
	    {
	    dev->scan_mode = SN9C10X_COLOR_RGB;
            dev->depth = 8;
	      dev->opt[OPT_TEXT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_DECOMP].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_READ_FORMAT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;

	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR)
		   == 0)
	    {
	    dev->scan_mode = SN9C10X_COLOR;
	    dev->depth = 8;
	      dev->opt[OPT_TEXT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_DECOMP].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_READ_FORMAT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
}
	  else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_TEXT_STR) == 0)
{
dev->scan_mode = SN9C10X_COLOR_RGB_TEXT;
	    dev->depth = 8;
	      dev->opt[OPT_TEXT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_DECOMP].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_READ_FORMAT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
}
	  else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_USB_ISO_STR) == 0)
{
dev->scan_mode = SN9C10X_COLOR_RGB_USB_ISO;
	    dev->depth = 8;
	      dev->opt[OPT_TEXT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_DECOMP].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_READ_FORMAT].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
}

	  /* The SN9C10X supports only a handful of resolution. */
	  /* This the default resolution range for the SN9C10X */

	  dev->depth = 8;
	  if (dev->resolutions_list != NULL)
	    {
	      int i;

	      dev->opt[OPT_RESOLUTION].constraint_type =
		SANE_CONSTRAINT_WORD_LIST;
	      dev->opt[OPT_RESOLUTION].constraint.word_list =
		dev->resolutions_list;

	      /* If the resolution isn't in the list, set a default. */
	      for (i = 1; i <= dev->resolutions_list[0]; i++)
		{
		  if (dev->resolutions_list[i] >= dev->val[OPT_RESOLUTION].w)
		    break;
		}
	      if (i > dev->resolutions_list[0])
		{
		  /* Too big. Take lowest. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[1];
		}
	      else
		{
		  /* Take immediate superioir value. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[i];
		}
	    }

	  /* String side-effect options */

	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }

  DBG (DBG_proc, "sane_control_option: exit, bad\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Sn9c10x_Vidcam *dev = handle;
  int i;

  DBG (DBG_proc, "sane_get_parameters: enter\n");

  if (!(dev->scanning))
    {
      dev->x_resolution = dev->val[OPT_RESOLUTION].w;
      /* Prepare the parameters for the caller. */
      memset (&dev->params, 0, sizeof (SANE_Parameters));

      dev->params.last_frame = SANE_TRUE;

      switch (dev->scan_mode)
	{
	case SN9C10X_COLOR_RGB_TEXT:
	case SN9C10X_COLOR_RGB_USB_ISO:
	case SN9C10X_COLOR_RGB:
	case SN9C10X_COLOR:
          dev->params.format = SANE_FRAME_RGB;
	  dev->bytes_pixel = 3;
	  break;
        case SN9C10X_COLOR_RAW:
	  dev->bytes_pixel = 1;  /* raw image code is ?, 1 byte/pixel */
          dev->params.format = SANE_FRAME_GRAY;
	  break;
  
	}
            if (dev->resolutions_list != NULL)
	{
	  /* This vidcam has a fixed number of supported
	   * resolutions. Find the color sequence for that
	   * resolution. */

	  for (i = 0;
	       dev->hw->color_adjust[i].resolution_x != dev->x_resolution;
	       i++);

	  dev->red = dev->hw->color_adjust[i].z1_color_0;
	  dev->green = dev->hw->color_adjust[i].z1_color_1;
	  dev->blue = dev->hw->color_adjust[i].z1_color_2;
	  dev->y_resolution = dev->hw->color_adjust[i].resolution_y;
	}
      switch (dev->val[OPT_RESOLUTION].w)
	{
	case 160:
	  dev->video_mode = 0x20;  /* sub-sampling */
	  dev->cwidth = dev->x_resolution;
	  dev->cheight = dev->y_resolution;
	  break;
	case 176:
	  dev->video_mode = 0x10;
	  dev->cwidth = dev->x_resolution;
	  dev->cheight = dev->y_resolution;
	  break;
	case 320:
	  dev->video_mode = 0x10;
	  dev->cwidth = dev->x_resolution;
	  dev->cheight = dev->y_resolution;
	  break;
	case 352:
	  dev->video_mode = 0x00;
	  dev->cwidth = dev->x_resolution;
	  dev->cheight = dev->y_resolution;
	  break;
	case 640:
	  dev->video_mode = 0x00;
	  dev->cwidth = dev->x_resolution;
	  dev->cheight = dev->y_resolution;
	  break;
	}
      dev->params.depth = 8;
      dev->params.pixels_per_line = dev->x_resolution;
      dev->params.lines = dev->y_resolution;
      dev->params.bytes_per_line =
	dev->params.pixels_per_line * dev->bytes_pixel;

      DBG (DBG_info, "sane_get_parameters: x=%d, y=%d\n", dev->x_resolution,
	   dev->y_resolution);
    }

  /* Return the current values. */
  if (params)
    {
      *params = (dev->params);
    }

  DBG (DBG_proc, "sane_get_parameters: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Sn9c10x_Vidcam *dev = handle;
  SANE_Status status;
  DBG (DBG_proc, "sane_start: enter\n");

  if (!(dev->scanning))
    {
      sane_get_parameters (dev, NULL);

      /* Open again the vidcam  */
      if (sanei_usb_open (dev->devicename, &(dev->fd)) != 0)
	{
	  DBG (DBG_error, "ERROR: sane_start: open failed\n");
	  return SANE_STATUS_INVAL;
	}

      /* Initialize the vidcam. */
      status = sn9c10x_vidcam_init (dev);
      if (status)
	{
	  DBG (DBG_error, "ERROR: failed to init the vidcam\n");
	  sn9c10x_close (dev);
	  return status;
	}
    }

  dev->image_end = 0;
  dev->image_begin = 0;
  dev->header_length = 0x40;
  /* real_byte_left is bulk read bytes, bytes_left is frontend buffer bytes */
  dev->real_bytes_left =
    dev->cwidth * dev->cheight + dev->header_length;
  dev->bytes_left =
    dev->params.bytes_per_line * dev->params.lines;

  dev->scanning = SANE_TRUE;

  DBG (DBG_proc, "sane_start: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  SANE_Status status;
  Sn9c10x_Vidcam *dev = handle;
  size_t size;
  size_t size_h;
  int bright_raw;
  int count;
  int x;
  int vw = dev->x_resolution;
  int vh = dev->y_resolution;

  DBG (DBG_proc, "sane_read: enter\n");

  *len = 0;

  if (dev->deliver_eof)
    {
      dev->deliver_eof = 0;
      return SANE_STATUS_EOF;
    }

  if (!(dev->scanning))
    {
      /* OOPS, not scanning, stop a scan. */
      sn9c10x_reset_vidcam (dev);
      sn9c10x_close (dev);
      dev->scanning = SANE_FALSE;
      return SANE_STATUS_CANCELLED;
    }

  if (dev->bytes_left <= 0)
    {
      return (SANE_STATUS_EOF);
    }

  if (dev->image_begin == dev->image_end)
    {
      if (dev->scan_mode != SN9C10X_COLOR_RGB_USB_ISO)
	{
	  /* Fill image, use bulk mode read */
	  status = sn9c10x_fill_image (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_info, "sane_read: sn9c10x_fill_image status NOK\n");
	      return (status);
	    }
	}
      else
	{
	  /* Fill image use isochronous mode (under development) */
	  status = sn9c10x_fill_image_iso (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_info,
		   "sane_read: sn9c10x_fill_image_iso status NOK\n");
	      return (status);
	    }
	}
    }

  /* Something must have been read */
  if (dev->image_begin == dev->image_end)
    {
      DBG (DBG_info, "sane_read: nothing read\n");
      return SANE_STATUS_IO_ERROR;
    }

  size = dev->bytes_left;
  if (((unsigned int) max_len) < size)
    {
      DBG (DBG_error, "sane_read: max_len < size\n");
      return (SANE_FALSE);
    }
  if ((dev->image_end - dev->image_begin) > size)
    {
      size = dev->image_end - dev->image_begin;
      DBG (DBG_proc, "sane_read: size < dev->image_end - dev->image_begin\n");
    }
  DBG (DBG_info, "sane_read: size =0x%lx bytes, max_len=0x%lx bytes\n",
       (unsigned long) (size_t) size, (unsigned long) (size_t) max_len);
  
  *len = dev->bytes_left;	/* needed */
  size = dev->bytes_left;
  dev->bytes_left = 0;	        /* needed for frontend or ? */


  if (dev->scan_mode != SN9C10X_COLOR_RAW)
    {
      /* do bayer unshuffle  after complete frame is read */
      status = sn9c10x_bayer_unshuffle (dev, buf, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sane_read: sn9c10x_bayer_unshuffle status NOK\n");
	  return (status);
	}
    }
  else
    {
      /* Copy the raw data to the frontend buffer. */

    size_h = 0x30;			/* show 30 bytes header + begin */
    hexdump (DBG_proc, "sane_read show raw 0x30 bytes header",
	   dev->image, size_h);

/* brightness adjustment, 0x0C is removing header */

  size = vw * vh + 0x0C;

  bright_raw = (dev->val[OPT_BRIGHTNESS].w);

  for (x = 0; x < size; x++)
    {
      if ((*(dev->image + x +0x0C) + bright_raw) >= 255)
	*(buf + x) = 255;

      else if ((*(dev->image + x + 0x0C)+ bright_raw) <= 0)
	*(buf + x) = 0;
      else
	*(buf + x) = (*(dev->image + x + 0x0C) + bright_raw);
    }

/*      memcpy (buf, dev->image, size); */
      size_h = 0x40;
      hexdump (DBG_proc, "sane_read show raw 0x40 bytes header", buf, size_h);
      /* *len = size; */
      DBG (DBG_info, "sane_read: raw mode\n");
    }
  DBG (DBG_info, "sane_read: exit\n");

  return (SANE_STATUS_GOOD);
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{

  DBG (DBG_proc, "sane_set_io_mode: enter\n");

  handle = handle;		/* silence gcc */
  non_blocking = non_blocking;	/* silence gcc */


  DBG (DBG_proc, "sane_set_io_mode: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG (DBG_proc, "sane_get_select_fd: enter\n");

  handle = handle;		/* silence gcc */
  fd = fd;			/* silence gcc */

  DBG (DBG_proc, "sane_get_select_fd: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_cancel (SANE_Handle handle)
{
  Sn9c10x_Vidcam *dev = handle;

  DBG (DBG_proc, "sane_cancel: enter\n");

  /* Stop a scan. */
  if (dev->scanning == SANE_TRUE)
    {
      /* Reset the vidcam */
      sn9c10x_reset_vidcam (dev);
      sn9c10x_close (dev);
    }
  dev->scanning = SANE_FALSE;

  /* return SANE_STATUS_CANCELLED; */
  DBG (DBG_proc, "sane_cancel: exit\n");
}

void
sane_close (SANE_Handle handle)
{
  Sn9c10x_Vidcam *dev = handle;
  Sn9c10x_Vidcam *dev_tmp;

  DBG (DBG_proc, "sane_close: enter\n");

/* Stop a scan. */

  if (dev->scanning == SANE_TRUE)
    {
      sn9c10x_reset_vidcam (dev);
      sn9c10x_close (dev);
    }
  dev->scanning = SANE_FALSE;

  /* Unlink dev. */
  if (first_dev == dev)
    {
      first_dev = dev->next;
    }
  else
    {
      dev_tmp = first_dev;
      while (dev_tmp->next && dev_tmp->next != dev)
	{
	  dev_tmp = dev_tmp->next;
	}
      if (dev_tmp->next != NULL)
	{
	  dev_tmp->next = dev_tmp->next->next;
	}
    }

  sn9c10x_free (dev);
  num_devices--;

  DBG (DBG_proc, "sane_close: exit\n");
}

void
sane_exit (void)
{
  DBG (DBG_proc, "sane_exit: enter\n");

  while (first_dev)
    {
      sane_close (first_dev);
    }

  if (devlist)
    {
      free (devlist);
      devlist = NULL;
    }

  DBG (DBG_proc, "sane_exit: exit\n");
}
