/* sane - Scanner Access Now Easy.

   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 HP ScanJet 3500 series scanners.
   Currently supported:
    - HP ScanJet 3500C
    - HP ScanJet 3530C
    - HP ScanJet 3570C

   SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach scanners
   . - sane_get_devices() : query list of scanner devices
   . - sane_open() : open a particular scanner device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get scanner 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_cancel() : cancel operation
   . - sane_close() : close opened scanner device
   - sane_exit() : terminate use of backend


   There are some device specific routines in this file that are in "#if 0"
   sections - these are left in place for documentation purposes in case
   somebody wants to implement features that use those routines.

*/

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

#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 <ctype.h>
#include <time.h>
#include <math.h>

#include <sys/types.h>
#include <unistd.h>
#ifdef HAVE_LIBC_H
# include <libc.h>		/* NeXTStep/OpenStep */
#endif

#include "../include/sane/sane.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_config.h"
#include "../include/sane/sanei_thread.h"
#include "../include/sane/sanei_backend.h"

#define RTCMD_GETREG		0x80
#define	RTCMD_READSRAM		0x81

#define	RTCMD_SETREG		0x88
#define	RTCMD_WRITESRAM		0x89

#define	RTCMD_NVRAMCONTROL	0x8a

#define	RTCMD_BYTESAVAIL	0x90
#define	RTCMD_READBYTES		0x91

#define	RT_CHANNEL_ALL		0
#define	RT_CHANNEL_RED		1
#define	RT_CHANNEL_GREEN	2
#define	RT_CHANNEL_BLUE		3

typedef int (*rts8801_callback) (void *param, unsigned bytes, void *data);

#define DEBUG 1
#define SCANNER_UNIT_TO_FIXED_MM(number) SANE_FIX(number * MM_PER_INCH / 1200)
#define FIXED_MM_TO_SCANNER_UNIT(number) SANE_UNFIX(number) * 1200 / MM_PER_INCH

#define MSG_ERR         1
#define MSG_USER        5
#define MSG_INFO        6
#define FLOW_CONTROL    10
#define MSG_IO          15
#define MSG_IO_READ     17
#define IO_CMD          20
#define IO_CMD_RES      20
#define MSG_GET         25
/* ------------------------------------------------------------------------- */

enum hp3500_option
{
  OPT_NUM_OPTS = 0,

  OPT_RESOLUTION,
  OPT_GEOMETRY_GROUP,
  OPT_TL_X,
  OPT_TL_Y,
  OPT_BR_X,
  OPT_BR_Y,
  OPT_MODE_GROUP,
  OPT_MODE,
  OPT_BRIGHTNESS,
  OPT_CONTRAST,
  OPT_GAMMA,

  NUM_OPTIONS
};

typedef struct
{
  int left;
  int top;
  int right;
  int bottom;
} hp3500_rect;

struct hp3500_data
{
  struct hp3500_data *next;
  char *devicename;

  int sfd;
  int pipe_r;
  int pipe_w;
  SANE_Pid reader_pid;

  int resolution;
  int mode;

  time_t last_scan;

  hp3500_rect request_mm;
  hp3500_rect actual_mm;
  hp3500_rect fullres_pixels;
  hp3500_rect actres_pixels;

  int rounded_left;
  int rounded_top;
  int rounded_right;
  int rounded_bottom;

  int bytes_per_scan_line;
  int scan_width_pixels;
  int scan_height_pixels;

  int brightness;
  int contrast;

  double gamma;

  SANE_Option_Descriptor opt[NUM_OPTIONS];
  SANE_Device sane;
};

struct hp3500_write_info
{
  struct hp3500_data *scanner;
  int bytesleft;
};

typedef struct detailed_calibration_data
{
  unsigned char const *channeldata[3];
  unsigned resolution_divisor;
} detailed_calibration_data;

static struct hp3500_data *first_dev = 0;
static struct hp3500_data **new_dev = &first_dev;
static int num_devices = 0;
static SANE_Int res_list[] =
  { 9, 50, 75, 100, 150, 200, 300, 400, 600, 1200 };
static const SANE_Range range_x =
  { 0, SANE_FIX (215.9), SANE_FIX (MM_PER_INCH / 1200) };
static const SANE_Range range_y =
  { 0, SANE_FIX (298.7), SANE_FIX (MM_PER_INCH / 1200) };
static const SANE_Range range_brightness =
  { 0, 255, 0 };
static const SANE_Range range_contrast =
  { 0, 255, 0 };
static const SANE_Range range_gamma =
  { SANE_FIX (0.2), SANE_FIX(4.0), SANE_FIX(0.01) };


#define HP3500_COLOR_SCAN 0
#define HP3500_GRAY_SCAN 1
#define	HP3500_LINEART_SCAN 2
#define HP3500_TOTAL_SCANS 3

static char const *scan_mode_list[HP3500_TOTAL_SCANS + 1] = { 0 };

static SANE_Status attachScanner (const char *name);
static SANE_Status init_options (struct hp3500_data *scanner);
static int reader_process (void *);
static void calculateDerivedValues (struct hp3500_data *scanner);
static void do_reset (struct hp3500_data *scanner);
static void do_cancel (struct hp3500_data *scanner);
static size_t max_string_size(char const **);

/*
 * used by sane_get_devices
 */
static const SANE_Device **devlist = 0;

/*
 * SANE Interface
 */


/**
 * Called by SANE initially.
 * 
 * From the SANE spec:
 * This function must be called before any other SANE function can be
 * called. The behavior of a SANE backend is undefined if this
 * function is not called first. The version code of the backend is
 * returned in the value pointed to by version_code. If that pointer
 * is NULL, no version code is returned. Argument authorize is either
 * a pointer to a function that is invoked when the backend requires
 * authentication for a specific resource or NULL if the frontend does
 * not support authentication.
 */
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  authorize = authorize;	/* get rid of compiler warning */

  DBG_INIT ();
  DBG (10, "sane_init\n");

  sanei_usb_init ();
  sanei_thread_init ();

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

  sanei_usb_find_devices (0x03f0, 0x2205, attachScanner);
  sanei_usb_find_devices (0x03f0, 0x2005, attachScanner);

  return SANE_STATUS_GOOD;
}


/**
 * Called by SANE to find out about supported devices.
 * 
 * From the SANE spec:
 * This function can be used to query the list of devices that are
 * available. If the function executes successfully, it stores a
 * pointer to a NULL terminated array of pointers to SANE_Device
 * structures in *device_list. The returned list is guaranteed to
 * remain unchanged and valid until (a) another call to this function
 * is performed or (b) a call to sane_exit() is performed. This
 * function can be called repeatedly to detect when new devices become
 * available. If argument local_only is true, only local devices are
 * returned (devices directly attached to the machine that SANE is
 * running on). If it is false, the device list includes all remote
 * devices that are accessible to the SANE library.
 * 
 * SANE does not require that this function is called before a
 * sane_open() call is performed. A device name may be specified
 * explicitly by a user which would make it unnecessary and
 * undesirable to call this function first.
 */
SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  int i;
  struct hp3500_data *dev;

  DBG (10, "sane_get_devices %d\n", local_only);

  if (devlist)
    free (devlist);
  devlist = calloc (num_devices + 1, sizeof (SANE_Device *));
  if (!devlist)
    return SANE_STATUS_NO_MEM;

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

  *device_list = devlist;

  return SANE_STATUS_GOOD;
}


/**
 * Called to establish connection with the scanner. This function will
 * also establish meaningful defauls and initialize the options.
 *
 * From the SANE spec:
 * This function is used to establish a connection to a particular
 * device. The name of the device to be opened is passed in argument
 * name. If the call completes successfully, a handle for the device
 * is returned in *h. As a special case, specifying a zero-length
 * string as the device requests opening the first available device
 * (if there is such a device).
 */
SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * handle)
{
  struct hp3500_data *dev = NULL;
  struct hp3500_data *scanner = NULL;

  if (name[0] == 0)
    {
      DBG (10, "sane_open: no device requested, using default\n");
      if (first_dev)
	{
	  scanner = (struct hp3500_data *) first_dev;
	  DBG (10, "sane_open: device %s found\n", first_dev->sane.name);
	}
    }
  else
    {
      DBG (10, "sane_open: device %s requested\n", name);

      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, name) == 0)
	    {
	      DBG (10, "sane_open: device %s found\n", name);
	      scanner = (struct hp3500_data *) dev;
	    }
	}
    }

  if (!scanner)
    {
      DBG (10, "sane_open: no device found\n");
      return SANE_STATUS_INVAL;
    }

  *handle = scanner;

  init_options (scanner);

  scanner->resolution = 200;
  scanner->request_mm.left = 0;
  scanner->request_mm.top = 0;
  scanner->request_mm.right = SCANNER_UNIT_TO_FIXED_MM (10200);
  scanner->request_mm.bottom = SCANNER_UNIT_TO_FIXED_MM (14100);
  scanner->mode = 0;
  scanner->brightness = 128;
  scanner->contrast = 64;
  scanner->gamma = 2.2;
  calculateDerivedValues (scanner);

  return SANE_STATUS_GOOD;

}


/**
 * An advanced method we don't support but have to define.
 */
SANE_Status
sane_set_io_mode (SANE_Handle h, SANE_Bool non_blocking)
{
  DBG (10, "sane_set_io_mode\n");
  DBG (99, "%d %p\n", non_blocking, h);
  return SANE_STATUS_UNSUPPORTED;
}


/**
 * An advanced method we don't support but have to define.
 */
SANE_Status
sane_get_select_fd (SANE_Handle h, SANE_Int * fdp)
{
  struct hp3500_data *scanner = (struct hp3500_data *) h;
  DBG (10, "sane_get_select_fd\n");
  *fdp = scanner->pipe_r;
  DBG (99, "%p %d\n", h, *fdp);
  return SANE_STATUS_GOOD;
}


/**
 * Returns the options we know.
 *
 * From the SANE spec:
 * This function is used to access option descriptors. The function
 * returns the option descriptor for option number n of the device
 * represented by handle h. Option number 0 is guaranteed to be a
 * valid option. Its value is an integer that specifies the number of
 * options that are available for device handle h (the count includes
 * option 0). If n is not a valid option index, the function returns
 * NULL. The returned option descriptor is guaranteed to remain valid
 * (and at the returned address) until the device is closed.
 */
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  struct hp3500_data *scanner = handle;

  DBG (MSG_GET,
       "sane_get_option_descriptor: \"%s\"\n", scanner->opt[option].name);

  if ((unsigned) option >= NUM_OPTIONS)
    return NULL;
  return &scanner->opt[option];
}


/**
 * Gets or sets an option value.
 * 
 * From the SANE spec:
 * This function is used to set or inquire the current value of option
 * number n of the device represented by handle h. The manner in which
 * the option is controlled is specified by parameter action. The
 * possible values of this parameter are described in more detail
 * below.  The value of the option is passed through argument val. It
 * is a pointer to the memory that holds the option value. The memory
 * area pointed to by v must be big enough to hold the entire option
 * value (determined by member size in the corresponding option
 * descriptor).
 * 
 * The only exception to this rule is that when setting the value of a
 * string option, the string pointed to by argument v may be shorter
 * since the backend will stop reading the option value upon
 * encountering the first NUL terminator in the string. If argument i
 * is not NULL, the value of *i will be set to provide details on how
 * well the request has been met.
 */
SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  struct hp3500_data *scanner = (struct hp3500_data *) handle;
  SANE_Status status;
  SANE_Word cap;
  SANE_Int dummy;
  int i;

  /* Make sure that all those statements involving *info cannot break (better
   * than having to do "if (info) ..." everywhere!)
   */
  if (info == 0)
    info = &dummy;

  *info = 0;

  if (option >= NUM_OPTIONS)
    return SANE_STATUS_INVAL;

  cap = scanner->opt[option].cap;

  /*
   * SANE_ACTION_GET_VALUE: We have to find out the current setting and
   * return it in a human-readable form (often, text).
   */
  if (action == SANE_ACTION_GET_VALUE)
    {
      DBG (MSG_GET, "sane_control_option: get value \"%s\"\n",
	   scanner->opt[option].name);
      DBG (11, "\tcap = %d\n", cap);

      if (!SANE_OPTION_IS_ACTIVE (cap))
	{
	  DBG (10, "\tinactive\n");
	  return SANE_STATUS_INVAL;
	}

      switch (option)
	{
	case OPT_NUM_OPTS:
	  *(SANE_Word *) val = NUM_OPTIONS;
	  return SANE_STATUS_GOOD;

	case OPT_RESOLUTION:
	  *(SANE_Word *) val = scanner->resolution;
	  return SANE_STATUS_GOOD;

	case OPT_TL_X:
	  *(SANE_Word *) val = scanner->request_mm.left;
	  return SANE_STATUS_GOOD;

	case OPT_TL_Y:
	  *(SANE_Word *) val = scanner->request_mm.top;
	  return SANE_STATUS_GOOD;

	case OPT_BR_X:
	  *(SANE_Word *) val = scanner->request_mm.right;
	  return SANE_STATUS_GOOD;

	case OPT_BR_Y:
	  *(SANE_Word *) val = scanner->request_mm.bottom;
	  return SANE_STATUS_GOOD;

	case OPT_MODE:
	  strcpy ((SANE_Char *) val, scan_mode_list[scanner->mode]);
	  return SANE_STATUS_GOOD;

	case OPT_CONTRAST:
	  *(SANE_Word *) val = scanner->contrast;
	  return SANE_STATUS_GOOD;

        case OPT_GAMMA:
          *(SANE_Word *) val = SANE_FIX(scanner->gamma);
	  return SANE_STATUS_GOOD;

	case OPT_BRIGHTNESS:
	  *(SANE_Word *) val = scanner->brightness;
	  return SANE_STATUS_GOOD;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      DBG (10, "sane_control_option: set value \"%s\"\n",
	   scanner->opt[option].name);

      if (!SANE_OPTION_IS_ACTIVE (cap))
	{
	  DBG (10, "\tinactive\n");
	  return SANE_STATUS_INVAL;
	}

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (10, "\tnot settable\n");
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (scanner->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (10, "\tbad value\n");
	  return status;
	}

      /*
       * Note - for those options which can assume one of a list of
       * valid values, we can safely assume that they will have
       * exactly one of those values because that's what
       * sanei_constrain_value does. Hence no "else: invalid" branches
       * below.
       */
      switch (option)
	{
	case OPT_RESOLUTION:
	  if (scanner->resolution == *(SANE_Word *) val)
	    {
	      return SANE_STATUS_GOOD;
	    }
	  scanner->resolution = (*(SANE_Word *) val);
	  calculateDerivedValues (scanner);
	  *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_TL_X:
	  if (scanner->request_mm.left == *(SANE_Word *) val)
	    return SANE_STATUS_GOOD;
	  scanner->request_mm.left = *(SANE_Word *) val;
	  calculateDerivedValues (scanner);
	  if (scanner->actual_mm.left != scanner->request_mm.left)
	    *info |= SANE_INFO_INEXACT;
	  *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_TL_Y:
	  if (scanner->request_mm.top == *(SANE_Word *) val)
	    return SANE_STATUS_GOOD;
	  scanner->request_mm.top = *(SANE_Word *) val;
	  calculateDerivedValues (scanner);
	  if (scanner->actual_mm.top != scanner->request_mm.top)
	    *info |= SANE_INFO_INEXACT;
	  *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_BR_X:
	  if (scanner->request_mm.right == *(SANE_Word *) val)
	    {
	      return SANE_STATUS_GOOD;
	    }
	  scanner->request_mm.right = *(SANE_Word *) val;
	  calculateDerivedValues (scanner);
	  if (scanner->actual_mm.right != scanner->request_mm.right)
	    *info |= SANE_INFO_INEXACT;
	  *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_BR_Y:
	  if (scanner->request_mm.bottom == *(SANE_Word *) val)
	    {
	      return SANE_STATUS_GOOD;
	    }
	  scanner->request_mm.bottom = *(SANE_Word *) val;
	  calculateDerivedValues (scanner);
	  if (scanner->actual_mm.bottom != scanner->request_mm.bottom)
	    *info |= SANE_INFO_INEXACT;
	  *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_MODE:
	  for (i = 0; scan_mode_list[i]; ++i)
	    {
	      if (!strcmp ((SANE_Char const *) val, scan_mode_list[i]))
		{
		  DBG (10, "Setting scan mode to %s (request: %s)\n",
		       scan_mode_list[i], (SANE_Char const *) val);
		  scanner->mode = i;
		  return SANE_STATUS_GOOD;
		}
	    }
	  /* Impossible */
	  return SANE_STATUS_INVAL;

	case OPT_BRIGHTNESS:
	  scanner->brightness = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	case OPT_CONTRAST:
	  scanner->contrast = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

        case OPT_GAMMA:
          scanner->gamma = SANE_UNFIX(*(SANE_Word *) val);
          return SANE_STATUS_GOOD;
	}			/* switch */
    }				/* else */
  return SANE_STATUS_INVAL;
}

