/* ========================================================================= */
/*
   SANE - Scanner Access Now Easy.
   coolscan2.c , version 0.1.8

   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.

   This file implements a SANE backend for Nikon Coolscan film scanners.

   Written by András Major (andras@users.sourceforge.net), 2001-2002.

   The developers wish to express their thanks to Nikon Corporation
   for providing technical information and thus making this backend
   possible.
*/
/* ========================================================================= */


/* ========================================================================= */
/*
   Revision log:

   0.1.8, 27/09/2002, andras: added subframe and load options
   0.1.7, 22/08/2002, andras: added exposure correction option
                                and hack for LS-40 IR readout
   0.1.6, 14/06/2002, andras: types etc. fixed, fixes for LS-8000
   0.1.5, 26/04/2002, andras: lots of minor fixes related to saned
   0.1.4, 22/04/2002, andras: first version to be included in SANE CVS

*/
/* ========================================================================= */

#ifdef _AIX
# include "../include/lalloca.h"	/* MUST come first for AIX! */
#endif
#include "../include/sane/config.h"
#include "../include/lalloca.h"

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
/*
#include <limits.h>
#include <sys/types.h>
*/

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_scsi.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_debug.h"
#include "../include/sane/sanei_config.h"
#define BACKEND_NAME coolscan2
#include "../include/sane/sanei_backend.h"	/* must be last */

#define CS2_VERSION_MAJOR 0
#define CS2_VERSION_MINOR 1
#define CS2_REVISION 8
#define CS2_CONFIG_FILE "coolscan2.conf"

#define WSIZE (sizeof (SANE_Word))

/*
#define CS2_BLEEDING_EDGE
*/


/* ========================================================================= */
/* typedefs */

typedef enum
{
  CS2_TYPE_UNKOWN,
  CS2_TYPE_LS30,
  CS2_TYPE_LS40,
  CS2_TYPE_LS2000,
  CS2_TYPE_LS4000,
  CS2_TYPE_LS8000
}
cs2_type_t;

typedef enum
{
  CS2_INTERFACE_UNKNOWN,
  CS2_INTERFACE_SCSI,		/* includes IEEE1394 via SBP2 */
  CS2_INTERFACE_USB
}
cs2_interface_t;

typedef enum
{
  CS2_PHASE_NONE = 0x00,
  CS2_PHASE_STATUS = 0x01,
  CS2_PHASE_OUT = 0x02,
  CS2_PHASE_IN = 0x03,
  CS2_PHASE_BUSY = 0x04
}
cs2_phase_t;

typedef enum
{
  CS2_SCAN_NORMAL,
  CS2_SCAN_AE,
  CS2_SCAN_AE_WB
}
cs2_scan_t;

typedef enum
{
  CS2_INFRARED_OFF,
  CS2_INFRARED_IN,
  CS2_INFRARED_OUT
}
cs2_infrared_t;

typedef enum
{
  CS2_STATUS_READY = 0,
  CS2_STATUS_BUSY = 1,
  CS2_STATUS_NO_DOCS = 2,
  CS2_STATUS_PROCESSING = 4,
  CS2_STATUS_ERROR = 8,
  CS2_STATUS_REISSUE = 16,
  CS2_STATUS_ALL = 31		/* sum of all others */
}
cs2_status_t;

typedef enum
{
  CS2_OPTION_NUM = 0,

  CS2_OPTION_PREVIEW,

  CS2_OPTION_NEGATIVE,

  CS2_OPTION_INFRARED,

  CS2_OPTION_DEPTH,

  CS2_OPTION_EXPOSURE,
  CS2_OPTION_EXPOSURE_R,
  CS2_OPTION_EXPOSURE_G,
  CS2_OPTION_EXPOSURE_B,
  CS2_OPTION_SCAN_AE,
  CS2_OPTION_SCAN_AE_WB,

  CS2_OPTION_LUT_R,
  CS2_OPTION_LUT_G,
  CS2_OPTION_LUT_B,

  CS2_OPTION_RES,
  CS2_OPTION_RESX,
  CS2_OPTION_RESY,
  CS2_OPTION_RES_INDEPENDENT,

  CS2_OPTION_PREVIEW_RESOLUTION,

  CS2_OPTION_FRAME,
  CS2_OPTION_SUBFRAME,
  CS2_OPTION_XMIN,
  CS2_OPTION_XMAX,
  CS2_OPTION_YMIN,
  CS2_OPTION_YMAX,

  CS2_OPTION_LOAD,
  CS2_OPTION_EJECT,
  CS2_OPTION_RESET,

  CS2_OPTION_FOCUS_ON_CENTRE,
  CS2_OPTION_FOCUS,
  CS2_OPTION_AUTOFOCUS,
  CS2_OPTION_FOCUSX,
  CS2_OPTION_FOCUSY,

  CS2_N_OPTIONS			/* must be last -- counts number of enum items */
}
cs2_option_t;

typedef unsigned int cs2_pixel_t;

typedef struct
{
  /* interface */
  cs2_interface_t interface;
  int fd;
  SANE_Byte *send_buf, *recv_buf;
  size_t send_buf_size, recv_buf_size;
  size_t n_cmd, n_send, n_recv;

  /* device characteristics */
  char vendor_string[9], product_string[17], revision_string[5];
  cs2_type_t type;
  int maxbits;
  unsigned int resx_optical, resx_min, resx_max, *resx_list, resx_n_list;
  unsigned int resy_optical, resy_min, resy_max, *resy_list, resy_n_list;
  unsigned long boundaryx, boundaryy;
  unsigned long frame_offset;
  unsigned int unit_dpi;
  double unit_mm;
  int n_frames;

  int focus_min, focus_max;

  /* settings */
  SANE_Bool preview, negative, infrared;
  int depth, real_depth, bytes_per_pixel, shift_bits, n_colour_in,
    n_colour_out;
  cs2_pixel_t n_lut;
  cs2_pixel_t *lut_r, *lut_g, *lut_b, *lut_neutral;
  unsigned long resx, resy, res, res_independent, res_preview;
  unsigned long xmin, xmax, ymin, ymax;
  int i_frame;
  double subframe;

  unsigned int real_resx, real_resy, real_pitchx, real_pitchy;
  unsigned long real_xoffset, real_yoffset, real_width, real_height,
    logical_width, logical_height;
  int odd_padding;

  double exposure, exposure_r, exposure_g, exposure_b;
  unsigned long real_exposure[10];

  SANE_Bool focus_on_centre;
  unsigned long focusx, focusy, real_focusx, real_focusy;
  int focus;

  /* status */
  SANE_Bool scanning;
  cs2_infrared_t infrared_stage, infrared_next;
  SANE_Byte *infrared_buf;
  size_t n_infrared_buf, infrared_index;
  SANE_Byte *line_buf;
  ssize_t n_line_buf, i_line_buf;
  unsigned long sense_key, sense_asc, sense_ascq, sense_info;
  unsigned long sense_code;
  cs2_status_t status;
  size_t xfer_position, xfer_bytes_total;

  /* SANE stuff */
  SANE_Option_Descriptor option_list[CS2_N_OPTIONS];
}
cs2_t;


/* ========================================================================= */
/* prototypes */

static SANE_Status cs2_open (const char *device, cs2_interface_t interface,
			     cs2_t ** sp);
static void cs2_close (cs2_t * s);
static SANE_Status cs2_attach (const char *dev);
static SANE_Status cs2_scsi_sense_handler (int fd, u_char * sense_buffer,
					   void *arg);
static SANE_Status cs2_parse_sense_data (cs2_t * s);
static void cs2_init_buffer (cs2_t * s);
static SANE_Status cs2_pack_byte (cs2_t * s, SANE_Byte byte);
static SANE_Status cs2_parse_cmd (cs2_t * s, char *text);
static SANE_Status cs2_grow_send_buffer (cs2_t * s);
static SANE_Status cs2_issue_cmd (cs2_t * s);
static cs2_phase_t cs2_phase_check (cs2_t * s);
static SANE_Status cs2_set_boundary (cs2_t *s);
static SANE_Status cs2_scanner_ready (cs2_t * s, int flags);
static SANE_Status cs2_page_inquiry (cs2_t * s, int page);
static SANE_Status cs2_full_inquiry (cs2_t * s);
static SANE_Status cs2_execute (cs2_t * s);
static SANE_Status cs2_load (cs2_t * s);
static SANE_Status cs2_eject (cs2_t * s);
static SANE_Status cs2_reset (cs2_t * s);
static SANE_Status cs2_focus (cs2_t * s);
static SANE_Status cs2_autofocus (cs2_t * s);
static SANE_Status cs2_get_exposure (cs2_t * s);
static SANE_Status cs2_convert_options (cs2_t * s);
static SANE_Status cs2_scan (cs2_t * s, cs2_scan_t type);
static void *cs2_xmalloc (size_t size);
static void *cs2_xrealloc (void *p, size_t size);
static void cs2_xfree (const void *p);


/* ========================================================================= */
/* global variables */

static int cs2_colour_list[] = { 1, 2, 3, 9 };

static SANE_Device **device_list = NULL;
static int n_device_list = 0;
static cs2_interface_t try_interface = CS2_INTERFACE_UNKNOWN;
static int open_devices = 0;


/* ========================================================================= */
/* SANE entry points */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  DBG_INIT ();
  DBG (10, "sane_init() called.\n");
  DBG (1, "coolscan2 backend, version %i.%i.%i initializing.\n", CS2_VERSION_MAJOR, CS2_VERSION_MINOR, CS2_REVISION);

  authorize = authorize;	/* to shut up compiler */

  if (version_code)
    *version_code =
      SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);

  sanei_usb_init ();

  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  int i;

  DBG (10, "sane_exit() called.\n");

  for (i = 0; i < n_device_list; i++)
    {
      cs2_xfree (device_list[i]->name);
      cs2_xfree (device_list[i]->vendor);
      cs2_xfree (device_list[i]->model);
      cs2_xfree (device_list[i]);
    }
  cs2_xfree (device_list);
}