/**
 * Called by SANE when a page acquisition operation is to be started.
 *
 */
SANE_Status
sane_start (SANE_Handle handle)
{
  struct hp3500_data *scanner = handle;
  int defaultFds[2];
  int ret;

  DBG (10, "sane_start\n");

  if (scanner->sfd < 0)
    {
      /* first call */
      DBG (10, "sane_start opening USB device\n");
      if (sanei_usb_open (scanner->sane.name, &(scanner->sfd)) !=
	  SANE_STATUS_GOOD)
	{
	  DBG (MSG_ERR,
	       "sane_start: open of %s failed:\n", scanner->sane.name);
	  return SANE_STATUS_INVAL;
	}
    }

  calculateDerivedValues (scanner);

  DBG (10, "\tbytes per line = %d\n", scanner->bytes_per_scan_line);
  DBG (10, "\tpixels_per_line = %d\n", scanner->scan_width_pixels);
  DBG (10, "\tlines = %d\n", scanner->scan_height_pixels);


  /* create a pipe, fds[0]=read-fd, fds[1]=write-fd */
  if (pipe (defaultFds) < 0)
    {
      DBG (MSG_ERR, "ERROR: could not create pipe\n");
      do_cancel (scanner);
      return SANE_STATUS_IO_ERROR;
    }

  scanner->pipe_r = defaultFds[0];
  scanner->pipe_w = defaultFds[1];

  ret = SANE_STATUS_GOOD;

  scanner->reader_pid = sanei_thread_begin (reader_process, scanner);
  time (&scanner->last_scan);

  if (!sanei_thread_is_valid (scanner->reader_pid))
    {
      DBG (MSG_ERR, "cannot fork reader process.\n");
      DBG (MSG_ERR, "%s", strerror (errno));
      ret = SANE_STATUS_IO_ERROR;
    }

  if (sanei_thread_is_forked ())
    {
      close (scanner->pipe_w);
    }

  if (ret == SANE_STATUS_GOOD)
    {
      DBG (10, "sane_start: ok\n");
    }

  return ret;
}


/**
 * Called by SANE to retrieve information about the type of data
 * that the current scan will return.
 *
 * From the SANE spec:
 * This function is used to obtain the current scan parameters. The
 * returned parameters are guaranteed to be accurate between the time
 * a scan has been started (sane_start() has been called) and the
 * completion of that request. Outside of that window, the returned
 * values are best-effort estimates of what the parameters will be
 * when sane_start() gets invoked.
 * 
 * Calling this function before a scan has actually started allows,
 * for example, to get an estimate of how big the scanned image will
 * be. The parameters passed to this function are the handle h of the
 * device for which the parameters should be obtained and a pointer p
 * to a parameter structure.
 */
SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  struct hp3500_data *scanner = (struct hp3500_data *) handle;


  DBG (10, "sane_get_parameters\n");

  calculateDerivedValues (scanner);

  params->format =
    (scanner->mode == HP3500_COLOR_SCAN) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
  params->depth = (scanner->mode == HP3500_LINEART_SCAN) ? 1 : 8;

  params->pixels_per_line = scanner->scan_width_pixels;
  params->lines = scanner->scan_height_pixels;

  params->bytes_per_line = scanner->bytes_per_scan_line;

  params->last_frame = 1;
  DBG (10, "\tdepth %d\n", params->depth);
  DBG (10, "\tlines %d\n", params->lines);
  DBG (10, "\tpixels_per_line %d\n", params->pixels_per_line);
  DBG (10, "\tbytes_per_line %d\n", params->bytes_per_line);
  return SANE_STATUS_GOOD;
}


/**
 * Called by SANE to read data.
 * 
 * In this implementation, sane_read does nothing much besides reading
 * data from a pipe and handing it back. On the other end of the pipe
 * there's the reader process which gets data from the scanner and
 * stuffs it into the pipe.
 * 
 * From the SANE spec:
 * This function is used to read image data from the device
 * represented by handle h.  Argument buf is a pointer to a memory
 * area that is at least maxlen bytes long.  The number of bytes
 * returned is stored in *len. A backend must set this to zero when
 * the call fails (i.e., when a status other than SANE_STATUS_GOOD is
 * returned).
 * 
 * When the call succeeds, the number of bytes returned can be
 * anywhere in the range from 0 to maxlen bytes.
 */
SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf,
	   SANE_Int max_len, SANE_Int * len)
{
  struct hp3500_data *scanner = (struct hp3500_data *) handle;
  ssize_t nread;
  int source = scanner->pipe_r;

  *len = 0;

  nread = read (source, buf, max_len);
  DBG (30, "sane_read: read %ld bytes of %ld\n",
       (long) nread, (long) max_len);

  if (nread < 0)
    {
      if (errno == EAGAIN)
	{
	  return SANE_STATUS_GOOD;
	}
      else
	{
	  do_cancel (scanner);
	  return SANE_STATUS_IO_ERROR;
	}
    }

  *len = nread;

  if (nread == 0)
    {
      close (source);
      DBG (10, "sane_read: pipe closed\n");
      return SANE_STATUS_EOF;
    }

  return SANE_STATUS_GOOD;
}				/* sane_read */


/**
 * Cancels a scan. 
 *
 * It has been said on the mailing list that sane_cancel is a bit of a
 * misnomer because it is routinely called to signal the end of a
 * batch - quoting David Mosberger-Tang:
 * 
 * > In other words, the idea is to have sane_start() be called, and
 * > collect as many images as the frontend wants (which could in turn
 * > consist of multiple frames each as indicated by frame-type) and
 * > when the frontend is done, it should call sane_cancel(). 
 * > Sometimes it's better to think of sane_cancel() as "sane_stop()"
 * > but that name would have had some misleading connotations as
 * > well, that's why we stuck with "cancel".
 * 
 * The current consensus regarding duplex and ADF scans seems to be
 * the following call sequence: sane_start; sane_read (repeat until
 * EOF); sane_start; sane_read...  and then call sane_cancel if the
 * batch is at an end. I.e. do not call sane_cancel during the run but
 * as soon as you get a SANE_STATUS_NO_DOCS.
 * 
 * From the SANE spec:
 * This function is used to immediately or as quickly as possible
 * cancel the currently pending operation of the device represented by
 * handle h.  This function can be called at any time (as long as
 * handle h is a valid handle) but usually affects long-running
 * operations only (such as image is acquisition). It is safe to call
 * this function asynchronously (e.g., from within a signal handler).
 * It is important to note that completion of this operaton does not
 * imply that the currently pending operation has been cancelled. It
 * only guarantees that cancellation has been initiated. Cancellation
 * completes only when the cancelled call returns (typically with a
 * status value of SANE_STATUS_CANCELLED).  Since the SANE API does
 * not require any other operations to be re-entrant, this implies
 * that a frontend must not call any other operation until the
 * cancelled operation has returned.
 */
void
sane_cancel (SANE_Handle h)
{
  DBG (10, "sane_cancel\n");
  do_cancel ((struct hp3500_data *) h);
}


/**
 * Ends use of the scanner.
 * 
 * From the SANE spec:
 * This function terminates the association between the device handle
 * passed in argument h and the device it represents. If the device is
 * presently active, a call to sane_cancel() is performed first. After
 * this function returns, handle h must not be used anymore.
 */
void
sane_close (SANE_Handle handle)
{
  DBG (10, "sane_close\n");
  do_reset (handle);
  do_cancel (handle);
}


/**
 * Terminates the backend.
 * 
 * From the SANE spec:
 * This function must be called to terminate use of a backend. The
 * function will first close all device handles that still might be
 * open (it is recommended to close device handles explicitly through
 * a call to sane_clo-se(), but backends are required to release all
 * resources upon a call to this function). After this function
 * returns, no function other than sane_init() may be called
 * (regardless of the status value returned by sane_exit(). Neglecting
 * to call this function may result in some resources not being
 * released properly.
 */
void
sane_exit (void)
{
  struct hp3500_data *dev, *next;

  DBG (10, "sane_exit\n");

  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      free (dev->devicename);
      free (dev);
    }

  if (devlist)
    free (devlist);
}

/*
 * The scanning code
 */

static SANE_Status
attachScanner (const char *devicename)
{
  struct hp3500_data *dev;

  DBG (15, "attach_scanner: %s\n", devicename);

  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  DBG (5, "attach_scanner: scanner already attached (is ok)!\n");
	  return SANE_STATUS_GOOD;
	}
    }


  if (NULL == (dev = malloc (sizeof (*dev))))
    return SANE_STATUS_NO_MEM;
  memset (dev, 0, sizeof (*dev));

  dev->devicename = strdup (devicename);
  dev->sfd = -1;
  dev->last_scan = 0;
  dev->reader_pid = (SANE_Pid) -1;
  dev->pipe_r = dev->pipe_w = -1;

  dev->sane.name = dev->devicename;
  dev->sane.vendor = "Hewlett-Packard";
  dev->sane.model = "ScanJet 3500";
  dev->sane.type = "scanner";

  ++num_devices;
  *new_dev = dev;

  DBG (15, "attach_scanner: done\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
init_options (struct hp3500_data *scanner)
{
  int i;
  SANE_Option_Descriptor *opt;

  memset (scanner->opt, 0, sizeof (scanner->opt));

  for (i = 0; i < NUM_OPTIONS; ++i)
    {
      scanner->opt[i].name = "filler";
      scanner->opt[i].size = sizeof (SANE_Word);
      scanner->opt[i].cap = SANE_CAP_INACTIVE;
    }

  opt = scanner->opt + OPT_NUM_OPTS;
  opt->title = SANE_TITLE_NUM_OPTIONS;
  opt->desc = SANE_DESC_NUM_OPTIONS;
  opt->type = SANE_TYPE_INT;
  opt->cap = SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_RESOLUTION;
  opt->name = SANE_NAME_SCAN_RESOLUTION;
  opt->title = SANE_TITLE_SCAN_RESOLUTION;
  opt->desc = SANE_DESC_SCAN_RESOLUTION;
  opt->type = SANE_TYPE_INT;
  opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
  opt->constraint.word_list = res_list;
  opt->unit = SANE_UNIT_DPI;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_GEOMETRY_GROUP;
  opt->title = SANE_I18N ("Geometry");
  opt->desc = SANE_I18N ("Geometry Group");
  opt->type = SANE_TYPE_GROUP;
  opt->constraint_type = SANE_CONSTRAINT_NONE;

  opt = scanner->opt + OPT_TL_X;
  opt->name = SANE_NAME_SCAN_TL_X;
  opt->title = SANE_TITLE_SCAN_TL_X;
  opt->desc = SANE_DESC_SCAN_TL_X;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_x;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_TL_Y;
  opt->name = SANE_NAME_SCAN_TL_Y;
  opt->title = SANE_TITLE_SCAN_TL_Y;
  opt->desc = SANE_DESC_SCAN_TL_Y;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_y;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_BR_X;
  opt->name = SANE_NAME_SCAN_BR_X;
  opt->title = SANE_TITLE_SCAN_BR_X;
  opt->desc = SANE_DESC_SCAN_BR_X;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_x;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_BR_Y;
  opt->name = SANE_NAME_SCAN_BR_Y;
  opt->title = SANE_TITLE_SCAN_BR_Y;
  opt->desc = SANE_DESC_SCAN_BR_Y;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_y;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  if (!scan_mode_list[0])
    {
      scan_mode_list[HP3500_COLOR_SCAN] = SANE_VALUE_SCAN_MODE_COLOR;
      scan_mode_list[HP3500_GRAY_SCAN] = SANE_VALUE_SCAN_MODE_GRAY;
      scan_mode_list[HP3500_LINEART_SCAN] = SANE_VALUE_SCAN_MODE_LINEART;
      scan_mode_list[HP3500_TOTAL_SCANS] = 0;
    }

  opt = scanner->opt + OPT_MODE_GROUP;
  opt->title = SANE_I18N ("Scan Mode Group");
  opt->desc = SANE_I18N ("Scan Mode Group");
  opt->type = SANE_TYPE_GROUP;
  opt->constraint_type = SANE_CONSTRAINT_NONE;

  opt = scanner->opt + OPT_MODE;
  opt->name = SANE_NAME_SCAN_MODE;
  opt->title = SANE_TITLE_SCAN_MODE;
  opt->desc = SANE_DESC_SCAN_MODE;
  opt->type = SANE_TYPE_STRING;
  opt->size = max_string_size(scan_mode_list);
  opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  opt->constraint.string_list = (SANE_String_Const *) scan_mode_list;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_BRIGHTNESS;
  opt->name = SANE_NAME_BRIGHTNESS;
  opt->title = SANE_TITLE_BRIGHTNESS;
  opt->desc = SANE_DESC_BRIGHTNESS;
  opt->type = SANE_TYPE_INT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_brightness;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_CONTRAST;
  opt->name = SANE_NAME_CONTRAST;
  opt->title = SANE_TITLE_CONTRAST;
  opt->desc = SANE_DESC_CONTRAST;
  opt->type = SANE_TYPE_INT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_contrast;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  opt = scanner->opt + OPT_GAMMA;
  opt->name = SANE_NAME_ANALOG_GAMMA;
  opt->title = SANE_TITLE_ANALOG_GAMMA;
  opt->desc = SANE_DESC_ANALOG_GAMMA;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_NONE;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  opt->constraint.range = &range_gamma;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

  return SANE_STATUS_GOOD;
}

static void
do_reset (struct hp3500_data *scanner)
{
  scanner = scanner;		/* kill warning */
}

static void
do_cancel (struct hp3500_data *scanner)
{
  if (sanei_thread_is_valid (scanner->reader_pid))
    {

      if (sanei_thread_kill (scanner->reader_pid) == 0)
	{
	  int exit_status;

	  sanei_thread_waitpid (scanner->reader_pid, &exit_status);
	}
      scanner->reader_pid = -1;
    }
  if (scanner->pipe_r >= 0)
    {
      close (scanner->pipe_r);
      scanner->pipe_r = -1;
    }
}

static void
calculateDerivedValues (struct hp3500_data *scanner)
{

  DBG (12, "calculateDerivedValues\n");

  /* Convert the SANE_FIXED values for the scan area into 1/1200 inch 
   * scanner units */

  scanner->fullres_pixels.left =
    FIXED_MM_TO_SCANNER_UNIT (scanner->request_mm.left);
  scanner->fullres_pixels.top =
    FIXED_MM_TO_SCANNER_UNIT (scanner->request_mm.top);
  scanner->fullres_pixels.right =
    FIXED_MM_TO_SCANNER_UNIT (scanner->request_mm.right);
  scanner->fullres_pixels.bottom =
    FIXED_MM_TO_SCANNER_UNIT (scanner->request_mm.bottom);

  DBG (12, "\tleft margin: %u\n", scanner->fullres_pixels.left);
  DBG (12, "\ttop margin: %u\n", scanner->fullres_pixels.top);
  DBG (12, "\tright margin: %u\n", scanner->fullres_pixels.right);
  DBG (12, "\tbottom margin: %u\n", scanner->fullres_pixels.bottom);


  scanner->scan_width_pixels =
    scanner->resolution * (scanner->fullres_pixels.right -
			   scanner->fullres_pixels.left) / 1200;
  scanner->scan_height_pixels =
    scanner->resolution * (scanner->fullres_pixels.bottom -
			   scanner->fullres_pixels.top) / 1200;
  if (scanner->mode == HP3500_LINEART_SCAN)
    scanner->bytes_per_scan_line = (scanner->scan_width_pixels + 7) / 8;
  else if (scanner->mode == HP3500_GRAY_SCAN)
    scanner->bytes_per_scan_line = scanner->scan_width_pixels;
  else
    scanner->bytes_per_scan_line = scanner->scan_width_pixels * 3;

  if (scanner->scan_width_pixels < 1)
    scanner->scan_width_pixels = 1;
  if (scanner->scan_height_pixels < 1)
    scanner->scan_height_pixels = 1;

  scanner->actres_pixels.left =
    scanner->fullres_pixels.left * scanner->resolution / 1200;
  scanner->actres_pixels.top =
    scanner->fullres_pixels.top * scanner->resolution / 1200;
  scanner->actres_pixels.right =
    scanner->actres_pixels.left + scanner->scan_width_pixels;
  scanner->actres_pixels.bottom =
    scanner->actres_pixels.top + scanner->scan_height_pixels;

  scanner->actual_mm.left =
    SCANNER_UNIT_TO_FIXED_MM (scanner->fullres_pixels.left);
  scanner->actual_mm.top =
    SCANNER_UNIT_TO_FIXED_MM (scanner->fullres_pixels.top);
  scanner->actual_mm.bottom =
    SCANNER_UNIT_TO_FIXED_MM (scanner->scan_width_pixels * 1200 /
			      scanner->resolution);
  scanner->actual_mm.right =
    SCANNER_UNIT_TO_FIXED_MM (scanner->scan_height_pixels * 1200 /
			      scanner->resolution);

  DBG (12, "calculateDerivedValues: ok\n");
}

/* From here on in we have the original code written for the scanner demo */

#define	MAX_COMMANDS_BYTES	131072
#define	MAX_READ_COMMANDS	1	/* Issuing more than one register
					 * read command in a single request
					 * seems to put the device in an
					 * unpredictable state.
					 */
#define	MAX_READ_BYTES		0xffc0

#define	REG_DESTINATION_POSITION 0x60
#define	REG_MOVE_CONTROL_TEST	0xb3

static int command_reads_outstanding = 0;
static int command_bytes_outstanding = 0;
static unsigned char command_buffer[MAX_COMMANDS_BYTES];
static int receive_bytes_outstanding = 0;
static char *command_readmem_outstanding[MAX_READ_COMMANDS];
static int command_readbytes_outstanding[MAX_READ_COMMANDS];
static unsigned char sram_access_method = 0;
static unsigned sram_size = 0;
static int udh;

static int
rt_execute_commands (void)
{
  SANE_Status result;
  size_t bytes;

  if (!command_bytes_outstanding)
    return 0;

  bytes = command_bytes_outstanding;

  result = sanei_usb_write_bulk (udh, /* 0x02, */ command_buffer, &bytes);

  if (result == SANE_STATUS_GOOD && receive_bytes_outstanding)
    {
      unsigned char readbuf[MAX_READ_BYTES];
      int total_read = 0;

      do
	{
	  bytes = receive_bytes_outstanding - total_read;
	  result = sanei_usb_read_bulk (udh,
					/* 0x81, */
					readbuf + total_read, &bytes);
	  if (result == SANE_STATUS_GOOD)
	    total_read += bytes;
	  else
	    break;
	}
      while (total_read < receive_bytes_outstanding);
      if (result == SANE_STATUS_GOOD)
	{
	  unsigned char *readptr;
	  int i;

	  for (i = 0, readptr = readbuf;
	       i < command_reads_outstanding;
	       readptr += command_readbytes_outstanding[i++])
	    {
	      memcpy (command_readmem_outstanding[i],
		      readptr, command_readbytes_outstanding[i]);
	    }
	}
    }
  receive_bytes_outstanding = command_reads_outstanding =
    command_bytes_outstanding = 0;
  return (result == SANE_STATUS_GOOD) ? 0 : -1;
}

static int
rt_queue_command (int command,
		  int reg,
		  int count,
		  int bytes, void const *data_, int readbytes, void *readdata)
{
  int len = 4 + bytes;
  unsigned char *buffer;
  unsigned char const *data = data_;

  /* We add "bytes" here to account for the possiblity that all of the
   * data bytes are 0xaa and hence require a following 0x00 byte.
   */
  if (command_bytes_outstanding + len + bytes > MAX_COMMANDS_BYTES ||
      (readbytes &&
       ((command_reads_outstanding >= MAX_READ_COMMANDS) ||
	(receive_bytes_outstanding >= MAX_READ_BYTES))))
    {
      if (rt_execute_commands () < 0)
	return -1;
    }

  buffer = command_buffer + command_bytes_outstanding;

  *buffer++ = command;
  *buffer++ = reg;
  *buffer++ = count >> 8;
  *buffer++ = count;
  while (bytes--)
    {
      *buffer++ = *data;
      if (*data++ == 0xaa)
	{
	  *buffer++ = 0;
	  ++len;
	}
    }
  command_bytes_outstanding += len;
  if (readbytes)
    {
      command_readbytes_outstanding[command_reads_outstanding] = readbytes;
      command_readmem_outstanding[command_reads_outstanding] = readdata;
      receive_bytes_outstanding += readbytes;
      ++command_reads_outstanding;
    }

  return 0;
}

static int
rt_send_command_immediate (int command,
			   int reg,
			   int count,
			   int bytes,
			   void *data, int readbytes, void *readdata)
{
  rt_queue_command (command, reg, count, bytes, data, readbytes, readdata);
  return rt_execute_commands ();
}

static int
rt_queue_read_register (int reg, int bytes, void *data)
{
  return rt_queue_command (RTCMD_GETREG, reg, bytes, 0, 0, bytes, data);
}

static int
rt_read_register_immediate (int reg, int bytes, void *data)
{
  if (rt_queue_read_register (reg, bytes, data) < 0)
    return -1;
  return rt_execute_commands ();
}

static int
rt_queue_set_register (int reg, int bytes, void *data)
{
  return rt_queue_command (RTCMD_SETREG, reg, bytes, bytes, data, 0, 0);
}

static int
rt_set_register_immediate (int reg, int bytes, void *data)
{
  if (reg < 0xb3 && reg + bytes > 0xb3)
    {
      int bytes_in_first_block = 0xb3 - reg;

      if (rt_set_register_immediate (reg, bytes_in_first_block, data) < 0 ||
	  rt_set_register_immediate (0xb4, bytes - bytes_in_first_block - 1,
				     (char *) data + bytes_in_first_block +
				     1) < 0)
	return -1;
      return 0;
    }
  if (rt_queue_set_register (reg, bytes, data) < 0)
    return -1;
  return rt_execute_commands ();
}

static int
rt_set_one_register (int reg, int val)
{
  char r = val;

  return rt_set_register_immediate (reg, 1, &r);
}

static int
rt_write_sram (int bytes, void *data_)
{
  unsigned char *data = (unsigned char *) data_;

  /* The number of bytes passed in could be much larger than we can transmit
   * (0xffc0) bytes. With 0xaa escapes it could be even larger. Accordingly
   * we need to count the 0xaa escapes and write in chunks if the number of
   * bytes would otherwise exceed a limit (I have used 0xf000 as the limit).
   */
  while (bytes > 0)
    {
      int now = 0;
      int bufsize = 0;

      while (now < bytes && bufsize < 0xf000)
	{
	  int i;

	  /* Try to avoid writing part pages */
	  for (i = 0; i < 32 && now < bytes; ++i)
	    {
	      ++bufsize;
	      if (data[now++] == 0xaa)
		++bufsize;
	    }
	}

      if (rt_send_command_immediate (RTCMD_WRITESRAM, 0, now, now, data, 0,
				     0) < 0)
	return -1;
      bytes -= now;
      data += now;
    }
  return 0;
}

static int
rt_read_sram (int bytes, void *data_)
{
  unsigned char *data = (unsigned char *) data_;

  while (bytes > 0)
    {
      int now = (bytes > 0xf000) ? 0xf000 : bytes;
      if (rt_send_command_immediate (RTCMD_READSRAM, 0, bytes, 0, 0, bytes,
				     data) < 0)
	return -1;
      bytes -= now;
      data += now;
    }
  return 0;
}

static int
rt_set_sram_page (int page)
{
  unsigned char regs[2];

  regs[0] = page;
  regs[1] = page >> 8;

  return rt_set_register_immediate (0x91, 2, regs);
}

static int
rt_detect_sram (unsigned *totalbytes, unsigned char *r93setting)
{
  char data[0x818];
  char testbuf[0x818];
  unsigned i;
  int test_values[] = { 6, 2, 1, -1 };

  for (i = 0; i < sizeof (data); ++i)
    data[i] = i % 0x61;


  for (i = 0; test_values[i] != -1; ++i)
    {
      if (rt_set_one_register (0x93, test_values[i]) ||
	  rt_set_sram_page (0x81) ||
	  rt_write_sram (0x818, data) ||
	  rt_set_sram_page (0x81) || rt_read_sram (0x818, testbuf))
	return -1;
      if (!memcmp (testbuf, data, 0x818))
	{
	  sram_access_method = test_values[i];
	  if (r93setting)
	    *r93setting = sram_access_method;
	  break;
	}
    }
  if (!sram_access_method)
    return -1;

  for (i = 0; i < 16; ++i)
    {
      int j;
      char write_data[32];
      char read_data[32];
      int pagesetting;

      for (j = 0; j < 16; j++)
	{
	  write_data[j * 2] = j * 2;
	  write_data[j * 2 + 1] = i;
	}

      pagesetting = i * 4096;


      if (rt_set_sram_page (pagesetting) < 0 ||
	  rt_write_sram (32, write_data) < 0)
	return -1;
      if (i)
	{
	  if (rt_set_sram_page (0) < 0 || rt_read_sram (32, read_data) < 0)
	    return -1;
	  if (!memcmp (read_data, write_data, 32))
	    {
	      sram_size = i * 0x20000;
	      if (totalbytes)
		*totalbytes = sram_size;
	      return 0;
	    }
	}
    }
  return -1;
}

static int
rt_get_available_bytes (void)
{
  unsigned char data[3];

  if (rt_queue_command (RTCMD_BYTESAVAIL, 0, 3, 0, 0, 3, data) < 0 ||
      rt_execute_commands () < 0)
    return -1;
  return ((unsigned) data[0]) |
    ((unsigned) data[1] << 8) | ((unsigned) data[2] << 16);
}

static int
rt_get_data (int bytes, void *data)
{
  int total = 0;

  while (bytes)
    {
      int bytesnow = bytes;

      if (bytesnow > 0xffc0)
	bytesnow = 0xffc0;
      if (rt_queue_command
	  (RTCMD_READBYTES, 0, bytesnow, 0, 0, bytesnow, data) < 0
	  || rt_execute_commands () < 0)
	return -1;
      total += bytesnow;
      bytes -= bytesnow;
      data = (char *) data + bytesnow;
    }
  return 0;
}

static int
rt_is_moving (void)
{
  char r;

  if (rt_read_register_immediate (REG_MOVE_CONTROL_TEST, 1, &r) < 0)
    return -1;
  if (r == 0x08)
    return 1;
  return 0;
}

static int
rt_is_rewound (void)
{
  char r;

  if (rt_read_register_immediate (0x1d, 1, &r) < 0)
    return -1;
  if (r & 0x02)
    return 1;
  return 0;
}

static int
rt_set_direction_forwards (unsigned char *regs)
{
  regs[0xc6] |= 0x08;
  return 0;
}

static int
rt_set_direction_rewind (unsigned char *regs)
{
  regs[0xc6] &= 0xf7;
  return 0;
}

static int
rt_set_stop_when_rewound (unsigned char *regs, int stop)
{
  if (stop)
    regs[0xb2] |= 0x10;
  else
    regs[0xb2] &= 0xef;
  return 0;
}

static int
rt_start_moving (void)
{
  if (rt_set_one_register (REG_MOVE_CONTROL_TEST, 2) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 2) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 0) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 0) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 8) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 8) < 0)
    return -1;
  return 0;
}