SANE_Status
sane_get_devices (const SANE_Device *** list, SANE_Bool local_only)
{
  char line[PATH_MAX], *p;
  FILE *config;

  local_only = local_only;	/* to shut up compiler */

  DBG (10, "sane_get_devices() called.\n");

  if (device_list)
    DBG (6,
	 "sane_get_devices(): Device list already populated, not probing again.\n");
  else
    {
      if (open_devices)
	{
	  DBG (4,
	       "sane_get_devices(): Devices open, not scanning for scanners.\n");
	  return SANE_STATUS_IO_ERROR;
	}

      config = sanei_config_open (CS2_CONFIG_FILE);
      if (config)
	{
	  DBG (4, "sane_get_devices(): Reading config file.\n");
	  while (sanei_config_read (line, sizeof (line), config))
	    {
	      p = line;
	      p += strspn (line, " \t");
	      if (strlen (p) && (p[0] != '\n') && (p[0] != '#'))
		cs2_open (line, CS2_INTERFACE_UNKNOWN, NULL);
	    }
	  fclose (config);
	}
      else
	{
	  DBG (4, "sane_get_devices(): No config file found.\n");
	  cs2_open ("auto", CS2_INTERFACE_UNKNOWN, NULL);
	}

      switch (n_device_list)
	{
	case 0:
	  DBG (6, "sane_get_devices(): No devices detected.\n");
	  break;
	case 1:
	  DBG (6, "sane_get_devices(): 1 device detected.\n");
	  break;
	default:
	  DBG (6, "sane_get_devices(): %i devices detected.\n",
	       n_device_list);
	  break;
	}
    }

  *list = (const SANE_Device **) device_list;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * h)
{
  SANE_Status status;
  cs2_t *s;
  int i_option;
  unsigned int i_list;
  SANE_Option_Descriptor o;
  SANE_Word *word_list;
  SANE_Range *range = NULL;
  int alloc_failed = 0;

  DBG (10, "sane_open() called.\n");

  status = cs2_open (name, CS2_INTERFACE_UNKNOWN, &s);
  if (status)
    return status;

  *h = (SANE_Handle) s;

  /* get device properties */

  s->lut_r = s->lut_g = s->lut_b = s->lut_neutral = NULL;
  s->resx_list = s->resy_list = NULL;
  s->resx_n_list = s->resy_n_list = 0;

  status = cs2_full_inquiry (s);
  if (status)
    return status;

  /* option descriptors */

  for (i_option = 0; i_option < CS2_N_OPTIONS; i_option++)
    {
      o.name = o.title = o.desc = NULL;
      o.type = o.unit = o.cap = o.constraint_type = o.size = 0;
      o.constraint.range = NULL;	/* only one union member needs to be NULLed */
      switch (i_option)
	{
	case CS2_OPTION_NUM:
	  o.name = "";
	  o.title = SANE_TITLE_NUM_OPTIONS;
	  o.desc = SANE_DESC_NUM_OPTIONS;
	  o.type = SANE_TYPE_INT;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_PREVIEW:
	  o.name = "preview";
	  o.title = "Preview mode";
	  o.desc = "Preview mode";
	  o.type = SANE_TYPE_BOOL;
	  o.size = WSIZE;
	  o.cap =
	    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
	  break;
	case CS2_OPTION_NEGATIVE:
	  o.name = "negative";
	  o.title = "Negative";
	  o.desc = "Negative film: make scanner invert colours";
	  o.type = SANE_TYPE_BOOL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
#ifndef CS2_BLEEDING_EDGE
	  o.cap |= SANE_CAP_INACTIVE;
#endif
	  break;
	case CS2_OPTION_INFRARED:
	  o.name = "infrared";
	  o.title = "Read infrared channel";
	  o.desc = "Read infrared channel in addition to scan colours";
	  o.type = SANE_TYPE_BOOL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_DEPTH:
	  o.name = "depth";
	  o.title = "Bit depth per channel";
	  o.desc = "Number of bits output by scanner for each channel";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_NONE;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_WORD_LIST;
	  word_list = (SANE_Word *) cs2_xmalloc (2 * sizeof (SANE_Word));
	  if (!word_list)
	    alloc_failed = 1;
	  else
	    {
	      word_list[1] = 8;
	      word_list[2] = s->maxbits;
	      word_list[0] = 2;
	      o.constraint.word_list = word_list;
	    }
	  break;
	case CS2_OPTION_EXPOSURE:
	  o.name = "exposure";
	  o.title = "Exposure multiplier";
	  o.desc = "Exposure multiplier for all channels";
	  o.type = SANE_TYPE_FIXED;
	  o.unit = SANE_UNIT_NONE;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = SANE_FIX (0.);
	      range->max = SANE_FIX (10.);
	      range->quant = SANE_FIX (0.1);
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_EXPOSURE_R:
	  o.name = "red-exposure";
	  o.title = "Red exposure time";
	  o.desc = "Exposure time for red channel";
	  o.type = SANE_TYPE_FIXED;
	  o.unit = SANE_UNIT_MICROSECOND;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = SANE_FIX (50.);
	      range->max = SANE_FIX (20000.);
	      range->quant = SANE_FIX (10.);
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_EXPOSURE_G:
	  o.name = "green-exposure";
	  o.title = "Green exposure time";
	  o.desc = "Exposure time for green channel";
	  o.type = SANE_TYPE_FIXED;
	  o.unit = SANE_UNIT_MICROSECOND;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = SANE_FIX (50.);
	      range->max = SANE_FIX (20000.);
	      range->quant = SANE_FIX (10.);
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_EXPOSURE_B:
	  o.name = "blue-exposure";
	  o.title = "Blue exposure time";
	  o.desc = "Exposure time for blue channel";
	  o.type = SANE_TYPE_FIXED;
	  o.unit = SANE_UNIT_MICROSECOND;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = SANE_FIX (50.);
	      range->max = SANE_FIX (20000.);
	      range->quant = SANE_FIX (10.);
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_LUT_R:
	  o.name = "red-gamma-table";
	  o.title = "LUT for red channel";
	  o.desc = "LUT for red channel";
	  o.type = SANE_TYPE_INT;
	  o.size = s->n_lut * WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->n_lut - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_LUT_G:
	  o.name = "green-gamma-table";
	  o.title = "LUT for green channel";
	  o.desc = "LUT for green channel";
	  o.type = SANE_TYPE_INT;
	  o.size = s->n_lut * WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->n_lut - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_LUT_B:
	  o.name = "blue-gamma-table";
	  o.title = "LUT for blue channel";
	  o.desc = "LUT for blue channel";
	  o.type = SANE_TYPE_INT;
	  o.size = s->n_lut * WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->n_lut - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_LOAD:
	  o.name = "load";
	  o.title = "Load";
	  o.desc = "Load next slide";
	  o.type = SANE_TYPE_BUTTON;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_EJECT:
	  o.name = "eject";
	  o.title = "Eject";
	  o.desc = "Eject loaded medium";
	  o.type = SANE_TYPE_BUTTON;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_RESET:
	  o.name = "reset";
	  o.title = "Reset scanner";
	  o.desc = "Initialize scanner";
	  o.type = SANE_TYPE_BUTTON;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_RESX:
	case CS2_OPTION_RES:
	case CS2_OPTION_PREVIEW_RESOLUTION:
	  if (i_option == CS2_OPTION_PREVIEW_RESOLUTION)
	    {
	      o.name = "preview-resolution";
	      o.title = "Preview resolution";
	      o.desc =
		"Scanning resolution for preview mode in dpi, affecting both x and y directions";
	    }
	  else if (i_option == CS2_OPTION_RES)
	    {
	      o.name = "resolution";
	      o.title = "Resolution";
	      o.desc =
		"Scanning resolution in dpi, affecting both x and y directions";
	    }
	  else
	    {
	      o.name = "x-resolution";
	      o.title = "X resolution";
	      o.desc =
		"Scanning resolution in dpi, affecting x direction only";
	    }
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_DPI;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  if (i_option == CS2_OPTION_RESX)
	    o.cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
	  if (i_option == CS2_OPTION_PREVIEW_RESOLUTION)
	    o.cap |= SANE_CAP_ADVANCED;
	  o.constraint_type = SANE_CONSTRAINT_WORD_LIST;
	  word_list =
	    (SANE_Word *) cs2_xmalloc ((s->resx_n_list + 1) *
				       sizeof (SANE_Word));
	  if (!word_list)
	    alloc_failed = 1;
	  else
	    {
	      for (i_list = 0; i_list < s->resx_n_list; i_list++)
		word_list[i_list + 1] = s->resx_list[i_list];
	      word_list[0] = s->resx_n_list;
	      o.constraint.word_list = word_list;
	    }
	  break;
	case CS2_OPTION_RESY:
	  o.name = "y-resolution";
	  o.title = "Y resolution";
	  o.desc = "Scanning resolution in dpi, affecting y direction only";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_DPI;
	  o.size = WSIZE;
	  o.cap =
	    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE |
	    SANE_CAP_ADVANCED;
	  o.constraint_type = SANE_CONSTRAINT_WORD_LIST;
	  word_list =
	    (SANE_Word *) cs2_xmalloc ((s->resy_n_list + 1) *
				       sizeof (SANE_Word));
	  if (!word_list)
	    alloc_failed = 1;
	  else
	    {
	      for (i_list = 0; i_list < s->resy_n_list; i_list++)
		word_list[i_list + 1] = s->resy_list[i_list];
	      word_list[0] = s->resy_n_list;
	      o.constraint.word_list = word_list;
	    }
	  break;
	case CS2_OPTION_RES_INDEPENDENT:
	  o.name = "independent-res";
	  o.title = "Independent x/y resolutions";
	  o.desc =
	    "Enable independent controls for scanning resolution in x and y direction";
	  o.type = SANE_TYPE_BOOL;
	  o.size = WSIZE;
	  o.cap =
	    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE |
	    SANE_CAP_ADVANCED;
	  break;
	case CS2_OPTION_FRAME:
	  o.name = "frame";
	  o.title = "Frame number";
	  o.desc = "Number of frame to be scanned, starting with 1";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_NONE;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  if (s->n_frames <= 1)
	    o.cap |= SANE_CAP_INACTIVE;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 1;
	      range->max = s->n_frames;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_SUBFRAME:
	  o.name = "subframe";
	  o.title = "Frame shift";
	  o.desc = "Fine position within the selected frame";
	  o.type = SANE_TYPE_FIXED;
	  o.unit = SANE_UNIT_MM;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = SANE_FIX (0.);
	      range->max = SANE_FIX ((s->boundaryy - 1) * s->unit_mm);
	      range->quant = SANE_FIX (0.);
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_XMIN:
	  o.name = "tl-x";
	  o.title = "Left x value of scan area";
	  o.desc = "Left x value of scan area";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_PIXEL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	      range->min = 0;
	      range->max = s->boundaryx - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_XMAX:
	  o.name = "br-x";
	  o.title = "Right x value of scan area";
	  o.desc = "Right x value of scan area";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_PIXEL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->boundaryx - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_YMIN:
	  o.name = "tl-y";
	  o.title = "Top y value of scan area";
	  o.desc = "Top y value of scan area";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_PIXEL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->boundaryy - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_YMAX:
	  o.name = "br-y";
	  o.title = "Bottom y value of scan area";
	  o.desc = "Bottom y value of scan area";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_PIXEL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->boundaryy - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_FOCUS_ON_CENTRE:
	  o.name = "focus-on-centre";
	  o.title = "Use centre of scan area as AF point";
	  o.desc =
	    "Use centre of scan area as AF point instead of manual AF point selection";
	  o.type = SANE_TYPE_BOOL;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_FOCUS:
	  o.name = "focus";
	  o.title = "Focus position";
	  o.desc = "Focus position for manual focus";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_NONE;
	  o.size = WSIZE;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = s->focus_min;
	      range->max = s->focus_max;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_AUTOFOCUS:
	  o.name = "autofocus";
	  o.title = "Autofocus now";
	  o.desc = "Autofocus now";
	  o.type = SANE_TYPE_BUTTON;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_FOCUSX:
	  o.name = "focusx";
	  o.title = "X coordinate of AF point";
	  o.desc = "X coordinate of AF point";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_PIXEL;
	  o.size = WSIZE;
	  o.cap =
	    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->boundaryx - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_FOCUSY:
	  o.name = "focusy";
	  o.title = "Y coordinate of AF point";
	  o.desc = "Y coordinate of AF point";
	  o.type = SANE_TYPE_INT;
	  o.unit = SANE_UNIT_PIXEL;
	  o.size = WSIZE;
	  o.cap =
	    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE;
	  o.constraint_type = SANE_CONSTRAINT_RANGE;
	  range = (SANE_Range *) cs2_xmalloc (sizeof (SANE_Range));
	  if (!range)
	    alloc_failed = 1;
	  else
	    {
	      range->min = 0;
	      range->max = s->boundaryy - 1;
	      range->quant = 1;
	      o.constraint.range = range;
	    }
	  break;
	case CS2_OPTION_SCAN_AE:
	  o.name = "ae";
	  o.title = "Auto-exposure scan now";
	  o.desc = "Perform auto-exposure scan";
	  o.type = SANE_TYPE_BUTTON;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	case CS2_OPTION_SCAN_AE_WB:
	  o.name = "ae-wb";
	  o.title = "Auto-exposure scan with white balance now";
	  o.desc = "Perform auto-exposure scan with white balance";
	  o.type = SANE_TYPE_BUTTON;
	  o.cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	  break;
	default:
	  DBG (1, "BUG: sane_open(): Unknown option number.\n");
	  return SANE_STATUS_INVAL;
	  break;
	}
      s->option_list[i_option] = o;
    }

  s->scanning = SANE_FALSE;
  s->preview = SANE_FALSE;
  s->negative = SANE_FALSE;
  s->depth = 8;
  s->infrared = 0;
  s->i_frame = 1;
  s->subframe = 0.;
  s->res = s->resx = s->resx_max;
  s->resy = s->resy_max;
  s->res_independent = SANE_FALSE;
  s->res_preview = s->resx_max / 10;
  if (s->res_preview < s->resx_min)
    s->res_preview = s->resx_min;
  s->xmin = 0;
  s->xmax = s->boundaryx - 1;
  s->ymin = 0;
  s->ymax = s->boundaryy - 1;
  s->focus_on_centre = SANE_TRUE;
  s->focus = 0;
  s->focusx = 0;
  s->focusy = 0;
  s->exposure = 1.;
  s->exposure_r = 1200.;
  s->exposure_g = 1200.;
  s->exposure_b = 1000.;
  s->infrared_stage = CS2_INFRARED_OFF;
  s->infrared_next = CS2_INFRARED_OFF;
  s->infrared_buf = NULL;
  s->n_infrared_buf = 0;
  s->line_buf = NULL;
  s->n_line_buf = 0;

  if (alloc_failed)
    {
      cs2_close (s);
      return SANE_STATUS_NO_MEM;
    }

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle h)
{
  cs2_t *s = (cs2_t *) h;

  DBG (10, "sane_close() called.\n");

  cs2_close (s);
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle h, SANE_Int n)
{
  cs2_t *s = (cs2_t *) h;

  DBG (10, "sane_get_option_descriptor() called, option #%i.\n", n);

  if ((n >= 0) && (n < CS2_N_OPTIONS))
    return &s->option_list[n];
  else
    return NULL;
}

SANE_Status
sane_control_option (SANE_Handle h, SANE_Int n, SANE_Action a, void *v,
		     SANE_Int * i)
{
  cs2_t *s = (cs2_t *) h;
  SANE_Int flags = 0;
  cs2_pixel_t pixel;
  SANE_Status status;
  SANE_Option_Descriptor o = s->option_list[n];

  DBG (10, "sane_control_option() called, option #%i, action #%i.\n", n, a);

  switch (a)
    {
    case SANE_ACTION_GET_VALUE:

      switch (n)
	{
	case CS2_OPTION_NUM:
	  *(SANE_Word *) v = CS2_N_OPTIONS;
	  break;
	case CS2_OPTION_NEGATIVE:
	  *(SANE_Word *) v = s->negative;
	  break;
	case CS2_OPTION_INFRARED:
	  *(SANE_Word *) v = s->infrared;
	  break;
	case CS2_OPTION_DEPTH:
	  *(SANE_Word *) v = s->depth;
	  break;
	case CS2_OPTION_PREVIEW:
	  *(SANE_Word *) v = s->preview;
	  break;
	case CS2_OPTION_EXPOSURE:
	  *(SANE_Word *) v = SANE_FIX (s->exposure);
	  break;
	case CS2_OPTION_EXPOSURE_R:
	  *(SANE_Word *) v = SANE_FIX (s->exposure_r);
	  break;
	case CS2_OPTION_EXPOSURE_G:
	  *(SANE_Word *) v = SANE_FIX (s->exposure_g);
	  break;
	case CS2_OPTION_EXPOSURE_B:
	  *(SANE_Word *) v = SANE_FIX (s->exposure_b);
	  break;
	case CS2_OPTION_LUT_R:
	  if (!(s->lut_r))
	    return SANE_STATUS_INVAL;
	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    ((SANE_Word *) v)[pixel] = s->lut_r[pixel];
	  break;
	case CS2_OPTION_LUT_G:
	  if (!(s->lut_g))
	    return SANE_STATUS_INVAL;
	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    ((SANE_Word *) v)[pixel] = s->lut_g[pixel];
	  break;
	case CS2_OPTION_LUT_B:
	  if (!(s->lut_b))
	    return SANE_STATUS_INVAL;
	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    ((SANE_Word *) v)[pixel] = s->lut_b[pixel];
	  break;
	case CS2_OPTION_EJECT:
	  break;
	case CS2_OPTION_LOAD:
	  break;
	case CS2_OPTION_RESET:
	  break;
	case CS2_OPTION_FRAME:
	  *(SANE_Word *) v = s->i_frame;
	  break;
	case CS2_OPTION_SUBFRAME:
	  *(SANE_Word *) v = SANE_FIX (s->subframe);
	  break;
	case CS2_OPTION_RES:
	  *(SANE_Word *) v = s->res;
	  break;
	case CS2_OPTION_RESX:
	  *(SANE_Word *) v = s->resx;
	  break;
	case CS2_OPTION_RESY:
	  *(SANE_Word *) v = s->resy;
	  break;
	case CS2_OPTION_RES_INDEPENDENT:
	  *(SANE_Word *) v = s->res_independent;
	  break;
	case CS2_OPTION_PREVIEW_RESOLUTION:
	  *(SANE_Word *) v = s->res_preview;
	  break;
	case CS2_OPTION_XMIN:
	  *(SANE_Word *) v = s->xmin;
	  break;
	case CS2_OPTION_XMAX:
	  *(SANE_Word *) v = s->xmax;
	  break;
	case CS2_OPTION_YMIN:
	  *(SANE_Word *) v = s->ymin;
	  break;
	case CS2_OPTION_YMAX:
	  *(SANE_Word *) v = s->ymax;
	  break;
	case CS2_OPTION_FOCUS_ON_CENTRE:
	  *(SANE_Word *) v = s->focus_on_centre;
	  break;
	case CS2_OPTION_FOCUS:
	  *(SANE_Word *) v = s->focus;
	  break;
	case CS2_OPTION_AUTOFOCUS:
	  break;
	case CS2_OPTION_FOCUSX:
	  *(SANE_Word *) v = s->focusx;
	  break;
	case CS2_OPTION_FOCUSY:
	  *(SANE_Word *) v = s->focusy;
	  break;
	case CS2_OPTION_SCAN_AE:
	  break;
	case CS2_OPTION_SCAN_AE_WB:
	  break;
	default:
	  DBG (4, "Error: sane_control_option(): Unknown option (bug?).\n");
	  return SANE_STATUS_INVAL;
	}
      break;

    case SANE_ACTION_SET_VALUE:
      if (s->scanning)
	return SANE_STATUS_INVAL;
/* XXXXXXXXXXXXXXXXX do this for all elements of arrays */
      switch (o.type)
	{
	case SANE_TYPE_BOOL:
	  if ((*(SANE_Word *) v != SANE_TRUE)
	      && (*(SANE_Word *) v != SANE_FALSE))
	    return SANE_STATUS_INVAL;
	  break;
	case SANE_TYPE_INT:
	case SANE_TYPE_FIXED:
	  switch (o.constraint_type)
	    {
	    case SANE_CONSTRAINT_RANGE:
	      if (*(SANE_Word *) v < o.constraint.range->min)
		{
		  *(SANE_Word *) v = o.constraint.range->min;
		  flags |= SANE_INFO_INEXACT;
		}
	      else if (*(SANE_Word *) v > o.constraint.range->max)
		{
		  *(SANE_Word *) v = o.constraint.range->max;
		  flags |= SANE_INFO_INEXACT;
		}
	      break;
	    case SANE_CONSTRAINT_WORD_LIST:
	      break;
	    default:
	      break;
	    }
	  break;
	case SANE_TYPE_STRING:
	  break;
	case SANE_TYPE_BUTTON:
	  break;
	case SANE_TYPE_GROUP:
	  break;
	}
      switch (n)
	{
	case CS2_OPTION_NUM:
	  return SANE_STATUS_INVAL;
	  break;
	case CS2_OPTION_NEGATIVE:
	  s->negative = *(SANE_Word *) v;
	  break;
	case CS2_OPTION_INFRARED:
	  s->infrared = *(SANE_Word *) v;
	  /*      flags |= SANE_INFO_RELOAD_PARAMS; XXXXXXXXXXXXXXXXX */
	  break;
	case CS2_OPTION_DEPTH:
	  s->depth = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_PREVIEW:
	  s->preview = *(SANE_Word *) v;
	  break;
	case CS2_OPTION_EXPOSURE:
	  s->exposure = SANE_UNFIX (*(SANE_Word *) v);
	  break;
	case CS2_OPTION_EXPOSURE_R:
	  s->exposure_r = SANE_UNFIX (*(SANE_Word *) v);
	  break;
	case CS2_OPTION_EXPOSURE_G:
	  s->exposure_g = SANE_UNFIX (*(SANE_Word *) v);
	  break;
	case CS2_OPTION_EXPOSURE_B:
	  s->exposure_b = SANE_UNFIX (*(SANE_Word *) v);
	  break;
	case CS2_OPTION_LUT_R:
	  if (!(s->lut_r))
	    return SANE_STATUS_INVAL;
	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    s->lut_r[pixel] = ((SANE_Word *) v)[pixel];
	  break;
	case CS2_OPTION_LUT_G:
	  if (!(s->lut_g))
	    return SANE_STATUS_INVAL;
	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    s->lut_g[pixel] = ((SANE_Word *) v)[pixel];
	  break;
	case CS2_OPTION_LUT_B:
	  if (!(s->lut_b))
	    return SANE_STATUS_INVAL;
	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    s->lut_b[pixel] = ((SANE_Word *) v)[pixel];
	  break;
	case CS2_OPTION_LOAD:
	  cs2_load (s);
	  break;
	case CS2_OPTION_EJECT:
	  cs2_eject (s);
	  break;
	case CS2_OPTION_RESET:
	  cs2_reset (s);
	  break;
	case CS2_OPTION_FRAME:
	  s->i_frame = *(SANE_Word *) v;
	  break;
	case CS2_OPTION_SUBFRAME:
	  s->subframe = SANE_UNFIX (*(SANE_Word *) v);
	  break;
	case CS2_OPTION_RES:
	  s->res = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_RESX:
	  s->resx = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_RESY:
	  s->resy = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_RES_INDEPENDENT:
	  s->res_independent = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_PREVIEW_RESOLUTION:
	  s->res_preview = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_XMIN:
	  s->xmin = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_XMAX:
	  s->xmax = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_YMIN:
	  s->ymin = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_YMAX:
	  s->ymax = *(SANE_Word *) v;
	  flags |= SANE_INFO_RELOAD_PARAMS;
	  break;
	case CS2_OPTION_FOCUS_ON_CENTRE:
	  s->focus_on_centre = *(SANE_Word *) v;
	  if (s->focus_on_centre)
	    {
	      s->option_list[CS2_OPTION_FOCUSX].cap |= SANE_CAP_INACTIVE;
	      s->option_list[CS2_OPTION_FOCUSY].cap |= SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      s->option_list[CS2_OPTION_FOCUSX].cap &= ~SANE_CAP_INACTIVE;
	      s->option_list[CS2_OPTION_FOCUSY].cap &= ~SANE_CAP_INACTIVE;
	    }
	  flags |= SANE_INFO_RELOAD_OPTIONS;
	  break;
	case CS2_OPTION_FOCUS:
	  s->focus = *(SANE_Word *) v;
	  break;
	case CS2_OPTION_AUTOFOCUS:
	  cs2_autofocus (s);
	  flags |= SANE_INFO_RELOAD_OPTIONS;
	  break;
	case CS2_OPTION_FOCUSX:
	  s->focusx = *(SANE_Word *) v;
	  break;
	case CS2_OPTION_FOCUSY:
	  s->focusy = *(SANE_Word *) v;
	  break;
	case CS2_OPTION_SCAN_AE:
	  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
	  status = cs2_scan (s, CS2_SCAN_AE);
	  if (status)
	    return status;
	  status = cs2_get_exposure (s);
	  if (status)
	    return status;
	  s->exposure = 1.;
	  s->exposure_r = s->real_exposure[1] / 100.;
	  s->exposure_g = s->real_exposure[2] / 100.;
	  s->exposure_b = s->real_exposure[3] / 100.;
	  flags |= SANE_INFO_RELOAD_OPTIONS;
	  break;
	case CS2_OPTION_SCAN_AE_WB:
	  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
	  status = cs2_scan (s, CS2_SCAN_AE_WB);
	  if (status)
	    return status;
	  status = cs2_get_exposure (s);
	  if (status)
	    return status;
	  s->exposure = 1.;
	  s->exposure_r = s->real_exposure[1] / 100.;
	  s->exposure_g = s->real_exposure[2] / 100.;
	  s->exposure_b = s->real_exposure[3] / 100.;
	  flags |= SANE_INFO_RELOAD_OPTIONS;
	  break;
	default:
	  DBG (4,
	       "Error: sane_control_option(): Unknown option number (bug?).\n");
	  return SANE_STATUS_INVAL;
	  break;
	}
      break;

    default:
      DBG (1, "BUG: sane_control_option(): Unknown action number.\n");
      return SANE_STATUS_INVAL;
      break;
    }

  if (i)
    *i = flags;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_parameters (SANE_Handle h, SANE_Parameters * p)
{
  cs2_t *s = (cs2_t *) h;
  SANE_Status status;

  DBG (10, "sane_get_parameters() called.\n");

  if (!s->scanning)		/* only recalculate when not scanning */
    {
      status = cs2_convert_options (s);
      if (status)
	return status;
    }

  if (s->infrared_stage == CS2_INFRARED_OUT)
    {
      p->format = SANE_FRAME_GRAY;
      p->bytes_per_line = s->logical_width * s->bytes_per_pixel;
    }
  else
    {
      p->format = SANE_FRAME_RGB;	/* XXXXXXXX CCCCCCCCCC */
      p->bytes_per_line =
	s->n_colour_out * s->logical_width * s->bytes_per_pixel;
    }
  p->last_frame = SANE_TRUE;
  p->lines = s->logical_height;
  p->depth = 8 * s->bytes_per_pixel;
  p->pixels_per_line = s->logical_width;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle h)
{
  cs2_t *s = (cs2_t *) h;
  SANE_Status status;

  DBG (10, "sane_start() called.\n");

  if (s->scanning)
    return SANE_STATUS_INVAL;

  status = cs2_convert_options (s);
  if (status)
    return status;

  s->infrared_index = 0;
  s->i_line_buf = 0;
  s->xfer_position = 0;

  s->scanning = SANE_TRUE;

  if (s->infrared_stage == CS2_INFRARED_OUT)
    return SANE_STATUS_GOOD;
  else
    return cs2_scan (s, CS2_SCAN_NORMAL);
}

SANE_Status
sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len)
{
  cs2_t *s = (cs2_t *) h;
  SANE_Status status;
  ssize_t xfer_len_in, xfer_len_line, xfer_len_out;
  unsigned long index;
  int colour;
  u_int8_t *s8 = NULL;
  u_int16_t *s16 = NULL;
  SANE_Byte *line_buf_new;

  DBG (10, "sane_read() called, maxlen = %i.\n", maxlen);

  if (!s->scanning) {
    *len = 0;
    return SANE_STATUS_CANCELLED;
  }

  if (s->infrared_stage == CS2_INFRARED_OUT)
    {
      xfer_len_out = maxlen;

      if (s->xfer_position + xfer_len_out > s->n_infrared_buf)
	xfer_len_out = s->n_infrared_buf - s->xfer_position;

      if (xfer_len_out == 0)	/* no more data */
	{
	  *len = 0;
	  return SANE_STATUS_EOF;
	}

      memcpy (buf, &(s->infrared_buf[s->xfer_position]), xfer_len_out);

      s->xfer_position += xfer_len_out;

      if (s->xfer_position >= s->n_infrared_buf)
	s->infrared_next = CS2_INFRARED_OFF;

      *len = xfer_len_out;
      return SANE_STATUS_GOOD;
    }

  if (s->i_line_buf > 0)
    {
      xfer_len_out = s->n_line_buf - s->i_line_buf;
      if (xfer_len_out > maxlen)
	xfer_len_out = maxlen;

      memcpy (buf, &(s->line_buf[s->i_line_buf]), xfer_len_out);

      s->i_line_buf += xfer_len_out;
      if (s->i_line_buf >= s->n_line_buf)
	s->i_line_buf = 0;

      *len = xfer_len_out;
      return SANE_STATUS_GOOD;
    }

  xfer_len_line = s->n_colour_out * s->logical_width * s->bytes_per_pixel;
  xfer_len_in =
    s->n_colour_in * s->logical_width * s->bytes_per_pixel +
    s->n_colour_in * s->odd_padding;

  if (s->xfer_position + xfer_len_line > s->xfer_bytes_total)
    xfer_len_line = s->xfer_bytes_total - s->xfer_position; /* just in case */

  if (xfer_len_line == 0)	/* no more data */
    {
      *len = 0;
      return SANE_STATUS_EOF;
    }

  if (xfer_len_line != s->n_line_buf)
    {
      line_buf_new =
	(SANE_Byte *) cs2_xrealloc (s->line_buf,
				    xfer_len_line * sizeof (SANE_Byte));
      if (!line_buf_new)
	{
	  *len = 0;
	  return SANE_STATUS_NO_MEM;
	}
      s->line_buf = line_buf_new;
      s->n_line_buf = xfer_len_line;
    }

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "28 00 00 00 00 00");
  cs2_pack_byte (s, (xfer_len_in >> 16) & 0xff);
  cs2_pack_byte (s, (xfer_len_in >> 8) & 0xff);
  cs2_pack_byte (s, xfer_len_in & 0xff);
  cs2_parse_cmd (s, "00");
  s->n_recv = xfer_len_in;
  status = cs2_issue_cmd (s);

  if (status)
    {
      *len = 0;
      return status;
    }

  for (index = 0; index < s->logical_width; index++)
    for (colour = 0;
	 colour < s->n_colour_out + (s->infrared_stage ==
				     CS2_INFRARED_IN ? 1 : 0); colour++)
      switch (s->bytes_per_pixel)
	{
	case 1:
	  if ((s->infrared_stage == CS2_INFRARED_IN)
	      && (colour == s->n_colour_out))
	    s8 = (u_int8_t *) & (s->infrared_buf[s->infrared_index++]);
	  else
	    s8 =
	      (u_int8_t *) & (s->line_buf[s->n_colour_out * index + colour]);
	  *s8 =
	    s->recv_buf[colour * s->logical_width +
			(colour + 1) * s->odd_padding + index];
	  break;
	case 2:
	  if ((s->infrared_stage == CS2_INFRARED_IN)
	      && (colour == s->n_colour_out))
	    s16 =
	      (u_int16_t *) & (s->infrared_buf[2 * (s->infrared_index++)]);
	  else
	    s16 =
	      (u_int16_t *) & (s->
			       line_buf[2 *
					(s->n_colour_out * index + colour)]);
	  *s16 =
	    s->recv_buf[2 * (colour * s->logical_width + index)] * 256 +
	    s->recv_buf[2 * (colour * s->logical_width + index) + 1];
	  *s16 <<= s->shift_bits;
	  break;
	default:
	  DBG (1, "BUG: sane_read(): Unknown number of bytes per pixel.\n");
	  *len = 0;
	  return SANE_STATUS_INVAL;
	  break;
	}
  s->xfer_position += xfer_len_line;

  xfer_len_out = xfer_len_line;
  if (xfer_len_out > maxlen)
    xfer_len_out = maxlen;

  memcpy (buf, s->line_buf, xfer_len_out);
  if (xfer_len_out < xfer_len_line)
    s->i_line_buf = xfer_len_out; /* data left in the line buffer, read out next time */

  if ((s->infrared_stage == CS2_INFRARED_IN)
      && (s->xfer_position >= s->n_infrared_buf))
    s->infrared_next = CS2_INFRARED_OUT;

  *len = xfer_len_out;
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle h)
{
  cs2_t *s = (cs2_t *) h;

  if (s->scanning)
    DBG (10, "sane_cancel() called while scanning.\n");
  else
    DBG (10, "sane_cancel() called while not scanning.\n");

  if (s->scanning && (s->infrared_stage != CS2_INFRARED_OUT))
    {
      cs2_init_buffer (s);
      cs2_parse_cmd (s, "c0 00 00 00 00 00");
      cs2_issue_cmd (s);
    }

  s->scanning = SANE_FALSE;
}