static int
rt_stop_moving (void)
{
  if (rt_set_one_register (REG_MOVE_CONTROL_TEST, 2) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 2) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 0) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, 0) < 0)
    return -1;
  return 0;
}

static int
rt_set_powersave_mode (int enable)
{
  unsigned char r;

  if (rt_read_register_immediate (REG_MOVE_CONTROL_TEST, 1, &r) < 0)
    return -1;
  if (r & 0x04)
    {
      if (enable == 1)
	return 0;
      r &= ~0x04;
    }
  else
    {
      if (enable == 0)
	return 0;
      r |= 0x04;
    }
  if (rt_set_one_register (REG_MOVE_CONTROL_TEST, r) < 0 ||
      rt_set_one_register (REG_MOVE_CONTROL_TEST, r) < 0)
    return -1;
  return 0;
}

static int
rt_turn_off_lamp (void)
{
  return rt_set_one_register (0x3a, 0);
}

static int
rt_turn_on_lamp (void)
{
  char r3ab[2];
  char r10;
  char r58;

  if (rt_read_register_immediate (0x3a, 1, r3ab) < 0 ||
      rt_read_register_immediate (0x10, 1, &r10) < 0 ||
      rt_read_register_immediate (0x58, 1, &r58) < 0)
    return -1;
  r3ab[0] |= 0x80;
  r3ab[1] = 0x40;
  r10 |= 0x01;
  r58 &= 0x0f;
  if (rt_set_register_immediate (0x3a, 2, r3ab) < 0 ||
      rt_set_one_register (0x10, r10) < 0 ||
      rt_set_one_register (0x58, r58) < 0)
    return -1;
  return 0;
}

static int
rt_set_value_lsbfirst (unsigned char *regs,
		       int firstreg, int totalregs, unsigned value)
{
  while (totalregs--)
    {
      regs[firstreg++] = value & 0xff;
      value >>= 8;
    }
  return 0;
}

#if 0
static int
rt_set_value_msbfirst (unsigned char *regs,
		       int firstreg, int totalregs, unsigned value)
{
  while (totalregs--)
    {
      regs[firstreg + totalregs] = value & 0xff;
      value >>= 8;
    }
  return 0;
}
#endif

static int
rt_set_ccd_shift_clock_multiplier (unsigned char *regs, unsigned value)
{
  return rt_set_value_lsbfirst (regs, 0xf0, 3, value);
}

static int
rt_set_ccd_clock_reset_interval (unsigned char *regs, unsigned value)
{
  return rt_set_value_lsbfirst (regs, 0xf9, 3, value);
}

static int
rt_set_ccd_clamp_clock_multiplier (unsigned char *regs, unsigned value)
{
  return rt_set_value_lsbfirst (regs, 0xfc, 3, value);
}

static int
rt_set_movement_pattern (unsigned char *regs, unsigned value)
{
  return rt_set_value_lsbfirst (regs, 0xc0, 3, value);
}

static int
rt_set_motor_movement_clock_multiplier (unsigned char *regs, unsigned value)
{
  regs[0x40] = (regs[0x40] & ~0xc0) | (value << 6);
  return 0;
}

static int
rt_set_motor_type (unsigned char *regs, unsigned value)
{
  regs[0xc9] = (regs[0xc9] & 0xf8) | (value & 0x7);
  return 0;
}

static int
rt_set_noscan_distance (unsigned char *regs, unsigned value)
{
  DBG (10, "Setting distance without scanning to %d\n", value);
  return rt_set_value_lsbfirst (regs, 0x60, 2, value);
}

static int
rt_set_total_distance (unsigned char *regs, unsigned value)
{
  DBG (10, "Setting total distance to %d\n", value);
  return rt_set_value_lsbfirst (regs, 0x62, 2, value);
}

static int
rt_set_scanline_start (unsigned char *regs, unsigned value)
{
  return rt_set_value_lsbfirst (regs, 0x66, 2, value);
}

static int
rt_set_scanline_end (unsigned char *regs, unsigned value)
{
  return rt_set_value_lsbfirst (regs, 0x6c, 2, value);
}

static int
rt_set_basic_calibration (unsigned char *regs,
			  int redoffset1,
			  int redoffset2,
			  int redgain,
			  int greenoffset1,
			  int greenoffset2,
			  int greengain,
			  int blueoffset1, int blueoffset2, int bluegain)
{
  regs[0x02] = redoffset1;
  regs[0x05] = redoffset2;
  regs[0x08] = redgain;
  regs[0x03] = greenoffset1;
  regs[0x06] = greenoffset2;
  regs[0x09] = greengain;
  regs[0x04] = blueoffset1;
  regs[0x07] = blueoffset2;
  regs[0x0a] = bluegain;
  return 0;
}

static int
rt_set_calibration_addresses (unsigned char *regs,
			      unsigned redaddr,
			      unsigned greenaddr,
			      unsigned blueaddr,
			      unsigned endaddr,
			      unsigned width)
{
  unsigned endpage = (endaddr + 31) / 32;
  unsigned scanline_pages = ((width + 1) * 3 + 31) / 32;

  /* Red, green and blue detailed calibration addresses */

  regs[0x84] = redaddr;
  regs[0x8e] = (regs[0x8e] & 0x0f) | ((redaddr >> 4) & 0xf0);
  rt_set_value_lsbfirst (regs, 0x85, 2, greenaddr);
  rt_set_value_lsbfirst (regs, 0x87, 2, blueaddr);

  /* I don't know what the next three are used for, but each buffer commencing
   * at 0x80 and 0x82 needs to hold a full scan line.
   */

  rt_set_value_lsbfirst (regs, 0x80, 2, endpage);
  rt_set_value_lsbfirst (regs, 0x82, 2, endpage + scanline_pages);
  rt_set_value_lsbfirst (regs, 0x89, 2, endpage + scanline_pages * 2);

  /* I don't know what this is, but it seems to be a number of pages that can hold
   * 16 complete scan lines, but not calculated as an offset from any other page
   */

  rt_set_value_lsbfirst (regs, 0x51, 2, (48 * (width + 1) + 31) / 32);

  /* I don't know what this is either, but this is what the Windows driver does */
  rt_set_value_lsbfirst (regs, 0x8f, 2, 0x1c00);
  return 0;
}

static int
rt_set_lamp_duty_cycle (unsigned char *regs,
			int enable, int frequency, int offduty)
{
  if (enable)
    regs[0x3b] |= 0x80;
  else
    regs[0x3b] &= 0x7f;

  regs[0x3b] =
    (regs[0x3b] & 0x80) | ((frequency & 0x7) << 4) | (offduty & 0x0f);
  regs[0x3d] = (regs[0x3d] & 0x7f) | ((frequency & 0x8) << 4);
  return 0;
}

static int
rt_set_data_feed_on (unsigned char *regs)
{
  regs[0xb2] &= ~0x04;
  return 0;
}

static int
rt_set_data_feed_off (unsigned char *regs)
{
  regs[0xb2] |= 0x04;
  return 0;
}

static int
rt_enable_ccd (unsigned char *regs, int enable)
{
  if (enable)
    regs[0x00] &= ~0x10;
  else
    regs[0x00] |= 0x10;
  return 0;
}

static int
rt_set_cdss (unsigned char *regs, int val1, int val2)
{
  regs[0x28] = (regs[0x28] & 0xe0) | (val1 & 0x1f);
  regs[0x2a] = (regs[0x2a] & 0xe0) | (val2 & 0x1f);
  return 0;
}

static int
rt_set_cdsc (unsigned char *regs, int val1, int val2)
{
  regs[0x29] = (regs[0x29] & 0xe0) | (val1 & 0x1f);
  regs[0x2b] = (regs[0x2b] & 0xe0) | (val2 & 0x1f);
  return 0;
}

static int
rt_update_after_setting_cdss2 (unsigned char *regs)
{
  int fullcolour = (!(regs[0x2f] & 0xc0) && (regs[0x2f] & 0x04));
  int value = regs[0x2a] & 0x1f;

  regs[0x2a] = (regs[0x2a] & 0xe0) | (value & 0x1f);

  if (fullcolour)
    value *= 3;
  if ((regs[0x40] & 0xc0) == 0x40)
    value += 17;
  else
    value += 16;

  regs[0x2c] = (regs[0x2c] & 0xe0) | (value % 24);
  regs[0x2d] = (regs[0x2d] & 0xe0) | ((value + 2) % 24);
  return 0;
}

static int
rt_set_cph0s (unsigned char *regs, int on)
{
  if (on)
    regs[0x2d] |= 0x20;		/* 1200dpi horizontal coordinate space */
  else
    regs[0x2d] &= ~0x20;	/* 600dpi horizontal coordinate space */
  return 0;
}

static int
rt_set_cvtr_lm (unsigned char *regs, int val1, int val2, int val3)
{
  regs[0x28] = (regs[0x28] & ~0xe0) | (val1 << 5);
  regs[0x29] = (regs[0x29] & ~0xe0) | (val2 << 5);
  regs[0x2a] = (regs[0x2a] & ~0xe0) | (val3 << 5);
  return 0;
}

static int
rt_set_cvtr_mpt (unsigned char *regs, int val1, int val2, int val3)
{
  regs[0x3c] = (val1 & 0x0f) | (val2 << 4);
  regs[0x3d] = (regs[0x3d] & 0xf0) | (val3 & 0x0f);
  return 0;
}

static int
rt_set_cvtr_wparams (unsigned char *regs,
		     unsigned fpw, unsigned bpw, unsigned w)
{
  regs[0x31] = (w & 0x0f) | ((bpw << 4) & 0x30) | (fpw << 6);
  return 0;
}

static int
rt_enable_movement (unsigned char *regs, int enable)
{
  if (enable)
    regs[0xc3] |= 0x80;
  else
    regs[0xc3] &= ~0x80;
  return 0;
}

static int
rt_set_scan_frequency (unsigned char *regs, int frequency)
{
  regs[0x64] = (regs[0x64] & 0xf0) | (frequency & 0x0f);
  return 0;
}

static int
rt_set_merge_channels (unsigned char *regs, int on)
{
  /* RGBRGB instead of RRRRR...GGGGG...BBBB */
  regs[0x2f] &= ~0x14;
  regs[0x2f] |= on ? 0x04 : 0x10;
  return 0;
}

static int
rt_set_channel (unsigned char *regs, int channel)
{
  regs[0x2f] = (regs[0x2f] & ~0xc0) | (channel << 6);
  return 0;
}

static int
rt_set_single_channel_scanning (unsigned char *regs, int on)
{
  if (on)
    regs[0x2f] |= 0x20;
  else
    regs[0x2f] &= ~0x20;
  return 0;
}

static int
rt_set_colour_mode (unsigned char *regs, int on)
{
  if (on)
    regs[0x2f] |= 0x02;
  else
    regs[0x2f] &= ~0x02;
  return 0;
}

static int
rt_set_horizontal_resolution (unsigned char *regs, int resolution)
{
  int base_resolution = 300;

  if (regs[0x2d] & 0x20)
    base_resolution *= 2;
  if (regs[0xd3] & 0x08)
    base_resolution *= 2;
  regs[0x7a] = base_resolution / resolution;
  return 0;
}

static int
rt_set_last_sram_page (unsigned char *regs, int pagenum)
{
  rt_set_value_lsbfirst (regs, 0x8b, 2, pagenum);
  return 0;
}

static int
rt_set_step_size (unsigned char *regs, int stepsize)
{
  rt_set_value_lsbfirst (regs, 0xe2, 2, stepsize);
  rt_set_value_lsbfirst (regs, 0xe0, 2, 0);
  return 0;
}

static int
rt_set_all_registers (void const *regs_)
{
  char regs[255];

  memcpy (regs, regs_, 255);
  regs[0x32] &= ~0x40;

  if (rt_set_one_register (0x32, regs[0x32]) < 0 ||
      rt_set_register_immediate (0, 255, regs) < 0 ||
      rt_set_one_register (0x32, regs[0x32] | 0x40) < 0)
    return -1;
  return 0;
}

static int
rt_adjust_misc_registers (unsigned char *regs)
{
  /* Mostly unknown purposes - probably no need to adjust */
  regs[0xc6] = (regs[0xc6] & 0x0f) | 0x20;	/* Purpose unknown - appears to do nothing */
  regs[0x2e] = 0x86;		/* ???? - Always has this value */
  regs[0x30] = 2;		/* CCPL = 1 */
  regs[0xc9] |= 0x38;		/* Doesn't have any obvious effect, but the Windows driver does this */
  return 0;
}


#define NVR_MAX_ADDRESS_SIZE	11
#define NVR_MAX_OPCODE_SIZE	3
#define NVR_DATA_SIZE		8
#define	NVR_MAX_COMMAND_SIZE	((NVR_MAX_ADDRESS_SIZE + \
				  NVR_MAX_OPCODE_SIZE + \
				  NVR_DATA_SIZE) * 2 + 1)

static int
rt_nvram_enable_controller (int enable)
{
  unsigned char r;

  if (rt_read_register_immediate (0x1d, 1, &r) < 0)
    return -1;
  if (enable)
    r |= 1;
  else
    r &= ~1;
  return rt_set_one_register (0x1d, r);

}