SANE_Status
sane_set_io_mode (SANE_Handle h, SANE_Bool m)
{
  cs2_t *s = (cs2_t *) h;

  DBG (10, "sane_set_io_mode() called.\n");

  if (!s->scanning)
    return SANE_STATUS_INVAL;
  if (m == SANE_FALSE)
    return SANE_STATUS_GOOD;
  else
    return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle h, SANE_Int * fd)
{
  cs2_t *s = (cs2_t *) h;

  DBG (10, "sane_get_select_fd() called.\n");

  fd = fd;			/* to shut up compiler */
  s = s;			/* to shut up compiler */

  return SANE_STATUS_UNSUPPORTED;
}


/* ========================================================================= */
/* private functions */

static SANE_Status
cs2_open (const char *device, cs2_interface_t interface, cs2_t ** sp)
{
  SANE_Status status;
  cs2_t *s;
  char *prefix = NULL, *line, *device2;
  int i;
  int alloc_failed = 0;
  SANE_Device **device_list_new;

  DBG (6, "cs2_open() called, with device = %s and interface = %i\n", device,
       interface);

  if (!strncmp (device, "auto", 5))
    {
      try_interface = CS2_INTERFACE_SCSI;
      sanei_config_attach_matching_devices ("scsi Nikon *", cs2_attach);
      try_interface = CS2_INTERFACE_USB;
      sanei_usb_attach_matching_devices ("usb 0x04b0 0x4000", cs2_attach);
      return SANE_STATUS_GOOD;
    }

  if ((s = (cs2_t *) cs2_xmalloc (sizeof (cs2_t))) == NULL)
    return SANE_STATUS_NO_MEM;
  memset (s, 0, sizeof (cs2_t));
  s->send_buf = s->recv_buf = NULL;
  s->send_buf_size = s->recv_buf_size = 0;

  switch (interface)
    {
    case CS2_INTERFACE_UNKNOWN:
      for (i = 0; i < 2; i++)
	{
	  switch (i)
	    {
	    case 1:
	      prefix = "usb:";
	      try_interface = CS2_INTERFACE_USB;
	      break;
	    default:
	      prefix = "scsi:";
	      try_interface = CS2_INTERFACE_SCSI;
	      break;
	    }
	  if (!strncmp (device, prefix, strlen (prefix)))
	    {
	      device2 = device + strlen (prefix);
	      cs2_xfree (s);
	      return cs2_open (device2, try_interface, sp);
	    }
	}
      cs2_xfree (s);
      return SANE_STATUS_INVAL;
      break;
    case CS2_INTERFACE_SCSI:
      s->interface = CS2_INTERFACE_SCSI;
      DBG (6,
	   "cs2_open(): Trying to open %s, assuming SCSI or SBP2 interface ...\n",
	   device);
      status = sanei_scsi_open (device, &s->fd, cs2_scsi_sense_handler, s);
      if (status)
	{
	  DBG (6, "cs2_open(): ... failed: %s.\n", sane_strstatus (status));
	  cs2_xfree (s);
	  return status;
	}
      break;
    case CS2_INTERFACE_USB:
      s->interface = CS2_INTERFACE_USB;
      DBG (6, "cs2_open(): Trying to open %s, assuming USB interface ...\n",
	   device);
      status = sanei_usb_open (device, &s->fd);
      if (status)
	{
	  DBG (6, "cs2_open(): ... failed: %s.\n", sane_strstatus (status));
	  cs2_xfree (s);
	  return status;
	}
      break;
    }

  open_devices++;
  DBG (6, "cs2_open(): ... looks OK, trying to identify device.\n");

  /* identify scanner */
  status = cs2_page_inquiry (s, -1);
  if (status)
    {
      DBG (4, "Error: cs2_open(): failed to get page: %s.\n",
	   sane_strstatus (status));
      cs2_close (s);
      return status;
    }

  strncpy (s->vendor_string, s->recv_buf + 8, 8);
  s->vendor_string[8] = '\0';
  strncpy (s->product_string, s->recv_buf + 16, 16);
  s->product_string[16] = '\0';
  strncpy (s->revision_string, s->recv_buf + 32, 4);
  s->revision_string[4] = '\0';

  DBG (10,
       "cs2_open(): Inquiry reveals: vendor = '%s', product = '%s', revision = '%s'.\n",
       s->vendor_string, s->product_string, s->revision_string);

  if (!strncmp (s->product_string, "COOLSCANIII     ", 16))
    s->type = CS2_TYPE_LS30;
  else if (!strncmp (s->product_string, "LS-40 ED        ", 16))
    s->type = CS2_TYPE_LS40;
  else if (!strncmp (s->product_string, "LS-2000         ", 16))
    s->type = CS2_TYPE_LS2000;
  else if (!strncmp (s->product_string, "LS-4000 ED      ", 16))
    s->type = CS2_TYPE_LS4000;
  else if (!strncmp (s->product_string, "LS-8000 ED      ", 16))
    s->type = CS2_TYPE_LS8000;

  if (s->type != CS2_TYPE_UNKOWN)
    DBG (10, "cs2_open(): Device identified as coolscan2 type #%i.\n",
	 s->type);
  else
    {
      DBG (10, "cs2_open(): Device not identified.\n");
      cs2_close (s);
      return SANE_STATUS_UNSUPPORTED;
    }

  if (sp)
    *sp = s;
  else
    {
      device_list_new =
	(SANE_Device **) cs2_xrealloc (device_list,
				       (n_device_list +
					2) * sizeof (SANE_Device *));
      if (!device_list_new)
	return SANE_STATUS_NO_MEM;
      device_list = device_list_new;
      device_list[n_device_list] =
	(SANE_Device *) cs2_xmalloc (sizeof (SANE_Device));
      if (!device_list[n_device_list])
	return SANE_STATUS_NO_MEM;
      switch (interface)
	{
	case CS2_INTERFACE_UNKNOWN:
	  DBG (1, "BUG: cs2_open(): unknown interface.\n");
	  cs2_close (s);
	  return SANE_STATUS_UNSUPPORTED;
	  break;
	case CS2_INTERFACE_SCSI:
	  prefix = "scsi:";
	  break;
	case CS2_INTERFACE_USB:
	  prefix = "usb:";
	  break;
	}

      line = (char *) cs2_xmalloc (strlen (device) + strlen (prefix) + 1);
      if (!line)
	alloc_failed = 1;
      else
	{
	  strcpy (line, prefix);
	  strcat (line, device);
	  device_list[n_device_list]->name = line;
	}

      line = (char *) cs2_xmalloc (strlen (s->vendor_string) + 1);
      if (!line)
	alloc_failed = 1;
      else
	{
	  strcpy (line, s->vendor_string);
	  device_list[n_device_list]->vendor = line;
	}

      line = (char *) cs2_xmalloc (strlen (s->product_string) + 1);
      if (!line)
	alloc_failed = 1;
      else
	{
	  strcpy (line, s->product_string);
	  device_list[n_device_list]->model = line;
	}

      device_list[n_device_list]->type = "film scanner";

      if (alloc_failed)
	{
	  cs2_xfree (device_list[n_device_list]->name);
	  cs2_xfree (device_list[n_device_list]->vendor);
	  cs2_xfree (device_list[n_device_list]->model);
	  cs2_xfree (device_list[n_device_list]);
	}
      else
	n_device_list++;
      device_list[n_device_list] = NULL;

      cs2_close (s);
    }

  return SANE_STATUS_GOOD;
}