static int
rt_nvram_init_command (void)
{
  unsigned char regs[13];

  if (rt_read_register_immediate (0x10, 13, regs) < 0)
    return -1;
  regs[2] |= 0xf0;
  regs[4] = (regs[4] & 0x1f) | 0x60;
  return rt_set_register_immediate (0x10, 13, regs);
}

static int
rt_nvram_init_stdvars (int block, int *addrbits, unsigned char *basereg)
{
  int bitsneeded;
  int capacity;

  switch (block)
    {
    case 0:
      bitsneeded = 7;
      break;

    case 1:
      bitsneeded = 9;
      break;

    case 2:
      bitsneeded = 11;
      break;

    default:
      bitsneeded = 0;
      capacity = 1;
      while (capacity < block)
	capacity <<= 1, ++bitsneeded;
      break;
    }

  *addrbits = bitsneeded;

  if (rt_read_register_immediate (0x10, 1, basereg) < 0)
    return -1;

  *basereg &= ~0x60;
  return 0;
}

static void
rt_nvram_set_half_bit (unsigned char *buffer,
		       int value, unsigned char stdbits, int whichhalf)
{
  *buffer = stdbits | (value ? 0x40 : 0) | (whichhalf ? 0x20 : 0);
}

static void
rt_nvram_set_command_bit (unsigned char *buffer,
			  int value, unsigned char stdbits)
{
  rt_nvram_set_half_bit (buffer, value, stdbits, 0);
  rt_nvram_set_half_bit (buffer + 1, value, stdbits, 1);
}

static void
rt_nvram_set_addressing_bits (unsigned char *buffer,
			      int location,
			      int addressingbits, unsigned char stdbits)
{
  int currentbit = 1 << (addressingbits - 1);

  while (addressingbits--)
    {
      rt_nvram_set_command_bit (buffer,
				(location & currentbit) ? 1 : 0, stdbits);
      buffer += 2;
      currentbit >>= 1;
    }
}

#if 0
static int
rt_nvram_enable_write (int addressingbits, int enable, unsigned char stdbits)
{
  unsigned char cmdbuffer[NVR_MAX_COMMAND_SIZE];
  int cmdsize = 6 + addressingbits * 2;

  rt_nvram_set_command_bit (cmdbuffer, 1, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 2, 0, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 4, 0, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 6, enable, stdbits);
  if (addressingbits > 1)
    rt_nvram_set_addressing_bits (cmdbuffer + 8, 0, addressingbits - 1,
				  stdbits);

  if (rt_nvram_enable_controller (1) < 0 ||
      rt_send_command_immediate (RTCMD_NVRAMCONTROL, 0, cmdsize, cmdsize,
				 cmdbuffer, 0, 0) < 0
      || rt_nvram_enable_controller (0) < 0)
    {
      return -1;
    }
  return 0;
}

static int
rt_nvram_write (int block, int location, char const *data, int bytes)
{
  int addressingbits;
  unsigned char stdbits;
  unsigned char cmdbuffer[NVR_MAX_COMMAND_SIZE];
  unsigned char *address_bits;
  unsigned char *data_bits;
  int cmdsize;

  /* This routine doesn't appear to work, but I can't see anything wrong with it */
  if (rt_nvram_init_stdvars (block, &addressingbits, &stdbits) < 0)
    return -1;

  cmdsize = (addressingbits + 8) * 2 + 6;
  address_bits = cmdbuffer + 6;
  data_bits = address_bits + (addressingbits * 2);

  rt_nvram_set_command_bit (cmdbuffer, 1, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 2, 0, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 4, 1, stdbits);

  if (rt_nvram_init_command () < 0 ||
      rt_nvram_enable_write (addressingbits, 1, stdbits) < 0)
    return -1;

  while (bytes--)
    {
      int i;

      rt_nvram_set_addressing_bits (address_bits, location, addressingbits,
				    stdbits);
      rt_nvram_set_addressing_bits (data_bits, *data++, 8, stdbits);

      if (rt_nvram_enable_controller (1) < 0 ||
	  rt_send_command_immediate (RTCMD_NVRAMCONTROL, 0, cmdsize, cmdsize,
				     cmdbuffer, 0, 0) < 0
	  || rt_nvram_enable_controller (0) < 0)
	return -1;

      if (rt_nvram_enable_controller (1) < 0)
	return -1;
      for (i = 0; i < cmdsize; ++i)
	{
	  unsigned char r;
	  unsigned char cmd;

	  rt_nvram_set_half_bit (&cmd, 0, stdbits, i & 1);
	  if (rt_send_command_immediate
	      (RTCMD_NVRAMCONTROL, 0, 1, 1, &cmd, 0, 0) < 0
	      || rt_read_register_immediate (0x10, 1, &r) < 0)
	    {
	      return -1;
	    }
	  else if (r & 0x80)
	    {
	      break;
	    }
	}
      if (rt_nvram_enable_controller (0) < 0)
	return -1;

      ++location;
    }

  if (rt_nvram_enable_write (addressingbits, 0, stdbits) < 0)
    return -1;
  return 0;
}
#endif

static int
rt_nvram_read (int block, int location, unsigned char *data, int bytes)
{
  int addressingbits;
  unsigned char stdbits;
  unsigned char cmdbuffer[NVR_MAX_COMMAND_SIZE];
  unsigned char *address_bits;
  unsigned char readbit_command[2];
  int cmdsize;

  if (rt_nvram_init_stdvars (block, &addressingbits, &stdbits) < 0)
    return -1;

  cmdsize = addressingbits * 2 + 7;
  address_bits = cmdbuffer + 6;

  rt_nvram_set_command_bit (cmdbuffer, 1, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 2, 1, stdbits);
  rt_nvram_set_command_bit (cmdbuffer + 4, 0, stdbits);
  rt_nvram_set_half_bit (cmdbuffer + cmdsize - 1, 0, stdbits, 0);

  rt_nvram_set_half_bit (readbit_command, 0, stdbits, 1);
  rt_nvram_set_half_bit (readbit_command + 1, 0, stdbits, 0);

  if (rt_nvram_init_command () < 0)
    return -1;

  while (bytes--)
    {
      char c = 0;
      unsigned char r;
      int i;

      rt_nvram_set_addressing_bits (address_bits, location, addressingbits,
				    stdbits);

      if (rt_nvram_enable_controller (1) < 0 ||
	  rt_send_command_immediate (RTCMD_NVRAMCONTROL, 0x1d, cmdsize,
				     cmdsize, cmdbuffer, 0, 0) < 0)
	return -1;

      for (i = 0; i < 8; ++i)
	{
	  c <<= 1;

	  if (rt_send_command_immediate
	      (RTCMD_NVRAMCONTROL, 0x1d, 2, 2, readbit_command, 0, 0) < 0
	      || rt_read_register_immediate (0x10, 1, &r) < 0)
	    return -1;
	  if (r & 0x80)
	    c |= 1;
	}
      if (rt_nvram_enable_controller (0) < 0)
	return -1;

      *data++ = c;
      ++location;
    }
  return 0;
}

/* This is what we want as the initial registers, not what they
 * are at power on time. In particular 13 bytes at 0x10 are
 * different, and the byte at 0x94 is different.
 */
static unsigned char initial_regs[] = {
  /* 0x00 */ 0xf5, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x08 */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
  /* 0x10 */ 0x81, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
  /* 0x18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
  /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x19,
  /* 0x30 */ 0xd0, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x38 */ 0x00, 0x00, 0xa0, 0x37, 0xff, 0x0f, 0x00, 0x00,
  /* 0x40 */ 0x80, 0x00, 0x00, 0x00, 0x8c, 0x76, 0x00, 0x00,
  /* 0x48 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x50 */ 0x20, 0xbc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x58 */ 0x1d, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00,
  /* 0x60 */ 0x5e, 0xea, 0x5f, 0xea, 0x00, 0x80, 0x64, 0x00,
  /* 0x68 */ 0x00, 0x00, 0x00, 0x00, 0x84, 0x04, 0x00, 0x00,
  /* 0x70 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x78 */ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0x80 */ 0x0f, 0x02, 0x4b, 0x02, 0x00, 0xec, 0x19, 0xd8,
  /* 0x88 */ 0x2d, 0x87, 0x02, 0xff, 0x3f, 0x78, 0x60, 0x00,
  /* 0x90 */ 0x1c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
  /* 0x98 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xa0 */ 0x00, 0x00, 0x00, 0x0c, 0x27, 0x64, 0x00, 0x00,
  /* 0xa8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xb0 */ 0x12, 0x08, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00,
  /* 0xb8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xc0 */ 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00,
  /* 0xc8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xd0 */ 0xff, 0xbf, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
  /* 0xd8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xe0 */ 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xe8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xf0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  /* 0xf8 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#define RT_NORMAL_TG 0
#define RT_DOUBLE_TG 1
#define RT_TRIPLE_TG 2
#define RT_DDOUBLE_TG 3
#define RT_300_TG 4
#define RT_150_TG 5
#define RT_TEST_TG 6
static struct tg_info__
{
  int tg_cph0p;
  int tg_crsp;
  int tg_cclpp;
  int tg_cph0s;
  int tg_cdss1;
  int tg_cdsc1;
  int tg_cdss2;
  int tg_cdsc2;
} tg_info[] =
{
  /* CPH              CCD Shifting Clock
   *    0P            ??? Perhaps CCD rising edge position
   *    0S            ???
   * CRS              Reset CCD Clock
   *    P             ??? Perhaps CCD falling edge position
   * CCLP             CCD Clamp Clock
   *     P            ???
   * CDS              ???
   *    S1            ???
   *    S2            ???
   *    C1            ???
   *    C2            ???
   */
  /*CPH0P     CRSP      CCLPP     CPH0S CDSS1 CDSC1 CDSS2 CDSC2 */
  {
  0x01FFE0, 0x3c0000, 0x003000, 1, 0xb, 0xd, 0x00, 0x01},	/* NORMAL */
  {
  0x7ff800, 0xf00000, 0x01c000, 0, 0xb, 0xc, 0x14, 0x15},	/* DOUBLE */
  {
  0x033fcc, 0x300000, 0x060000, 1, 0x8, 0xa, 0x00, 0x01},	/* TRIPLE */
  {
  0x028028, 0x300000, 0x060000, 1, 0x8, 0xa, 0x00, 0x01},	/* DDOUBLE */
  {
  0x7ff800, 0x030000, 0x060000, 0, 0xa, 0xc, 0x17, 0x01},	/* 300 */
  {
  0x7fc700, 0x030000, 0x060000, 0, 0x7, 0x9, 0x17, 0x01},	/* 150 */
  {
  0x7ff800, 0x300000, 0x060000, 0, 0xa, 0xc, 0x17, 0x01},	/* TEST */
};

struct resolution_parameters
{
  unsigned resolution;
  int reg_39_value;
  int reg_c3_value;
  int reg_c6_value;
  int scan_frequency;
  int cph0s;
  int red_green_offset;
  int green_blue_offset;
  int intra_channel_offset;
  int motor_movement_clock_multiplier;
  int d3_bit_3_value;
  int tg;
  int step_size;
};

/* The TG value sets seem to affect the exposure time:
 * At 200dpi:
 * NORMAL gets higher values than DOUBLE
 * DDOUBLE gives a crazy spike in the data
 * TRIPLE gives a black result
 * TEST gives a black result
 * 300 gives a black result
 * 150 gives a black result
 */

static struct resolution_parameters resparms[] = {
  /* Acceptable values for stepsz are:
   * 0x157b 0xabd, 0x55e, 0x2af, 0x157, 0xab, 0x55
   */
  /* My values - all work */
  /*res   r39 rC3 rC6 freq cph0s rgo gbo intra mmcm d3 tg            stepsz */
  {1200, 3, 6, 4, 2, 1, 22, 22, 4, 2, 1, RT_NORMAL_TG, 0x157b},
  {600, 15, 6, 4, 1, 1, 9, 10, 0, 2, 1, RT_NORMAL_TG, 0x055e},
  {400, 3, 1, 4, 1, 1, 6, 6, 1, 2, 1, RT_NORMAL_TG, 0x157b},
  {300, 15, 3, 4, 1, 1, 5, 4, 0, 2, 1, RT_NORMAL_TG, 0x02af},
  {200, 7, 1, 4, 1, 1, 3, 3, 0, 2, 1, RT_NORMAL_TG, 0x055e},
  {150, 15, 3, 1, 1, 1, 2, 2, 0, 2, 1, RT_NORMAL_TG, 0x02af},
  {100, 3, 1, 3, 1, 1, 1, 1, 0, 2, 1, RT_NORMAL_TG, 0x0abd},
  {75, 15, 3, 3, 1, 1, 1, 1, 0, 2, 1, RT_NORMAL_TG, 0x02af},
  {50, 15, 1, 1, 1, 1, 0, 0, 0, 2, 1, RT_NORMAL_TG, 0x055e},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};

struct dcalibdata
{
  unsigned char *buffers[3];
  int pixelsperrow;
  int pixelnow;
  int channelnow;
  int firstrowdone;
};

static void dump_registers (unsigned char const *);
static int
rts8801_rewind (void)
{
  unsigned char regs[255];
  int n;
  int tg_setting = RT_DOUBLE_TG;

  rt_read_register_immediate (0, 255, regs);

  rt_set_noscan_distance (regs, 59998);
  rt_set_total_distance (regs, 59999);

  rt_set_stop_when_rewound (regs, 0);

  rt_set_one_register (0xc6, 0);
  rt_set_one_register (0xc6, 0);


  rt_set_direction_rewind (regs);

  rt_set_step_size (regs, 0x55);
  regs[0x39] = 3;
  regs[0xc3] = (regs[0xc3] & 0xf8) | 0x86;
  regs[0xc6] = (regs[0xc6] & 0xf8) | 4;

  rt_set_horizontal_resolution (regs, 25);
  rt_set_ccd_shift_clock_multiplier (regs, tg_info[tg_setting].tg_cph0p);
  rt_set_ccd_clock_reset_interval (regs, tg_info[tg_setting].tg_crsp);
  rt_set_ccd_clamp_clock_multiplier (regs, tg_info[tg_setting].tg_cclpp);
  rt_set_cdss (regs, tg_info[tg_setting].tg_cdss1,
	       tg_info[tg_setting].tg_cdss2);
  rt_set_cdsc (regs, tg_info[tg_setting].tg_cdsc1,
	       tg_info[tg_setting].tg_cdsc2);
  rt_update_after_setting_cdss2 (regs);
  rt_set_cvtr_wparams (regs, 3, 0, 6);
  rt_set_cvtr_mpt (regs, 15, 15, 15);
  rt_set_cvtr_lm (regs, 7, 7, 7);
  rt_set_motor_type (regs, 2);

  if (DBG_LEVEL >= 5)
    dump_registers (regs);

  rt_set_all_registers (regs);
  rt_set_one_register (0x2c, regs[0x2c]);

  rt_start_moving ();

  while (!rt_is_rewound () &&
	 ((n = rt_get_available_bytes ()) > 0 || rt_is_moving () > 0))
    {
      if (n)
	{
	  char buffer[0xffc0];

	  if (n > (int) sizeof (buffer))
	    n = sizeof (buffer);
	  rt_get_data (n, buffer);
	}
      else
	{
	  usleep (10000);
	}
    }

  rt_stop_moving ();
  return 0;
}

static int cancelled_scan = 0;

static unsigned
get_lsbfirst_int (unsigned char const *p, int n)
{
  unsigned value = *p++;
  int shift = 8;

  while (--n)
    {
      unsigned now = *p++;
      value |= now << shift;
      shift += 8;
    }
  return value;
}

static int
convert_c6 (int i)
{
  switch (i)
    {
    case 3:
      return 1;

    case 1:
      return 2;

    case 4:
      return 4;
    }
  return -1;
}

static void
dump_registers (unsigned char const *regs)
{
  int i = 0;
  long pixels;

  DBG (5, "Scan commencing with registers:\n");
  while (i < 255)
    {
      int j = 0;
      char buffer[80];

      buffer[0] = 0;

      sprintf (buffer + strlen (buffer), "%02x:", i);
      while (j < 8)
	{
	  sprintf (buffer + strlen (buffer), " %02x", regs[i++]);
	  j++;
	}
      sprintf (buffer + strlen (buffer), " -");
      while (j++ < 16 && i < 255)
	sprintf (buffer + strlen (buffer), " %02x", regs[i++]);
      DBG (5, "    %s\n", buffer);
    }

  DBG (5, "  Position:\n");
  DBG (5, "    Distance without scanning:       %u\n",
       get_lsbfirst_int (regs + 0x60, 2));
  DBG (5, "    Total distance:                  %u\n",
       get_lsbfirst_int (regs + 0x62, 2));
  DBG (5, "    Scanning distance:               %u\n",
       get_lsbfirst_int (regs + 0x62, 2) - get_lsbfirst_int (regs + 0x60, 2));
  DBG (5, "    Direction:                       %s\n",
       (regs[0xc6] & 0x08) ? "forward" : "rewind");
  DBG (5, "    Motor:                           %s\n",
       (regs[0xc3] & 0x80) ? "enabled" : "disabled");
  if (regs[0x7a])
    DBG (5, "    X range:                         %u-%u\n",
	 get_lsbfirst_int (regs + 0x66, 2) / regs[0x7a],
	 get_lsbfirst_int (regs + 0x6c, 2) / regs[0x7a]);
  DBG (5, "  TG Info:\n");
  DBG (5, "    CPH0P:                           %06x\n",
       get_lsbfirst_int (regs + 0xf0, 3));
  DBG (5, "    CRSP:                            %06x\n",
       get_lsbfirst_int (regs + 0xf9, 3));
  DBG (5, "    CCLPP:                           %06x\n",
       get_lsbfirst_int (regs + 0xfc, 3));
  DBG (5, "    CPH0S:                           %d\n",
       (regs[0x2d] & 0x20) ? 1 : 0);
  DBG (5, "    CDSS1:                           %02x\n", regs[0x28] & 0x1f);
  DBG (5, "    CDSC1:                           %02x\n", regs[0x29] & 0x1f);
  DBG (5, "    CDSS2:                           %02x\n", regs[0x2a] & 0x1f);
  DBG (5, "    CDSC2:                           %02x\n", regs[0x2b] & 0x1f);

  DBG (5, "  Resolution specific:\n");
  if (!regs[0x7a])
    DBG (5, "    Horizontal resolution:           Denominator is zero!\n");
  else
    DBG (5, "    Horizontal resolution:           %u\n", 300
	 * ((regs[0x2d] & 0x20) ? 2 : 1)
	 * ((regs[0xd3] & 0x08) ? 2 : 1) / regs[0x7a]);
  DBG (5, "    Derived vertical resolution:     %u\n",
       400 * (regs[0xc3] & 0x1f) * convert_c6 (regs[0xc6] & 0x7) /
       (regs[0x39] + 1));
  DBG (5, "    Register D3:3                    %u\n",
       (regs[0xd3] & 0x08) ? 1 : 0);
  DBG (5, "    Register 39:                     %u\n", regs[0x39]);
  DBG (5, "    Register C3:0-5:                 %u\n", regs[0xc3] & 0x1f);
  DBG (5, "    Register C6:0-2:                 %u\n", regs[0xc6] & 0x7);
  DBG (5, "    Motor movement clock multiplier: %u\n", regs[0x40] >> 6);
  DBG (5, "    Step Size:                       %04x\n",
       get_lsbfirst_int (regs + 0xe2, 2));
  DBG (5, "    Frequency:                       %u\n", regs[0x64] & 0xf);
  DBG (5, "  Colour registers\n");
  DBG (5, "    Register 2F:                     %02x\n", regs[0x2f]);
  DBG (5, "    Register 2C:                     %02x\n", regs[0x2c]);
  if (regs[0x7a])
    {
      DBG (5, "  Scan data estimates:\n");
      pixels =
	(long) (get_lsbfirst_int (regs + 0x62, 2) -
		get_lsbfirst_int (regs + 0x60,
				  2)) * (long) (get_lsbfirst_int (regs + 0x6c,
								  2) -
						get_lsbfirst_int (regs + 0x66,
								  2)) /
	regs[0x7a];
      DBG (5, "    Pixels:                          %ld\n", pixels);
      DBG (5, "    Bytes at 24BPP:                  %ld\n", pixels * 3);
      DBG (5, "    Bytes at 1BPP:                   %ld\n", pixels / 8);
    }
  DBG (5, "\n");
}

static int
constrain (int val, int min, int max)
{
  if (val < min)
    {
      DBG (10, "Clipped %d to %d\n", val, min);
      val = min;
    }
  else if (val > max)
    {
      DBG (10, "Clipped %d to %d\n", val, max);
      val = max;
    }
  return val;
}

#if 0
static void
sram_dump_byte(FILE *fp,
               unsigned char const *left,
               unsigned leftstart,
               unsigned leftlimit,
               unsigned char const *right,
               unsigned rightstart,
               unsigned rightlimit,
               unsigned idx)
{
  unsigned ridx = rightstart + idx;
  unsigned lidx = leftstart + idx;

  putc(' ', fp);
  if (rightstart < rightlimit && leftstart < leftlimit && left[lidx] != right[ridx])
    fputs("<b>", fp);
  if (leftstart < leftlimit)
    fprintf(fp, "%02x", left[lidx]);
  else
    fputs("  ", fp);
  if (rightstart < rightlimit && leftstart < leftlimit && left[lidx] != right[ridx])
    fputs("</b>", fp);
}

static void
dump_sram_to_file(char const *fname,
                  unsigned char const *expected,
                  unsigned end_calibration_offset)
{
  FILE *fp = fopen(fname, "w");
  rt_set_sram_page(0);

  if (fp)
    {
      unsigned char buf[1024];
      unsigned loc = 0;

      fprintf(fp, "<html><head></head><body><pre>\n");
      while (loc < end_calibration_offset)
        {
          unsigned byte = 0;

          rt_read_sram(1024, buf);

          while (byte < 1024)
            {
              unsigned idx = 0;

              fprintf(fp, "%06x:", loc);
              do
                {
		  sram_dump_byte(fp, buf, byte, 1024, expected, loc, end_calibration_offset, idx);
                } while (++idx & 0x7);
              fprintf(fp, " -");
              do
                {
		  sram_dump_byte(fp, buf, byte, 1024, expected, loc, end_calibration_offset, idx);
                } while (++idx & 0x7);

              idx = 0;
              fputs("     ", fp);

              do
                {
                  sram_dump_byte(fp, expected, loc, end_calibration_offset, buf, byte, 1024, idx);
                } while (++idx & 0x7);
              fprintf(fp, " -");
              do
                {
                  sram_dump_byte(fp, expected, loc, end_calibration_offset, buf, byte, 1024, idx);
                } while (++idx & 0x7);


              fputs("\n", fp);
              byte += 16;
              loc += 16;
            }
        }
      fprintf(fp, "</pre></body></html>");
      fclose(fp);
    }
}
#endif

static int
rts8801_doscan (unsigned width,
		unsigned height,
		unsigned colour,
		unsigned red_green_offset,
		unsigned green_blue_offset,
		unsigned intra_channel_offset,
		rts8801_callback cbfunc,
		void *params,
		int oddfirst,
		unsigned char const *calib_info,
		int merged_channels,
		double *postprocess_offsets,
		double *postprocess_gains)
{
  unsigned rowbytes = 0;
  unsigned output_rowbytes = 0;
  unsigned channels = 0;
  unsigned total_rows = 0;
  unsigned char *row_buffer;
  unsigned char *output_buffer;
  unsigned buffered_rows;
  int rows_to_begin;
  int rowbuffer_bytes;
  int n;
  unsigned rownow = 0;
  unsigned bytenow = 0;
  unsigned char *channel_data[3][2];
  unsigned i;
  unsigned j;
  int result = 0;
  unsigned rows_supplied = 0;

  calib_info = calib_info;	/* Kill warning */
  if (cancelled_scan)
    return -1;
  rt_start_moving ();

  channels = 3;
  rowbytes = width * 3;

  switch (colour)
    {
    case HP3500_GRAY_SCAN:
      output_rowbytes = width;
      break;

    case HP3500_COLOR_SCAN:
      output_rowbytes = rowbytes;
      break;

    case HP3500_LINEART_SCAN:
      output_rowbytes = (width + 7) / 8;
      break;
    }

  buffered_rows =
    red_green_offset + green_blue_offset + intra_channel_offset + 1;
  rows_to_begin = buffered_rows;
  rowbuffer_bytes = buffered_rows * rowbytes;
  row_buffer = (unsigned char *) malloc (rowbuffer_bytes);
  output_buffer = (unsigned char *) malloc (rowbytes);

  for (i = j = 0; i < channels; ++i)
    {
      if (i == 1)
	j += red_green_offset;
      else if (i == 2)
	j += green_blue_offset;
      if (merged_channels)
	channel_data[i][1 - oddfirst] = row_buffer + rowbytes * j + i;
      else
	channel_data[i][1 - oddfirst] = row_buffer + rowbytes * j + width * i;
      channel_data[i][oddfirst] =
	channel_data[i][1 - oddfirst] + rowbytes * intra_channel_offset;
    }

  while (((n = rt_get_available_bytes ()) > 0 || rt_is_moving () > 0)
	 && !cancelled_scan)
    {
      if (n == 1 && (rt_is_moving () || rt_get_available_bytes () != 1))
	n = 0;
      if (n > 0)
	{
	 unsigned char buffer[0xffc0];

	  if (n > 0xffc0)
	    n = 0xffc0;
	  else if ((n > 1) && (n & 1))
	    --n;
	  if (rt_get_data (n, buffer) >= 0)
	    {
	      unsigned char *bufnow = buffer;

	      while (n)
		{
		  int numcopy = rowbytes - bytenow;

		  if (numcopy > n)
		    numcopy = n;

		  memcpy (row_buffer + rownow * rowbytes + bytenow,
		  	  bufnow, numcopy);
		  bytenow += numcopy;
		  bufnow += numcopy;
		  n -= numcopy;

		  if (bytenow == rowbytes)
		    {
		      if (!rows_to_begin || !--rows_to_begin)
			{
			  unsigned char *outnow = output_buffer;
                          unsigned x;

			  for (i = x = 0;
			       x < width;
			       ++x, i += merged_channels ? channels : 1)
			    {
			      for (j = 0; j < channels; ++j)
				{
				  unsigned pix =
				    (unsigned char) channel_data[j][i & 1][i];

                                  if (postprocess_gains && postprocess_offsets)
                                  {
                                    int ppidx = j * width + x;

                                    pix = constrain ( pix
                                                       * postprocess_gains[ppidx]
                                                       - postprocess_offsets[ppidx],
                                                      0,
                                                      255);
                                  }
				  *outnow++ = pix;
				}
			    }

			  if (colour == HP3500_GRAY_SCAN || colour == HP3500_LINEART_SCAN)
			    {
			      unsigned char const *in_now = output_buffer;
			      int	bit = 7;

			      outnow = output_buffer;
			      for (i = 0; i < width; ++i)
				{

				  if (colour == HP3500_GRAY_SCAN)
				    {
				      *outnow++ = ((unsigned) in_now[0] * 2989 +
						   (unsigned) in_now[1] * 5870 +
						   (unsigned) in_now[2] * 1140) / 10000;
				    }
				  else
				    {
				      if (bit == 7)
					*outnow = ((in_now[1] < 0x80) ? 0x80 : 0);
				      else if (in_now[1] < 0x80)
					*outnow |= (1 << bit);
				      if (bit == 0)
					{
					  ++outnow;
					  bit = 7;
					}
				      else
					{
					  --bit;
					}
				    }
				  in_now += 3;
				}
			    }
			  if (rows_supplied++ < height &&
			      !((*cbfunc) (params, output_rowbytes, output_buffer)))
			    break;

			  for (i = 0; i < channels; ++i)
			    {
			      for (j = 0; j < 2; ++j)
				{
				  channel_data[i][j] += rowbytes;
				  if (channel_data[i][j] - row_buffer >=
				      rowbuffer_bytes)
				    channel_data[i][j] -= rowbuffer_bytes;
				}
			    }
			}
		      ++total_rows;
		      if (++rownow == buffered_rows)
			rownow = 0;
		      bytenow = 0;
		    }
		}
	    }
	  DBG (30, "total_rows = %d\r", total_rows);
	}
      else
	{
	  usleep (10000);
	}
    }
  DBG (10, "\n");
  if (n < 0)
    result = -1;

  free (output_buffer);
  free (row_buffer);

  rt_stop_moving ();
  return result;
}