void
cs2_close (cs2_t * s)
{
  cs2_xfree (s->lut_r);
  cs2_xfree (s->lut_g);
  cs2_xfree (s->lut_b);
  cs2_xfree (s->lut_neutral);
  cs2_xfree (s->infrared_buf);
  cs2_xfree (s->line_buf);

  switch (s->interface)
    {
    case CS2_INTERFACE_UNKNOWN:
      DBG (1, "BUG: cs2_close(): Unknown interface number.\n");
      break;
    case CS2_INTERFACE_SCSI:
      sanei_scsi_close (s->fd);
      open_devices--;
      break;
    case CS2_INTERFACE_USB:
      sanei_usb_close (s->fd);
      open_devices--;
      break;
    }

  cs2_xfree (s);
}

static SANE_Status
cs2_attach (const char *dev)
{
  SANE_Status status;

  if (try_interface == CS2_INTERFACE_UNKNOWN)
    return SANE_STATUS_UNSUPPORTED;

  status = cs2_open (dev, try_interface, NULL);
  return status;
}

static SANE_Status
cs2_scsi_sense_handler (int fd, u_char * sense_buffer, void *arg)
{
  cs2_t *s = (cs2_t *) arg;

  fd = fd;			/* to shut up compiler */

  /* sort this out ! XXXXXXXXX */

  s->sense_key = sense_buffer[2] & 0x0f;
  s->sense_asc = sense_buffer[12];
  s->sense_ascq = sense_buffer[13];
  s->sense_info = sense_buffer[3];

  return cs2_parse_sense_data (s);
}

static SANE_Status
cs2_parse_sense_data (cs2_t * s)
{
  SANE_Status status = SANE_STATUS_GOOD;

  s->sense_code =
    (s->sense_key << 24) + (s->sense_asc << 16) + (s->sense_ascq << 8) +
    s->sense_info;

  if (s->sense_key)
    DBG (10, "Sense code: %02lx-%02lx-%02lx-%02lx\n", s->sense_key,
	 s->sense_asc, s->sense_ascq, s->sense_info);

  switch (s->sense_key)
    {
    case 0x00:
      s->status = CS2_STATUS_READY;
      break;
    case 0x02:
      switch (s->sense_asc)
	{
	case 0x04:
	  s->status = CS2_STATUS_PROCESSING;
	  break;
	case 0x3a:
	  s->status = CS2_STATUS_NO_DOCS;
	  break;
	default:
	  s->status = CS2_STATUS_ERROR;
	  status = SANE_STATUS_IO_ERROR;
	  break;
	}
      break;
    default:
      s->status = CS2_STATUS_ERROR;
      status = SANE_STATUS_IO_ERROR;
      break;
    }

  if (s->sense_code == 0x09800600)
    s->status = CS2_STATUS_REISSUE;

  return status;
}

static void
cs2_init_buffer (cs2_t * s)
{
  s->n_cmd = 0;
  s->n_send = 0;
  s->n_recv = 0;
}

static SANE_Status
cs2_pack_byte (cs2_t * s, SANE_Byte byte)
{
  while (s->send_buf_size <= s->n_send)
    {
      s->send_buf_size += 16;
      s->send_buf =
	(SANE_Byte *) cs2_xrealloc (s->send_buf, s->send_buf_size);
      if (!s->send_buf)
	return SANE_STATUS_NO_MEM;
    }

  s->send_buf[s->n_send++] = byte;

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_parse_cmd (cs2_t * s, char *text)
{
  size_t i, j;
  char c, h;
  SANE_Status status;

  for (i = 0; i < strlen (text); i += 2)
    if (text[i] == ' ')
      i--;			/* a bit dirty... advance by -1+2=1 */
    else
      {
	if ((!isxdigit (text[i])) || (!isxdigit (text[i + 1])))
	  DBG (1, "BUG: cs2_parse_cmd(): Parser got invalid character.\n");
	c = 0;
	for (j = 0; j < 2; j++)
	  {
	    h = tolower (text[i + j]);
	    if ((h >= 'a') && (h <= 'f'))
	      c += 10 + h - 'a';
	    else
	      c += h - '0';
	    if (j == 0)
	      c <<= 4;
	  }
	status = cs2_pack_byte (s, c);
	if (status)
	  return status;
      }

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_grow_send_buffer (cs2_t * s)
{
  if (s->n_send > s->send_buf_size)
    {
      s->send_buf_size = s->n_send;
      s->send_buf =
	(SANE_Byte *) cs2_xrealloc (s->send_buf, s->send_buf_size);
      if (!s->send_buf)
	return SANE_STATUS_NO_MEM;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_issue_cmd (cs2_t * s)
{
  SANE_Status status = SANE_STATUS_INVAL;
  size_t n_data, n_status;
  static SANE_Byte status_buf[8];
  int status_only = 0;

  DBG (20, "cs2_issue_cmd(): opcode = 0x%02x, n_send = %lu, n_recv = %lu.\n",
       s->send_buf[0], (unsigned long) s->n_send, (unsigned long) s->n_recv);

  s->status = CS2_STATUS_READY;

  if (!s->n_cmd)
    switch (s->send_buf[0])
      {
      case 0x00:
      case 0x12:
      case 0x15:
      case 0x16:
      case 0x17:
      case 0x1a:
      case 0x1b:
      case 0x1c:
      case 0x1d:
      case 0xc0:
      case 0xc1:
	s->n_cmd = 6;
	break;
      case 0x24:
      case 0x25:
      case 0x28:
      case 0x2a:
      case 0xe0:
      case 0xe1:
	s->n_cmd = 10;
	break;
      default:
	DBG (1, "BUG: cs2_issue_cmd(): Unknown command opcode 0x%02x.\n",
	     s->send_buf[0]);
	break;
      }

  if (s->n_send < s->n_cmd)
    {
      DBG (1,
	   "BUG: cs2_issue_cmd(): Negative number of data out bytes requested.\n");
      return SANE_STATUS_INVAL;
    }

  n_data = s->n_send - s->n_cmd;
  if (s->n_recv > 0)
    {
      if (n_data > 0)
	{
	  DBG (1,
	       "BUG: cs2_issue_cmd(): Both data in and data out requested.\n");
	  return SANE_STATUS_INVAL;
	}
      else
	{
	  n_data = s->n_recv;
	}
    }

  s->recv_buf = (SANE_Byte *) cs2_xrealloc (s->recv_buf, s->n_recv);
  if (!s->recv_buf)
    return SANE_STATUS_NO_MEM;

  switch (s->interface)
    {
    case CS2_INTERFACE_UNKNOWN:
      DBG (1,
	   "BUG: cs2_issue_cmd(): Unknown or uninitialized interface number.\n");
      break;
    case CS2_INTERFACE_SCSI:
      sanei_scsi_cmd2 (s->fd, s->send_buf, s->n_cmd, s->send_buf + s->n_cmd,
		       s->n_send - s->n_cmd, s->recv_buf, &s->n_recv);
      status = SANE_STATUS_GOOD;
      break;
    case CS2_INTERFACE_USB:
      status = sanei_usb_write_bulk (s->fd, s->send_buf, &s->n_cmd);
      switch (cs2_phase_check (s))
	{
	case CS2_PHASE_OUT:
	  if (s->n_send - s->n_cmd < n_data || !n_data)
	    {
	      DBG (4, "Error: cs2_issue_cmd(): Unexpected data out phase.\n");
	      return SANE_STATUS_IO_ERROR;
	    }
	  status =
	    sanei_usb_write_bulk (s->fd, s->send_buf + s->n_cmd, &n_data);
	  break;
	case CS2_PHASE_IN:
	  if (s->n_recv < n_data || !n_data)
	    {
	      DBG (4, "Error: cs2_issue_cmd(): Unexpected data in phase.\n");
	      return SANE_STATUS_IO_ERROR;
	    }
	  status = sanei_usb_read_bulk (s->fd, s->recv_buf, &n_data);
	  s->n_recv = n_data;
	  break;
	default:
	  if (n_data)
	    {
	      DBG (4,
		   "Error: cs2_issue_cmd(): Unexpected non-data phase, but n_data != 0.\n");
	      status_only = 1;
	    }
	  break;
	}
      n_status = 8;
      status = sanei_usb_read_bulk (s->fd, status_buf, &n_status);
      if (n_status != 8)
	{
	  DBG (4,
	       "Error: cs2_issue_cmd(): Failed to read 8 status bytes from USB.\n");
	  return SANE_STATUS_IO_ERROR;
	}
      s->sense_key = status_buf[1] & 0x0f;
      s->sense_asc = status_buf[2] & 0xff;
      s->sense_ascq = status_buf[3] & 0xff;
      s->sense_info = status_buf[4] & 0xff;
      cs2_parse_sense_data (s);
      break;
    }

  if (status_only)
    return SANE_STATUS_IO_ERROR;
  else
    return status;
}

static cs2_phase_t
cs2_phase_check (cs2_t * s)
{
  static SANE_Byte phase_send_buf[1] = { 0xd0 }, phase_recv_buf[1];
  SANE_Status status = 0;
  size_t n = 1;

  status = sanei_usb_write_bulk (s->fd, phase_send_buf, &n);
  status |= sanei_usb_read_bulk (s->fd, phase_recv_buf, &n);

  DBG (6, "cs2_phase_check(): Phase check returned phase = 0x%02x.\n",
       phase_recv_buf[0]);

  if (status)
    return -1;
  else
    return phase_recv_buf[0];
}

static SANE_Status
cs2_scanner_ready (cs2_t * s, int flags)
{
  SANE_Status status = SANE_STATUS_GOOD;
  int i = -1;
  unsigned long count = 0;
  int retry = 3;

  do
    {
      if (i >= 0)		/* dirty !!! */
	usleep (500000);
      cs2_init_buffer (s);
      for (i = 0; i < 6; i++)
	cs2_pack_byte (s, 0x00);
      status = cs2_issue_cmd (s);
      if (status)
	if (--retry < 0)
	  return status;
      if (++count > 240)
	{			/* 120s timeout */
	  DBG (4, "Error: cs2_scanner_ready(): Timeout expired.\n");
	  status = SANE_STATUS_IO_ERROR;
	  break;
	}
    }
  while (s->status & ~flags);	/* until all relevant bits are 0 */

  return status;
}

static SANE_Status
cs2_page_inquiry (cs2_t * s, int page)
{
  SANE_Status status;

  size_t n;

  if (page >= 0)
    {

      cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
      cs2_init_buffer (s);
      cs2_parse_cmd (s, "12 01");
      cs2_pack_byte (s, page);
      cs2_parse_cmd (s, "00 04 00");
      s->n_recv = 4;
      status = cs2_issue_cmd (s);
      if (status)
	{
	  DBG (4,
	       "Error: cs2_page_inquiry(): Inquiry of page size failed: %s.\n",
	       sane_strstatus (status));
	  return status;
	}

      n = s->recv_buf[3] + 4;

    }
  else
    n = 36;

  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
  cs2_init_buffer (s);
  if (page >= 0)
    {
      cs2_parse_cmd (s, "12 01");
      cs2_pack_byte (s, page);
      cs2_parse_cmd (s, "00");
    }
  else
    cs2_parse_cmd (s, "12 00 00 00");
  cs2_pack_byte (s, n);
  cs2_parse_cmd (s, "00");
  s->n_recv = n;
  status = cs2_issue_cmd (s);
  if (status)
    {
      DBG (4, "Error: cs2_page_inquiry(): Inquiry of page failed: %s.\n",
	   sane_strstatus (status));
      return status;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_full_inquiry (cs2_t * s)
{
  SANE_Status status;
  int pitch, pitch_max;
  cs2_pixel_t pixel;

  status = cs2_page_inquiry (s, 0xc1);
  if (status)
    {
      DBG (4, "Error: cs2_full_inquiry(): Failed to get page: %s\n",
	   sane_strstatus (status));
      return status;
    }

  s->maxbits = s->recv_buf[82];
  if (s->type == CS2_TYPE_LS30)	/* must be overridden, LS-30 claims to have 12 bits */
    s->maxbits = 10;

  s->n_lut = 1;
  s->n_lut <<= s->maxbits;
  s->lut_r =
    (cs2_pixel_t *) cs2_xrealloc (s->lut_r, s->n_lut * sizeof (cs2_pixel_t));
  s->lut_g =
    (cs2_pixel_t *) cs2_xrealloc (s->lut_g, s->n_lut * sizeof (cs2_pixel_t));
  s->lut_b =
    (cs2_pixel_t *) cs2_xrealloc (s->lut_b, s->n_lut * sizeof (cs2_pixel_t));
  s->lut_neutral =
    (cs2_pixel_t *) cs2_xrealloc (s->lut_neutral,
				  s->n_lut * sizeof (cs2_pixel_t));

  if (!s->lut_r || !s->lut_g || !s->lut_b || !s->lut_neutral)
    {
      cs2_xfree (s->lut_r);
      cs2_xfree (s->lut_g);
      cs2_xfree (s->lut_b);
      cs2_xfree (s->lut_neutral);
      return SANE_STATUS_NO_MEM;
    }

  for (pixel = 0; pixel < s->n_lut; pixel++)
    s->lut_r[pixel] = s->lut_g[pixel] = s->lut_b[pixel] =
      s->lut_neutral[pixel] = pixel;

  s->resx_optical = 256 * s->recv_buf[18] + s->recv_buf[19];
  s->resx_max = 256 * s->recv_buf[20] + s->recv_buf[21];
  s->resx_min = 256 * s->recv_buf[22] + s->recv_buf[23];
  s->boundaryx =
    65536 * (s->recv_buf[36] + 256 * s->recv_buf[37]) +
    256 * s->recv_buf[38] + s->recv_buf[39];

  s->resy_optical = 256 * s->recv_buf[40] + s->recv_buf[41];
  s->resy_max = 256 * s->recv_buf[42] + s->recv_buf[43];
  s->resy_min = 256 * s->recv_buf[44] + s->recv_buf[45];
  s->boundaryy =
    65536 * (s->recv_buf[58] + 256 * s->recv_buf[59]) +
    256 * s->recv_buf[60] + s->recv_buf[61];

  s->focus_min = 256 * s->recv_buf[76] + s->recv_buf[77];
  s->focus_max = 256 * s->recv_buf[78] + s->recv_buf[79];

  s->n_frames = s->recv_buf[75];

  s->frame_offset = s->resy_max * 1.5 + 1;	/* works for LS-30, maybe not for others */

  /* generate resolution list for x */
  s->resx_n_list = pitch_max = floor (s->resx_max / (double) s->resx_min);
  s->resx_list =
    (unsigned int *) cs2_xrealloc (s->resx_list,
				   pitch_max * sizeof (unsigned int));
  for (pitch = 1; pitch <= pitch_max; pitch++)
    s->resx_list[pitch - 1] = s->resx_max / pitch;

  /* generate resolution list for y */
  s->resy_n_list = pitch_max = floor (s->resy_max / (double) s->resy_min);
  s->resy_list =
    (unsigned int *) cs2_xrealloc (s->resy_list,
				   pitch_max * sizeof (unsigned int));
  for (pitch = 1; pitch <= pitch_max; pitch++)
    s->resy_list[pitch - 1] = s->resy_max / pitch;

  s->unit_dpi = s->resx_max;
  s->unit_mm = 25.4 / s->unit_dpi;

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_execute (cs2_t * s)
{
  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "c1 00 00 00 00 00");
  return cs2_issue_cmd (s);
}

static SANE_Status
cs2_load (cs2_t * s)
{
  SANE_Status status;

  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "e0 00 d1 00 00 00 00 00 0d 00");
  s->n_send += 13;
  status = cs2_grow_send_buffer (s);
  if (status)
    return status;
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  return cs2_execute (s);
}

static SANE_Status
cs2_eject (cs2_t * s)
{
  SANE_Status status;

  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "e0 00 d0 00 00 00 00 00 0d 00");
  s->n_send += 13;
  status = cs2_grow_send_buffer (s);
  if (status)
    return status;
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  return cs2_execute (s);
}

static SANE_Status
cs2_reset (cs2_t * s)
{
  SANE_Status status;

  cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "e0 00 80 00 00 00 00 00 0d 00");
  s->n_send += 13;
  status = cs2_grow_send_buffer (s);
  if (status)
    return status;
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  return cs2_execute (s);
}

static SANE_Status
cs2_focus (cs2_t * s)
{
  SANE_Status status;

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "e0 00 c1 00 00 00 00 00 0d 00 00");
  cs2_pack_byte (s, (s->focus >> 24) & 0xff);
  cs2_pack_byte (s, (s->focus >> 16) & 0xff);
  cs2_pack_byte (s, (s->focus >> 8) & 0xff);
  cs2_pack_byte (s, s->focus & 0xff);
  cs2_parse_cmd (s, "00 00 00 00 00 00 00 00");
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  return cs2_execute (s);
}

static SANE_Status
cs2_autofocus (cs2_t * s)
{
  SANE_Status status;

  cs2_convert_options (s);

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "e0 00 a0 00 00 00 00 00 0d 00 00");
  cs2_pack_byte (s, (s->real_focusx >> 24) & 0xff);
  cs2_pack_byte (s, (s->real_focusx >> 16) & 0xff);
  cs2_pack_byte (s, (s->real_focusx >> 8) & 0xff);
  cs2_pack_byte (s, s->real_focusx & 0xff);
  cs2_pack_byte (s, (s->real_focusy >> 24) & 0xff);
  cs2_pack_byte (s, (s->real_focusy >> 16) & 0xff);
  cs2_pack_byte (s, (s->real_focusy >> 8) & 0xff);
  cs2_pack_byte (s, s->real_focusy & 0xff);
  cs2_parse_cmd (s, "00 00 00 00");
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  status = cs2_execute (s);
  if (status)
    return status;

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "e1 00 c1 00 00 00 00 00 0d 00");
  s->n_recv = 13;
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  s->focus =
    65536 * (256 * s->recv_buf[1] + s->recv_buf[2]) + 256 * s->recv_buf[3] +
    s->recv_buf[4];

  return status;
}