static unsigned local_sram_size;
static unsigned char r93setting;

#define RTS8801_F_SUPPRESS_MOVEMENT	1
#define	RTS8801_F_LAMP_OFF		2
#define RTS8801_F_NO_DISPLACEMENTS	4
#define RTS8801_F_ODDX			8

static int
find_resolution_index (unsigned resolution)
{
  int res = 0;

  for (res = 0; resparms[res].resolution != resolution; ++res)
    {
      if (!resparms[res].resolution)
	return -1;
    }
  return res;
}

static int
rts8801_fullscan (unsigned x,
		  unsigned y,
		  unsigned w,
		  unsigned h,
		  unsigned xresolution,
		  unsigned yresolution,
		  unsigned colour,
		  rts8801_callback cbfunc,
		  void *param,
		  unsigned char *calib_info,
		  int flags,
		  int red_calib_offset,
		  int green_calib_offset,
		  int blue_calib_offset,
		  int end_calib_offset,
                  double *postprocess_offsets,
                  double *postprocess_gains)
{
  int ires, jres;
  int tg_setting;
  unsigned char regs[256];
  unsigned char offdutytime;
  int result;
  int scan_frequency;
  unsigned intra_channel_offset;
  unsigned red_green_offset;
  unsigned green_blue_offset;
  unsigned total_offsets;

  ires = find_resolution_index (xresolution);
  jres = find_resolution_index (yresolution);

  if (ires < 0 || jres < 0)
    return -1;

  /* Set scan parameters */

  rt_read_register_immediate (0, 255, regs);
  regs[255] = 0;

  rt_enable_ccd (regs, 1);
  rt_enable_movement (regs, 1);
  rt_set_scan_frequency (regs, 1);

  rt_adjust_misc_registers (regs);

  rt_set_cvtr_wparams (regs, 3, 0, 6);
  rt_set_cvtr_mpt (regs, 15, 15, 15);
  rt_set_cvtr_lm (regs, 7, 7, 7);
  rt_set_motor_type (regs, 2);

  if (rt_nvram_read (0, 0x7b, &offdutytime, 1) < 0 || offdutytime >= 15)
    {
      offdutytime = 6;
    }
  rt_set_lamp_duty_cycle (regs, 1,	/* On */
			  10,	/* Frequency */
			  offdutytime);	/* Off duty time */

  rt_set_movement_pattern (regs, 0x800000);

  rt_set_direction_forwards (regs);
  rt_set_stop_when_rewound (regs, 0);

  rt_set_calibration_addresses (regs, 0, 0, 0, 0, 0);

  rt_set_basic_calibration (regs,
			    calib_info[0], calib_info[1], calib_info[2],
			    calib_info[3], calib_info[4], calib_info[5],
			    calib_info[6], calib_info[7], calib_info[8]);
  regs[0x0b] = 0x70;		/* If set to 0x71, the alternative, all values are low */
  regs[0x40] &= 0xc0;

  if (red_calib_offset >= 0
      && green_calib_offset >= 0
      && blue_calib_offset >= 0)
    {
      rt_set_calibration_addresses (regs, red_calib_offset,
				    green_calib_offset, blue_calib_offset,
				    end_calib_offset,
				    w);
      regs[0x40] |= 0x2f;
    }
  else if (end_calib_offset >= 0)
    {
      rt_set_calibration_addresses (regs, 0x600, 0x600, 0x600,
				    end_calib_offset, w);
    }

  rt_set_channel (regs, RT_CHANNEL_ALL);
  rt_set_single_channel_scanning (regs, 0);
  rt_set_merge_channels (regs, 1);
  rt_set_colour_mode (regs, 1);

  rt_set_last_sram_page (regs, (local_sram_size - 1) >> 5);

  scan_frequency = resparms[jres].scan_frequency;
  rt_set_cph0s (regs, resparms[ires].cph0s);
  if (resparms[ires].d3_bit_3_value)
    regs[0xd3] |= 0x08;
  else
    regs[0xd3] &= 0xf7;

  if (flags & RTS8801_F_SUPPRESS_MOVEMENT)
    regs[0xc3] &= 0x7f;

  regs[0xb2] &= 0xf7;

  rt_set_horizontal_resolution (regs, xresolution);

  rt_set_scanline_start (regs,
			 x * (1200 / xresolution) /
			 (resparms[ires].cph0s ? 1 : 2) /
			 (resparms[ires].d3_bit_3_value ? 1 : 2));
  rt_set_scanline_end (regs,
		       (x +
			w) * (1200 / xresolution) /
		       (resparms[ires].cph0s ? 1 : 2) /
		       (resparms[ires].d3_bit_3_value ? 1 : 2));

  if (flags & RTS8801_F_NO_DISPLACEMENTS)
    {
      red_green_offset = green_blue_offset = intra_channel_offset = 0;
    }
  else
    {
      red_green_offset = resparms[jres].red_green_offset;
      green_blue_offset = resparms[jres].green_blue_offset;
      intra_channel_offset = resparms[jres].intra_channel_offset;
    }
  total_offsets = red_green_offset + green_blue_offset + intra_channel_offset;
  if (y > total_offsets + 2)
    y -= total_offsets;
  h += total_offsets;

  if (yresolution > 75 && !(flags & RTS8801_F_SUPPRESS_MOVEMENT))
    {
      int rmres = find_resolution_index (50);

      if (rmres >= 0)
	{
	  int factor = yresolution / 50;
	  int fastres = y / factor;
	  int remainder = y % factor;

	  while (remainder < 2)
	    {
		--fastres;
		remainder += factor;
	    }

	  if (fastres >= 3)
	    {
	      y = remainder;

	      rt_set_noscan_distance(regs, fastres * resparms[rmres].scan_frequency - 2);
	      rt_set_total_distance(regs, fastres * resparms[rmres].scan_frequency - 1);

	      rt_set_scan_frequency(regs, 1);

	      tg_setting = resparms[rmres].tg;
	      rt_set_ccd_shift_clock_multiplier (regs, tg_info[tg_setting].tg_cph0p);
	      rt_set_ccd_clock_reset_interval (regs, tg_info[tg_setting].tg_crsp);
	      rt_set_ccd_clamp_clock_multiplier (regs, tg_info[tg_setting].tg_cclpp);	      

	      rt_set_one_register (0xc6, 0);
	      rt_set_one_register (0xc6, 0);

	      rt_set_step_size (regs, resparms[rmres].step_size);

	      rt_set_motor_movement_clock_multiplier (regs,
						      resparms[rmres].
							  motor_movement_clock_multiplier);

	      rt_set_cdss (regs, tg_info[tg_setting].tg_cdss1,
			   tg_info[tg_setting].tg_cdss2);
	      rt_set_cdsc (regs, tg_info[tg_setting].tg_cdsc1,
			   tg_info[tg_setting].tg_cdsc2);
	      rt_update_after_setting_cdss2 (regs);

	      regs[0x39] = resparms[rmres].reg_39_value;
	      regs[0xc3] = (regs[0xc3] & 0xf8) | resparms[rmres].reg_c3_value;
	      regs[0xc6] = (regs[0xc6] & 0xf8) | resparms[rmres].reg_c6_value;

	      rt_set_data_feed_off (regs);

	      rt_set_all_registers (regs);

  	      rt_set_one_register (0x2c, regs[0x2c]);

	      if (DBG_LEVEL >= 5)
	        dump_registers (regs);

	      rt_start_moving ();
	      while (rt_is_moving ());
	    }
	}
    }


  rt_set_noscan_distance (regs, y * scan_frequency - 1);
  rt_set_total_distance (regs, scan_frequency * (y + h) - 1);

  rt_set_scan_frequency (regs, scan_frequency);

  tg_setting = resparms[jres].tg;

  rt_set_ccd_shift_clock_multiplier (regs, tg_info[tg_setting].tg_cph0p);
  rt_set_ccd_clock_reset_interval (regs, tg_info[tg_setting].tg_crsp);
  rt_set_ccd_clamp_clock_multiplier (regs, tg_info[tg_setting].tg_cclpp);

  rt_set_one_register (0xc6, 0);
  rt_set_one_register (0xc6, 0);

  rt_set_step_size (regs, resparms[jres].step_size);

  rt_set_motor_movement_clock_multiplier (regs,
					  resparms[jres].
					  motor_movement_clock_multiplier);

  rt_set_cdss (regs, tg_info[tg_setting].tg_cdss1,
	       tg_info[tg_setting].tg_cdss2);
  rt_set_cdsc (regs, tg_info[tg_setting].tg_cdsc1,
	       tg_info[tg_setting].tg_cdsc2);
  rt_update_after_setting_cdss2 (regs);

  regs[0x39] = resparms[jres].reg_39_value;
  regs[0xc3] = (regs[0xc3] & 0xf8) | resparms[jres].reg_c3_value;
  regs[0xc6] = (regs[0xc6] & 0xf8) | resparms[jres].reg_c6_value;

  rt_set_data_feed_on (regs);

  rt_set_all_registers (regs);

  rt_set_one_register (0x2c, regs[0x2c]);

  if (DBG_LEVEL >= 5)
    dump_registers (regs);

  result = rts8801_doscan (w,
			   h,
			   colour,
			   red_green_offset,
			   green_blue_offset,
			   intra_channel_offset,
			   cbfunc, param, (x & 1), calib_info,
			   (regs[0x2f] & 0x04) != 0,
                           postprocess_offsets,
                           postprocess_gains);
  return result;
}

static int
accumfunc (struct dcalibdata *dcd, int bytes, char *data)
{
  unsigned char *c = (unsigned char *) data;

  while (bytes > 0)
    {
      if (dcd->firstrowdone)
	dcd->buffers[dcd->channelnow][dcd->pixelnow - dcd->pixelsperrow] = *c;
      if (++dcd->channelnow >= 3)
	{
	  dcd->channelnow = 0;
	  if (++dcd->pixelnow == dcd->pixelsperrow)
	    ++dcd->firstrowdone;
	}
      c++;
      bytes--;
    }
  return 1;
}

static int
calcmedian (unsigned char const *data,
	    int pixel, int pixels_per_row, int elements)
{
  int tallies[256];
  int i;
  int elemstogo = elements / 2;

  memset (tallies, 0, sizeof (tallies));
  data += pixel;
  for (i = 0; i < elements; ++i)
    {
      ++tallies[*data];
      data += pixels_per_row;
    }
  i = 0;
  while (elemstogo - tallies[i] > 0)
    elemstogo -= tallies[i++];
  return i;
}

struct calibdata
{
  unsigned char *buffer;
  int space;
};

static int
storefunc (struct calibdata *cd, int bytes, char *data)
{
  if (cd->space > 0)
    {
      if (bytes > cd->space)
	bytes = cd->space;
      memcpy (cd->buffer, data, bytes);
      cd->buffer += bytes;
      cd->space -= bytes;
    }
  return 1;
}

static unsigned
sum_channel (unsigned char *p, int n, int bytwo)
{
  unsigned v = 0;

  while (n-- > 0)
    {
      v += *p;
      p += 3;
      if (bytwo)
	p += 3;
    }
  return v;
}

static int do_warmup = 1;

#define DETAILED_PASS_COUNT		3
#define DETAILED_PASS_OFFSETS		0
#define	DETAILED_PASS_GAINS_FIRSTPASS	1
#define	DETAILED_PASS_GAINS_SECONDPASS	2

static int
rts8801_scan (unsigned x,
	      unsigned y,
	      unsigned w,
	      unsigned h,
	      unsigned resolution,
	      unsigned colour,
	      unsigned brightness,
	      unsigned contrast,
	      rts8801_callback cbfunc,
	      void *param,
	      double gamma)
{
  unsigned char calib_info[9];
  unsigned char calibbuf[2400];
  struct dcalibdata dcd;
  struct calibdata cd;
  unsigned char *detail_buffer = 0;
  int iCalibY;
  int iCalibTarget;
  int iMoveFlags = 0;
  unsigned aiBestOffset[6];
  int aiPassed[6];
  int i;
  unsigned j;
  int k;
  int calibration_size;
  unsigned char *pDetailedCalib;
  int red_calibration_offset;
  int green_calibration_offset;
  int blue_calibration_offset;
  int end_calibration_offset;
  int base_resolution;
  int resolution_divisor;
  int resolution_index;
  int detailed_calibration_rows = 50;
  unsigned char *tdetail_buffer;
  int pass;
  int onechanged;
  double *postprocess_gains;
  double *postprocess_offsets;
  int needs_postprocessed_calibration = 0;
  double contrast_adjust = (double) contrast / 64;
  int brightness_adjust = brightness - 0x80;

  /* Initialise and power up */

  rt_set_all_registers (initial_regs);
  rt_set_powersave_mode (0);

  /* Initial rewind in case scanner is stuck away from home position */

  rts8801_rewind ();

  /* Detect SRAM */

  rt_detect_sram (&local_sram_size, &r93setting);

  /* Warm up the lamp */

  DBG (10, "Warming up the lamp\n");

  rt_turn_on_lamp ();
  if (do_warmup)
    sleep (25);

  /* Basic calibration */

  DBG (10, "Calibrating (stage 1)\n");

  calib_info[2] = calib_info[5] = calib_info[8] = 1;

  iCalibY = (resolution == 25) ? 1 : 2;
  iCalibTarget = 550;
 
  rt_turn_off_lamp();

  for (i = 0; i < 6; ++i)
    {
      aiBestOffset[i] = 0xbf;
      aiPassed[i] = 0;
    }
 
  do
    {
      DBG (30, "Initial calibration pass commences\n");

      onechanged = 0;
      for (i = 0; i < 3; ++i)
        {
	  calib_info[i * 3] = aiBestOffset[i];
	  calib_info[i * 3 + 1] = aiBestOffset[i + 3];
        }
 
      cd.buffer = calibbuf;
      cd.space = sizeof (calibbuf);
      DBG (30, "Commencing scan for initial calibration pass\n");
      rts8801_fullscan (1401, iCalibY, 100, 2, 400, resolution,
			HP3500_COLOR_SCAN, (rts8801_callback) storefunc, &cd,
			calib_info, iMoveFlags, -1, -1, -1, -1, 0, 0);
      DBG (30, "Completed scan for initial calibration pass\n");
      iMoveFlags = RTS8801_F_SUPPRESS_MOVEMENT | RTS8801_F_NO_DISPLACEMENTS;
      iCalibY = 2;

      for (i = 0; i < 6; ++i)
	{
	  int sum;

	  if (aiBestOffset[i] >= 255 || aiPassed[i] > 2)
	    continue;
	  sum = sum_channel (calibbuf + i, 50, 1);
	  DBG (20, "channel[%d] sum = %d (target %d)\n", i, sum,
	       iCalibTarget);

	  if (sum < iCalibTarget)
            {
              onechanged = 1;
              ++aiBestOffset[i];
            }
          else
            {
              ++aiPassed[i];
            }
	}
      DBG (30, "Initial calibration pass completed\n");
    }
  while (onechanged);

  DBG (20, "Offsets calculated\n");

  rt_turn_on_lamp();
  usleep(500000);

  tdetail_buffer =
    (unsigned char *) malloc (w * 3 * detailed_calibration_rows);

  for (i = 0; i < 3; ++i)
    {
      calib_info[i * 3 + 2] = 1;
      aiPassed[i] = 0;
    }
 
  do
    {
      struct dcalibdata dcdt;

      dcdt.buffers[0] = tdetail_buffer;
      dcdt.buffers[1] = (tdetail_buffer + w * detailed_calibration_rows);
      dcdt.buffers[2] = (dcdt.buffers[1] + w * detailed_calibration_rows);
      dcdt.pixelsperrow = w;
      dcdt.pixelnow = dcdt.channelnow = dcdt.firstrowdone = 0;
      DBG (20, "Scanning for part 2 of initial calibration\n");
      rts8801_fullscan (x, 4, w, detailed_calibration_rows + 1, resolution,
			resolution, HP3500_COLOR_SCAN,
			(rts8801_callback) accumfunc, &dcdt, calib_info,
			RTS8801_F_SUPPRESS_MOVEMENT | RTS8801_F_NO_DISPLACEMENTS, -1, -1, -1, -1, 0, 0);
      DBG (20, "Scan for part 2 of initial calibration completed\n");

      onechanged = 0;
      for (i = 0; i < 3; ++i)
	{
	  int largest = 1;

          if (aiPassed[i] > 2 || calib_info[i * 3 + 2] >= 63)
            continue;

 	  for (j = 0; j < w; ++j)
	    {
	      int val =
		calcmedian (dcdt.buffers[i], j, w, detailed_calibration_rows);

	      if (val > largest)
		largest = val;
	    }

	  if (largest < 0xe0)
            {
              ++calib_info[i * 3 + 2];
              onechanged = 1;
            }
          else
            {
              ++aiPassed[i];
            }
	}
    }
  while (onechanged);

  for (i = 0; i < 3; ++i)
    {
      DBG (10, "Channel [%d] gain=%02x  offset=%02x\n",
	   i, calib_info[i * 3] + 2, calib_info[i * 3]);
    }

  DBG (20, "Gain factors calculated\n");

  /* Stage 2 calibration */

  DBG (10, "Calibrating (stage 2)\n");

  detail_buffer =
    (unsigned char *) malloc (w * 3 * detailed_calibration_rows);

  dcd.buffers[0] = detail_buffer;
  dcd.buffers[1] = (detail_buffer + w * detailed_calibration_rows);
  dcd.buffers[2] = (dcd.buffers[1] + w * detailed_calibration_rows);
  dcd.pixelsperrow = w;


  /* And now for the detailed calibration */
  resolution_index = find_resolution_index (resolution);
  base_resolution = 300;
  if (resparms[resolution_index].cph0s)
    base_resolution *= 2;
  if (resparms[resolution_index].d3_bit_3_value)
    base_resolution *= 2;
  resolution_divisor = base_resolution / resolution;

  calibration_size = w * resolution_divisor * 6 + 1568 + 96;
  red_calibration_offset = 0x600;
  green_calibration_offset =
    red_calibration_offset + w * resolution_divisor * 2;
  blue_calibration_offset =
    green_calibration_offset + w * resolution_divisor * 2;
  end_calibration_offset =
    blue_calibration_offset + w * resolution_divisor * 2;
  pDetailedCalib = (unsigned char *) malloc (calibration_size);

  memset (pDetailedCalib, 0, calibration_size);

  for (i = 0; i < 3; ++i)
    {
      int idx =
        (i == 0) ? red_calibration_offset :
        (i == 1) ? green_calibration_offset :
                       blue_calibration_offset;

      for (j = 0; j < 256; j++)
        {
          /* Gamma table - appears to be 256 byte pairs for each input
           * range (so the first entry cover inputs in the range 0 to 1,
           * the second 1 to 2, and so on), mapping that input range
           * (including the fractional parts within it) to an output
           * range.
           */
          pDetailedCalib[i * 512 + j * 2] = j;
          pDetailedCalib[i * 512 + j * 2 + 1] = j;
        }

      for (j = 0; j < w; ++j)
        {
          for (k = 0; k < resolution_divisor; ++k)
            {
              pDetailedCalib[idx++] = 0;
              pDetailedCalib[idx++] = 0x80;
            }
        }
    }

  rt_set_sram_page (0);
  rt_set_one_register (0x93, r93setting);
  rt_write_sram (calibration_size, pDetailedCalib);

  postprocess_gains = (double *) malloc(sizeof(double) * 3 * w);
  postprocess_offsets = (double *) malloc(sizeof(double) * 3 * w);

  for (pass = 0; pass < DETAILED_PASS_COUNT; ++pass)
    {
      int ppidx = 0;

      DBG (10, "Performing detailed calibration scan %d\n", pass);

      switch (pass)
      {
      case DETAILED_PASS_OFFSETS:
        rt_turn_off_lamp();
	usleep(500000); /* To be sure it has gone off */
        break;

      case DETAILED_PASS_GAINS_FIRSTPASS:
        rt_turn_on_lamp();
	usleep(500000); /* Give the lamp time to settle */
        break;
      }

      dcd.pixelnow = dcd.channelnow = dcd.firstrowdone = 0;
      rts8801_fullscan (x, iCalibY, w, detailed_calibration_rows + 1,
                        resolution, resolution, HP3500_COLOR_SCAN,
                        (rts8801_callback) accumfunc, &dcd,
			calib_info,
                        RTS8801_F_SUPPRESS_MOVEMENT | RTS8801_F_NO_DISPLACEMENTS,
			red_calibration_offset,
			green_calibration_offset,
			blue_calibration_offset,
			end_calibration_offset,
			0, 0);

      DBG (10, " Detailed calibration scan %d completed\n", pass);

      for (i = 0; i < 3; ++i)
        {
          int idx =
            (i == 0) ? red_calibration_offset :
	    (i == 1) ? green_calibration_offset :
                       blue_calibration_offset;

          for (j = 0; j < w; ++j)
            {
              double multnow = 0x80;
              int offnow = 0;

              /* This seems to be the approach for reg 0x40 & 0x3f == 0x27, which allows detailed
               * calibration to return either higher or lower values.
               */

              {
                double denom1 =
                  calcmedian (dcd.buffers[i], j, w, detailed_calibration_rows);

		switch (pass)
                  {
                  case DETAILED_PASS_OFFSETS:
                    /* The offset is the number needed to be subtracted from "black" at detailed gain = 0x80,
                     * which is the value we started with. For the next round, pull the gain down to 0x20. Our
                     * next scan is a test scan to confirm the offset works.
                     */
                    multnow = 0x20;
                    offnow = denom1;
                    break;

                  case DETAILED_PASS_GAINS_FIRSTPASS:
                    multnow = 128.0 / denom1 * 0x20; /* Then bring it up to whatever we need to hit 192 */
                    if (multnow > 255)
                      multnow = 255;
                    offnow = pDetailedCalib[idx];
                    break;

                  case DETAILED_PASS_GAINS_SECONDPASS:
                    multnow = 255.0 / denom1 * contrast_adjust * pDetailedCalib[idx+1]; /* And finally to 255 */
                    offnow = pDetailedCalib[idx] - brightness_adjust * 0x80 / multnow;

                    if (offnow < 0)
                      {
                        postprocess_offsets[ppidx] = multnow * offnow / 0x80;
                        offnow = 0;
                        needs_postprocessed_calibration = 1;
                      }
                    else if (offnow > 255)
                      {
                        postprocess_offsets[ppidx] = multnow * (offnow - 255) / 0x80;
                        offnow = 255;
                        needs_postprocessed_calibration = 1;
                      }
                    else
                      {
                        postprocess_offsets[ppidx] = 0;
                      }
                    if (multnow > 255)
                      {
                        postprocess_gains[ppidx] = multnow / 255;
                        multnow = 255;
                        needs_postprocessed_calibration = 1;
                      }
                    else
                      {
                        postprocess_gains[ppidx] = 1.0;
                      }
                    break;
                  }
              }
              if (offnow > 255)
                offnow = 255;

              for (k = 0; k < resolution_divisor; ++k)
                {
                  pDetailedCalib[idx++] = offnow;         /* Subtract this value from the result  at gains = 0x80*/
                  pDetailedCalib[idx++] = multnow;        /* Then multiply by this value divided by 0x80	*/
                }
              ++ppidx;
            }
        }

      if (pass == DETAILED_PASS_GAINS_SECONDPASS)
        {
           /* Build gamma table */
           unsigned char *redgamma = pDetailedCalib;
           unsigned char *greengamma = redgamma + 512;
           unsigned char *bluegamma = greengamma + 512;
           double val;
	   double invgamma = 1.0l / gamma;

           *redgamma++ = *bluegamma++ = *greengamma++ = 0;

           /* The windows driver does a linear interpolation for the next 19 boundaries */
           val = pow (20.0l / 255, invgamma) * 255;

	   for (j = 1; j <= 20; ++j)
             {
               *redgamma++ = *bluegamma++ = *greengamma++ = val * j / 20 + 0.5;
               *redgamma++ = *bluegamma++ = *greengamma++ = val * j / 20 + 0.5;
             }

           for (; j <= 255; ++j)
             {
               val = pow((double) j / 255, invgamma) * 255;

               *redgamma++ = *bluegamma++ = *greengamma++ = val + 0.5;
               *redgamma++ = *bluegamma++ = *greengamma++ = val + 0.5;
             }
           *redgamma++ = *bluegamma++ = *greengamma++ = 255;
        }

      DBG (10, "\n");

      rt_set_sram_page (0);
      rt_set_one_register (0x93, r93setting);
      rt_write_sram (calibration_size, pDetailedCalib);
    }

  /* And finally, perform the scan */
  DBG (10, "Scanning\n");

  rts8801_rewind ();

  rts8801_fullscan (x, y, w, h, resolution, resolution, colour, cbfunc, param,
		    calib_info, 0,
		    red_calibration_offset, green_calibration_offset,
		    blue_calibration_offset, end_calibration_offset,
                    needs_postprocessed_calibration ? postprocess_offsets : 0,
                    needs_postprocessed_calibration ? postprocess_gains : 0);

  rt_turn_off_lamp ();

  rts8801_rewind ();
  rt_set_powersave_mode (1);

  if (pDetailedCalib)
    free (pDetailedCalib);
  if (detail_buffer)
    free (detail_buffer);
  if (tdetail_buffer)
    free(tdetail_buffer);
  if (postprocess_gains)
    free(postprocess_gains);
  if (postprocess_offsets)
    free(postprocess_offsets);
  return 0;
}

static int
writefunc (struct hp3500_write_info *winfo, int bytes, char *data)
{
  static int warned = 0;

  if (bytes > winfo->bytesleft)
    {
      if (!warned)
	{
	  warned = 1;
	  DBG (1, "Overflow protection triggered\n");
	  rt_stop_moving ();
	}
      bytes = winfo->bytesleft;
      if (!bytes)
	return 0;
    }
  winfo->bytesleft -= bytes;
  return write (winfo->scanner->pipe_w, data, bytes) == bytes;
}

static void
sigtermHandler (int signal)
{
  signal = signal;		/* get rid of compiler warning */
  cancelled_scan = 1;
}

static int
reader_process (void *pv)
{
  struct hp3500_data *scanner = pv;
  time_t t;
  sigset_t ignore_set;
  sigset_t sigterm_set;
  struct SIGACTION act;
  struct hp3500_write_info winfo;
  int status;

  if (sanei_thread_is_forked ())
    {
      close (scanner->pipe_r);

      sigfillset (&ignore_set);
      sigdelset (&ignore_set, SIGTERM);
#if     defined (__APPLE__) && defined (__MACH__)
      sigdelset (&ignore_set, SIGUSR2);
#endif
      sigprocmask (SIG_SETMASK, &ignore_set, 0);

      sigemptyset (&sigterm_set);
      sigaddset (&sigterm_set, SIGTERM);

      memset (&act, 0, sizeof (act));
#ifdef     _POSIX_SOURCE
      act.sa_handler = sigtermHandler;
#endif
      sigaction (SIGTERM, &act, 0);
    }

  /* Warm up the lamp again if our last scan ended more than 5 minutes ago. */
  time (&t);
  do_warmup = (t - scanner->last_scan) > 300;

  if (getenv ("HP3500_NOWARMUP") && atoi (getenv ("HP3500_NOWARMUP")) > 0)
    do_warmup = 0;

  udh = scanner->sfd;

  cancelled_scan = 0;

  winfo.scanner = scanner;
  winfo.bytesleft =
    scanner->bytes_per_scan_line * scanner->scan_height_pixels;

  if (getenv ("HP3500_SLEEP"))
    {
      int seconds = atoi (getenv ("HP3500_SLEEP"));

      DBG (1, "Backend process %d sleeping for %d seconds\n", getpid (),
	   seconds);
      sleep (seconds);
    }
  DBG (10, "Scanning at %ddpi, mode=%s\n", scanner->resolution,
       scan_mode_list[scanner->mode]);
  if (rts8801_scan
      (scanner->actres_pixels.left + 250 * scanner->resolution / 1200,
       scanner->actres_pixels.top + 599 * scanner->resolution / 1200,
       scanner->actres_pixels.right - scanner->actres_pixels.left,
       scanner->actres_pixels.bottom - scanner->actres_pixels.top,
       scanner->resolution, scanner->mode, scanner->brightness,
       scanner->contrast, (rts8801_callback) writefunc, &winfo,
       scanner->gamma) >= 0)
    status = SANE_STATUS_GOOD;
  status = SANE_STATUS_IO_ERROR;
  close (scanner->pipe_w);
  return status;
}

static size_t
max_string_size (char const **strings)
{
  size_t size, max_size = 0;
  SANE_Int i;

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