static SANE_Status
cs2_get_exposure (cs2_t * s)
{
  SANE_Status status;
  int i_colour;

  for (i_colour = 0; i_colour < 3; i_colour++)
    {				/* XXXXXXXXXXXXX CCCCCCCCCCCCC */
      cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);

      cs2_init_buffer (s);
      cs2_parse_cmd (s, "25 01 00 00 00");
      cs2_pack_byte (s, cs2_colour_list[i_colour]);
      cs2_parse_cmd (s, "00 00 3a 00");
      s->n_recv = 58;
      status = cs2_issue_cmd (s);
      if (status)
	return status;

      s->real_exposure[cs2_colour_list[i_colour]] =
	65536 * (256 * s->recv_buf[54] + s->recv_buf[55]) +
	256 * s->recv_buf[56] + s->recv_buf[57];

      DBG (6, "cs2_get_exposure(): exposure for colour %i: %li * 10ns\n", cs2_colour_list[i_colour], s->real_exposure[cs2_colour_list[i_colour]]);
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_convert_options (cs2_t * s)
{
  int i_colour;
  unsigned long xmin, xmax, ymin, ymax;
  SANE_Byte *infrared_buf_new;

  s->real_depth = (s->preview ? 8 : s->depth);
  s->bytes_per_pixel = (s->real_depth > 8 ? 2 : 1);
  s->shift_bits = 8 * s->bytes_per_pixel - s->real_depth;

  if (s->preview)
    {
      s->real_resx = s->res_preview;
      s->real_resy = s->res_preview;
    }
  else if (s->res_independent)
    {
      s->real_resx = s->resx;
      s->real_resy = s->resy;
    }
  else
    {
      s->real_resx = s->res;
      s->real_resy = s->res;
    }
  s->real_pitchx = s->resx_max / s->real_resx;
  s->real_pitchy = s->resy_max / s->real_resy;

  s->real_resx = s->resx_max / s->real_pitchx;
  s->real_resy = s->resy_max / s->real_pitchy;

  /* The prefix "real_" refers to data in device units (1/maxdpi), "logical_" refers to resolution-dependent data. */

  if (s->xmin < s->xmax)
    {
      xmin = s->xmin;
      xmax = s->xmax;
    }
  else
    {
      xmin = s->xmax;
      xmax = s->xmin;
    }

  if (s->ymin < s->ymax)
    {
      ymin = s->ymin;
      ymax = s->ymax;
    }
  else
    {
      ymin = s->ymax;
      ymax = s->ymin;
    }

  s->real_xoffset = xmin;
  s->real_yoffset =
    ymin + (s->i_frame - 1) * s->frame_offset + s->subframe / s->unit_mm;
  s->logical_width = (xmax - xmin + 1) / s->real_pitchx;	/* XXXXXXXXX use mm units */
  s->logical_height = (ymax - ymin + 1) / s->real_pitchy;
  s->real_width = s->logical_width * s->real_pitchx;
  s->real_height = s->logical_height * s->real_pitchy;

  s->odd_padding = 0;
  if ((s->bytes_per_pixel == 1) && (s->logical_width & 0x01)
      && (s->type != CS2_TYPE_LS30) && (s->type != CS2_TYPE_LS2000))
    s->odd_padding = 1;

  if (s->focus_on_centre)
    {
      s->real_focusx = s->real_xoffset + s->real_width / 2;
      s->real_focusy = s->real_yoffset + s->real_height / 2;
    }
  else
    {
      s->real_focusx = s->focusx;
      s->real_focusy =
	s->focusy + (s->i_frame - 1) * s->frame_offset +
	s->subframe / s->unit_mm;
    }

  s->real_exposure[1] = s->exposure * s->exposure_r * 100.;
  s->real_exposure[2] = s->exposure * s->exposure_g * 100.;
  s->real_exposure[3] = s->exposure * s->exposure_b * 100.;

  for (i_colour = 0; i_colour < 3; i_colour++)
    if (s->real_exposure[cs2_colour_list[i_colour]] < 1)
      s->real_exposure[cs2_colour_list[i_colour]] = 1;

  s->n_colour_out = s->n_colour_in = 3;	/* XXXXXXXXXXXXXX CCCCCCCCCCCCCC */

  s->xfer_bytes_total =
    s->bytes_per_pixel * s->n_colour_out * s->logical_width *
    s->logical_height;

  if (s->preview)
    s->infrared_stage = s->infrared_next = CS2_INFRARED_OFF;
  else
    {
      if ((s->infrared) && (s->infrared_stage == CS2_INFRARED_OFF))
	s->infrared_next = CS2_INFRARED_IN;

      s->infrared_stage = s->infrared_next;

      if (s->infrared)
	{
	  s->n_colour_in ++;
	  s->n_infrared_buf =
	    s->bytes_per_pixel * s->logical_width * s->logical_height;
	  infrared_buf_new =
	    (SANE_Byte *) cs2_xrealloc (s->infrared_buf, s->n_infrared_buf);
	  if (infrared_buf_new)
	    s->infrared_buf = infrared_buf_new;
	  else
	    return SANE_STATUS_NO_MEM;
	}
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_set_boundary (cs2_t *s)
{
  SANE_Status status;
  int i_boundary;
  unsigned long lvalue;

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "2a 00 88 00 00 03");
  cs2_pack_byte (s, ((4 + s->n_frames * 16) >> 16) & 0xff);
  cs2_pack_byte (s, ((4 + s->n_frames * 16) >> 8) & 0xff);
  cs2_pack_byte (s, (4 + s->n_frames * 16) & 0xff);
  cs2_parse_cmd (s, "00");

  cs2_pack_byte (s, ((4 + s->n_frames * 16) >> 8) & 0xff);
  cs2_pack_byte (s, (4 + s->n_frames * 16) & 0xff);
  cs2_pack_byte (s, s->n_frames);
  cs2_pack_byte (s, s->n_frames);
  for (i_boundary = 0; i_boundary < s->n_frames; i_boundary++)
    {
      lvalue = s->frame_offset * i_boundary + s->subframe / s->unit_mm;
      cs2_pack_byte (s, (lvalue >> 24) & 0xff);
      cs2_pack_byte (s, (lvalue >> 16) & 0xff);
      cs2_pack_byte (s, (lvalue >> 8) & 0xff);
      cs2_pack_byte (s, lvalue & 0xff);

      lvalue = 0;
      cs2_pack_byte (s, (lvalue >> 24) & 0xff);
      cs2_pack_byte (s, (lvalue >> 16) & 0xff);
      cs2_pack_byte (s, (lvalue >> 8) & 0xff);
      cs2_pack_byte (s, lvalue & 0xff);

      lvalue = s->frame_offset * i_boundary + s->subframe / s->unit_mm + s->frame_offset - 1;
      cs2_pack_byte (s, (lvalue >> 24) & 0xff);
      cs2_pack_byte (s, (lvalue >> 16) & 0xff);
      cs2_pack_byte (s, (lvalue >> 8) & 0xff);
      cs2_pack_byte (s, lvalue & 0xff);

      lvalue = s->boundaryx - 1;
      cs2_pack_byte (s, (lvalue >> 24) & 0xff);
      cs2_pack_byte (s, (lvalue >> 16) & 0xff);
      cs2_pack_byte (s, (lvalue >> 8) & 0xff);
      cs2_pack_byte (s, lvalue & 0xff);
    }
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  return SANE_STATUS_GOOD;
}

static SANE_Status
cs2_scan (cs2_t * s, cs2_scan_t type)
{
  SANE_Status status;
  int i_colour;
  cs2_pixel_t pixel;
  cs2_pixel_t *lut;

  /* wait for device to be ready with document, and set device unit */

  status = cs2_scanner_ready (s, CS2_STATUS_NO_DOCS);
  if (status)
    return status;
  if (s->status & CS2_STATUS_NO_DOCS)
    return SANE_STATUS_NO_DOCS;

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  cs2_parse_cmd (s, "15 10 00 00 0c 00 0b 00 00 00 03 06 00 00");
  cs2_pack_byte (s, (s->unit_dpi >> 8) & 0xff);
  cs2_pack_byte (s, s->unit_dpi & 0xff);
  cs2_parse_cmd (s, "00 00");
  status = cs2_issue_cmd (s);
  if (status)
    return status;

  status = cs2_convert_options (s);
  if (status)
    return status;

  status = cs2_set_boundary (s);
  if (status)
    return status;

  switch (type)
    {
    case CS2_SCAN_NORMAL:

      for (i_colour = 0; i_colour < s->n_colour_in; i_colour++)
	{
	  cs2_scanner_ready (s, CS2_STATUS_READY);

	  switch (i_colour)
	    {
	    case 0:
	      lut = s->lut_r;
	      break;
	    case 1:
	      lut = s->lut_g;
	      break;
	    case 2:
	      lut = s->lut_b;
	      break;
	    case 3:
	      lut = s->lut_neutral;
	      break;
	    default:
	      DBG (1,
		   "BUG: cs2_scan(): Unknown colour number for LUT download.\n");
	      return SANE_STATUS_INVAL;
	      break;
	    }

	  cs2_init_buffer (s);
	  cs2_parse_cmd (s, "2a 00 03 00");
	  cs2_pack_byte (s, cs2_colour_list[i_colour]);
	  cs2_pack_byte (s, 2 - 1);	/* XXXXXXXXXX number of bytes per data point - 1 */
	  cs2_pack_byte (s, ((2 * s->n_lut) >> 16) & 0xff);	/* XXXXXXXXXX 2 bytes per point */
	  cs2_pack_byte (s, ((2 * s->n_lut) >> 8) & 0xff);	/* XXXXXXXXXX 2 bytes per point */
	  cs2_pack_byte (s, (2 * s->n_lut) & 0xff);	/* XXXXXXXXXX 2 bytes per point */
	  cs2_pack_byte (s, 0x00);

	  for (pixel = 0; pixel < s->n_lut; pixel++)
	    {			/* XXXXXXXXXXXXXXX 2 bytes per point */
	      cs2_pack_byte (s, (lut[pixel] >> 8) & 0xff);
	      cs2_pack_byte (s, lut[pixel] & 0xff);
	    }

	  status = cs2_issue_cmd (s);
	  if (status)
	    return status;
	}

      break;

    default:
      break;
    }

  for (i_colour = 0; i_colour < s->n_colour_in; i_colour++)
    {
      cs2_scanner_ready (s, CS2_STATUS_READY);

      cs2_init_buffer (s);
      if ((s->type == CS2_TYPE_LS40) || (s->type == CS2_TYPE_LS4000))
	cs2_parse_cmd (s, "24 00 00 00 00 00 00 00 3a 80");
      else
	cs2_parse_cmd (s, "24 00 00 00 00 00 00 00 3a 00");
      cs2_parse_cmd (s, "00 00 00 00 00 00 00 32");

      cs2_pack_byte (s, cs2_colour_list[i_colour]);

      cs2_pack_byte (s, 0x00);

      cs2_pack_byte (s, s->real_resx >> 8);
      cs2_pack_byte (s, s->real_resx & 0xff);
      cs2_pack_byte (s, s->real_resy >> 8);
      cs2_pack_byte (s, s->real_resy & 0xff);

      cs2_pack_byte (s, (s->real_xoffset >> 24) & 0xff);
      cs2_pack_byte (s, (s->real_xoffset >> 16) & 0xff);
      cs2_pack_byte (s, (s->real_xoffset >> 8) & 0xff);
      cs2_pack_byte (s, s->real_xoffset & 0xff);

      cs2_pack_byte (s, (s->real_yoffset >> 24) & 0xff);
      cs2_pack_byte (s, (s->real_yoffset >> 16) & 0xff);
      cs2_pack_byte (s, (s->real_yoffset >> 8) & 0xff);
      cs2_pack_byte (s, s->real_yoffset & 0xff);

      cs2_pack_byte (s, (s->real_width >> 24) & 0xff);
      cs2_pack_byte (s, (s->real_width >> 16) & 0xff);
      cs2_pack_byte (s, (s->real_width >> 8) & 0xff);
      cs2_pack_byte (s, s->real_width & 0xff);

      cs2_pack_byte (s, (s->real_height >> 24) & 0xff);
      cs2_pack_byte (s, (s->real_height >> 16) & 0xff);
      cs2_pack_byte (s, (s->real_height >> 8) & 0xff);
      cs2_pack_byte (s, s->real_height & 0xff);

      cs2_pack_byte (s, 0x00);	/* brightness, etc. */
      cs2_pack_byte (s, 0x00);
      cs2_pack_byte (s, 0x00);
      cs2_pack_byte (s, 0x05);	/* image composition CCCCCCC */
      cs2_pack_byte (s, s->real_depth);	/* pixel composition */
      cs2_parse_cmd (s, "00 00 00 00 00 00 00 00 00 00 00 00 00");
      cs2_pack_byte (s, 0x00);	/* multiread, ordering */
      cs2_pack_byte (s, 0x80 + (s->negative ? 0 : 1));	/* averaging, pos/neg */
      switch (type)
	{			/* scanning kind */
	case CS2_SCAN_NORMAL:
	  cs2_pack_byte (s, 0x01);
	  break;
	case CS2_SCAN_AE:
	  cs2_pack_byte (s, 0x20);
	  break;
	case CS2_SCAN_AE_WB:
	  cs2_pack_byte (s, 0x40);
	  break;
	default:
	  DBG (1, "BUG: cs2_scan(): Unknown scanning type.\n");
	  return SANE_STATUS_INVAL;
	}
      cs2_pack_byte (s, 0x02);	/* scanning mode */
      cs2_pack_byte (s, 0x02);	/* colour interleaving */
      cs2_pack_byte (s, 0xff);	/* (ae) */
      if (i_colour == 3)	/* infrared */
	cs2_parse_cmd (s, "00 00 00 00");	/* automatic */
      else
	{
	  cs2_pack_byte (s,
			 (s->
			  real_exposure[cs2_colour_list[i_colour]] >> 24) &
			 0xff);
	  cs2_pack_byte (s,
			 (s->
			  real_exposure[cs2_colour_list[i_colour]] >> 16) &
			 0xff);
	  cs2_pack_byte (s,
			 (s->
			  real_exposure[cs2_colour_list[i_colour]] >> 8) &
			 0xff);
	  cs2_pack_byte (s,
			 s->real_exposure[cs2_colour_list[i_colour]] & 0xff);
	}
      status = cs2_issue_cmd (s);
      if (status)
	return status;
    }

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_focus (s);

  cs2_scanner_ready (s, CS2_STATUS_READY);
  cs2_init_buffer (s);
  switch (s->n_colour_in)
    {
    case 3:
      cs2_parse_cmd (s, "1b 00 00 00 03 00 01 02 03");
      break;
    case 4:
      cs2_parse_cmd (s, "1b 00 00 00 04 00 01 02 03 09");
      break;
    default:
      DBG (1, "BUG: cs2_scan(): Unknown number of input colours.\n");
      break;
    }
  status = cs2_issue_cmd (s);
  if (status)
    return status;
  if (s->status == CS2_STATUS_REISSUE)
    {
      status = cs2_issue_cmd (s);
      if (status)
	return status;
    }

  return SANE_STATUS_GOOD;
}

static void *
cs2_xmalloc (size_t size)
{
  register void *value = malloc (size);

  if (!value)
    DBG (0, "Error: cs2_xmalloc(): Failed to malloc() %lu bytes.\n",
	 (unsigned long) size);

  return value;
}

static void *
cs2_xrealloc (void *p, size_t size)
{
  register void *value;

  if (!size)
    return p;

  value = realloc (p, size);

  if (!value)
    DBG (0, "Error: cs2_xrealloc(): Failed to realloc() %lu bytes.\n",
	 (unsigned long) size);

  return value;
}

static void
cs2_xfree (const void *p)
{
  if (p)
    free ((void *) p);
}
