/* sane - Scanner Access Now Easy.

   Copyright (C) 1999 Paul Mackerras
   Copyright (C) 2000 Adrian Perez Jorge
   Copyright (C) 2001 Frank Zago
   Copyright (C) 2001 Marcio Teixeira

   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.

   Defines a driver and API for accessing the UMAX Astra 1220U
   USB scanner. Based on the original command line tool by Paul
   Mackerras.

   The UMAX Astra 1220U scanner uses the PowerVision PV8630
   Parallel Port to USB bridge. This chip is also used
   by the HP4200C flatbed scanner. Adrian Perez Jorge wrote
   a nice interface file for that chip and Frank Zago adapted
   it to use the sanei_usb interface. Thanks, guys, for making
   my life easier! :)

 */

#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

/* 
 * The backend performs test scans in order to calibrate
 * the CCD and to find the zero location. If you would like
 * to look at those scans, define DEBUG_CALIBRATION to have
 * the backend save "find_zero.pgm" and "calibration.pgm" to
 * disk.
 */
/*#define DEBUG_CALIBRATION*/

/*
 * Define DEBUG_BOUNDS to insert paranoid array bounds
 * overrun detection into the code.
 */
/* #define DEBUG_BOUNDS */

/* These values are empirically determined and are given
 * in 1/600 inch units. If UMAX_MAX_HEIGHT is too large,
 * the scanner may grind its gears. I assume there is a
 * physical limit to UMAX_MAX_WIDTH as well (based on the
 * sensor size) but I do not know what it is. The current
 * value can be increased beyond what it is now, but you
 * gain nothing in usuable scan area (you only scan more
 * of the underside of the scanner's plastic lid).
 */

#define UMAX_MAX_WIDTH    5400
#define UMAX_MAX_HEIGHT   7040

/* Buffer size. Specifies the size of the buffer that is
 * used to copy data from the scanner. The old command
 * line driver had this set at 0x80000 which is likely
 * the largest possible chunck of data that can be.
 * at once. This is probably most efficient, but using
 * a lower value for the SANE driver makes the driver
 * more responsive to interaction.
 */
#define BUFFER_SIZE 0x80000

/* Constants that can be used with set_lamp_state to
 * control the state of the scanner's lamp
 */
typedef enum
{
  UMAX_LAMP_OFF = 0,
  UMAX_LAMP_ON = 1
}
UMAX_Lamp_State;

/* Constants that can be used with move to control
 * the rate of scanner head movement
 */
typedef enum
{
  UMAX_NOT_FINE = 0,
  UMAX_FINE = 1
}
UMAX_Speed;

/* If anyone knows some descriptive names for these,
 * please update
 */
typedef enum
{
  CMD_0 = 0x00,
  CMD_1 = 0x01,
  CMD_2 = 0x02,
  CMD_4 = 0x04,
  CMD_8 = 0x08,
  CMD_40 = 0x40,
  CMD_WRITE = 0x80,
  CMD_READ = 0xC0
}
UMAX_Cmd;

/* Product IDs for Astra scanners
 */
typedef enum
{
  ASTRA_1220U = 0x0010,
  ASTRA_2000U = 0x0030,
  ASTRA_2100U = 0x0130
}
UMAX_Model;

/* The bytes UMAX_SYNC1 and UMAX_SYNC2 serve as a
 * synchronization signal. Unintentional sync bytes
 * in the data stream are escaped with UMAX_ESCAPE
 * character
 */

#define UMAX_SYNC1  0x55
#define UMAX_SYNC2  0xAA
#define UMAX_ESCAPE 0x1B

/* Status bits. These bits are active low.
 * In umax_pp, UMAX_REVERSE_BIT is called
 * MOTOR_BIT.
 */

#define UMAX_FORWARD_BIT       0x40
#define UMAX_ERROR_BIT         0x20
#define UMAX_MOTOR_OFF_BIT     0x08

#define UMAX_OK                0x48        /* Used to be 0xC8 */
#define UMAX_OK_WITH_MOTOR     0x40        /* Used to be 0xD0 */

#define UMAX_STATUS_MASK       0x68

/* This byte is used as a placeholder for bytes that are parameterized
 * in the opcode strings */

#define XXXX 0x00

/* This macro is used to check the return code of
 * functions
 */
#define CHK(A) {if( (res = A) != SANE_STATUS_GOOD ) { \
                 DBG( 1, "Failure on line of %s: %d\n", __FILE__, \
                      __LINE__ ); return A; }}

/* Macros that are used for array overrun detection
 * (when DEBUG_BOUNDS is defined)
 */
#ifdef DEBUG_BOUNDS
#define PAD 10
#define PAD_ARRAY( A, len ) {int i; \
                 for( i = 0; i < PAD; i++ ) {A[len+i]=0x55;}}

#define CHK_ARRAY( A, len ) {int i;for( i = 0; i < PAD; i++ ) {\
   if(A[len+i]!=0x55) { \
     DBG( 1, "Array overrun detected on line %d\n", __LINE__ ); \
   }}}
#else
#define PAD 0
#define PAD_ARRAY( A, len )
#define CHK_ARRAY( A, len )
#endif

/* This data structure contains data related
 * to the scanning process.
 */
typedef struct
{
  /* Constant data */

  int color;
  int w;
  int h;
  int xo;
  int yo;
  int xdpi;                      /* Physical x dpi */
  int ydpi;                      /* Physical y dpi */
  int xsamp;
  int ysamp;

  int xskip;
  int yskip;

  int fd;                        /* Device file handle */
  UMAX_Model model;

  /* Raw scan data buffer */

  unsigned char *p;
  int bh;                        /* Size of buffer in lines    */
  int hexp;                      /* Scan lines yet to be read  */

  /* Decoding logic */

  int x, y, maxh;
  int done;                      /* Boolean, all lines decoded */

  /* Calibration data */

  unsigned char caldata[16070 + PAD];

  /* Scan head position */

  int scanner_ypos;
  int scanner_yorg;
}
UMAX_Handle;

typedef unsigned char UMAX_Status_Byte;

/* This seems to configure the pv8630 chip somehow. I wish
 * all the magic numbers were defined as self-descriptive
 * constants somewhere. I made some guesses based on what
 * I found in "pv8630.c", but alas there wasn't enough in
 * there. If you know what this does, please let me know!
 */
static SANE_Status
xxxops (UMAX_Handle * scan)
{
  SANE_Status res;

  DBG (9, "doing xxxops\n");

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_RMODE, 0x02));

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_UNKNOWN, 0x0E));
  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_RDATA, 0x40));
  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_UNKNOWN, 0x06));
  CHK (sanei_pv8630_xpect_byte (scan->fd, PV8630_RSTATUS, 0x38, 0xFF));

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_UNKNOWN, 0x07));
  CHK (sanei_pv8630_xpect_byte (scan->fd, PV8630_RSTATUS, 0x38, 0xFF));

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_UNKNOWN, 0x04));
  CHK (sanei_pv8630_xpect_byte (scan->fd, PV8630_RSTATUS, 0xF8, 0xFF));

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_UNKNOWN, 0x05));
  CHK (sanei_pv8630_xpect_byte (scan->fd, PV8630_UNKNOWN, 0x05, 0xFF));

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_UNKNOWN, 0x04));

  CHK (sanei_pv8630_write_byte (scan->fd, PV8630_RMODE, 0x1E));

  return res;
}

/*
Apparently sends the two syncronization characters followed
by the command length, followed by the command number
*/
static SANE_Status
usync (UMAX_Handle * scan, UMAX_Cmd cmd, int len)
{
  UMAX_Status_Byte s0, s4;
  SANE_Status res;
  unsigned char buf[4];
  size_t nb;

  DBG (80, "usync: len = %d, cmd = %d\n", len, cmd);

  buf[0] = UMAX_SYNC1;
  buf[1] = UMAX_SYNC2;

  nb = 2;
  CHK (sanei_pv8630_flush_buffer (scan->fd));
  CHK (sanei_pv8630_prep_bulkwrite (scan->fd, nb));
  CHK (sanei_pv8630_bulkwrite (scan->fd, buf, &nb));

  CHK (sanei_pv8630_wait_byte
       (scan->fd, PV8630_RSTATUS, UMAX_OK, UMAX_STATUS_MASK, 20));

  buf[0] = len >> 16;
  buf[1] = len >> 8;
  buf[2] = len;
  buf[3] = cmd;

  nb = 4;
  CHK (sanei_pv8630_flush_buffer (scan->fd));
  CHK (sanei_pv8630_prep_bulkwrite (scan->fd, nb));
  CHK (sanei_pv8630_bulkwrite (scan->fd, buf, &nb));

  CHK (sanei_pv8630_read_byte (scan->fd, PV8630_RDATA, &s0));
  CHK (sanei_pv8630_read_byte (scan->fd, PV8630_RSTATUS, &s4));

  DBG (90, "usync: s0 = %#x s4 = %#x\n", s0, s4);

  return SANE_STATUS_GOOD;
}

/*
This function escapes any syncronization sequence that may be
in data, storing the result in buf. In the worst case where
every character gets escaped buf must be at least twice as
large as dlen.
*/
static int
bescape (const unsigned char *data, int dlen, unsigned char *buf, int blen)
{
  const unsigned char *p;
  unsigned char *q;
  int i, c;
  i = blen;   /* Eliminate compiler warning about unused param */

  p = data;
  q = buf;
  for (i = 0; i < dlen; ++i)
    {
      c = *p++;
      if (c == UMAX_ESCAPE
          || (c == UMAX_SYNC2 && i > 0 && p[-2] == UMAX_SYNC1))
        *q++ = UMAX_ESCAPE;
      *q++ = c;
#ifdef DEBUG_BOUNDS
      if (q > (buf + blen))
        {
          DBG (2, "Overrun error in bescape!\n");
        }
#endif
    }
  return q - buf;
}

static SANE_Status
cwrite (UMAX_Handle * scan, UMAX_Cmd cmd, size_t len, const unsigned char *data,
        UMAX_Status_Byte * s)
{
  SANE_Status res;
  UMAX_Status_Byte s0, s4;

  static unsigned char *escaped = NULL;
  static size_t escaped_size = 0;

  DBG (80, "cwrite: cmd = %d, len = %lu\n", cmd, (u_long) len);

  CHK (usync (scan, cmd | CMD_WRITE, len));

  if (len <= 0)
    return SANE_STATUS_GOOD;

  if (escaped_size < len * 2)
    {
      escaped_size = len * 2;
      if (escaped)
        free (escaped);
      escaped = malloc (escaped_size);
      if (escaped == NULL)
        return SANE_STATUS_NO_MEM;
    }

  len = bescape (data, len, escaped, len * 2);

  CHK (sanei_pv8630_wait_byte
       (scan->fd, PV8630_RSTATUS, UMAX_OK, UMAX_STATUS_MASK, 20));

  CHK (sanei_pv8630_flush_buffer (scan->fd));
  CHK (sanei_pv8630_prep_bulkwrite (scan->fd, len));
  CHK (sanei_pv8630_bulkwrite (scan->fd, escaped, &len));

  CHK (sanei_pv8630_read_byte (scan->fd, PV8630_RSTATUS, &s4));
  CHK (sanei_pv8630_read_byte (scan->fd, PV8630_RDATA, &s0));

  DBG (90, "cwrite: s0 = %#x s4 = %#x\n", s0, s4);

  if (s)
    *s = s0;

  return SANE_STATUS_GOOD;
}

static SANE_Status
cread (UMAX_Handle * scan, UMAX_Cmd cmd, size_t len, unsigned char *data,
       UMAX_Status_Byte * s)
{
  SANE_Status res;
  UMAX_Status_Byte s0, s4;

  DBG (80, "cread: cmd = %d, len = %lu\n", cmd, (u_long) len);

  CHK (usync (scan, cmd | CMD_READ, len));

  if (len > 0)
    {
      CHK (sanei_pv8630_wait_byte
           (scan->fd, PV8630_RSTATUS, UMAX_OK_WITH_MOTOR, UMAX_STATUS_MASK,
            2000));

      while (len > 0)
        {
          size_t req, n;

          req = n = (len > 0xf000) ? 0xf000 : len;
          CHK (sanei_pv8630_prep_bulkread (scan->fd, n));
          CHK (sanei_pv8630_bulkread (scan->fd, data, &n));
          if (n < req)
            {
              DBG (1, "qread: Expecting to read %lu, only got %lu\n", (u_long) req, (u_long) n);
              return SANE_STATUS_IO_ERROR;
            }
          data += n;
          len -= n;
        }
    }

  CHK (sanei_pv8630_read_byte (scan->fd, PV8630_RSTATUS, &s4));
  CHK (sanei_pv8630_read_byte (scan->fd, PV8630_RDATA, &s0));

  DBG (90, "cwrite: s0 = %#x s4 = %#x\n", s0, s4);

  if (s)
    *s = s0;

  return SANE_STATUS_GOOD;
}

/* Seems to be like cwrite, with a verification option? */
static SANE_Status
cwritev (UMAX_Handle * scan, UMAX_Cmd cmd, size_t len, const unsigned char *data,
         UMAX_Status_Byte * s)
{
  SANE_Status res;
  unsigned char buf[16384];

  /* Write out the opcode */

  CHK (cwrite (scan, cmd, len, data, s));
  if (len <= 0)
    return SANE_STATUS_GOOD;

  /* Read the opcode back */

  CHK (cread (scan, cmd, len, buf, NULL));
  if (bcmp (buf, data, len))
    {
      DBG (1, "cwritev: verification failed\n");
      return SANE_STATUS_IO_ERROR;
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
csend (UMAX_Handle * scan, UMAX_Cmd cmd)
{
  DBG (80, "csend: cmd = %d\n", cmd);

  return usync (scan, cmd, 0);
}

#if 0
static void
unused_operations ()
{
  /* These operations are unused anywhere in the driver */

  unsigned char opb8[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x04,
    0x40, 0x01, 0x00, 0x00, 0x04, 0x00, 0x6e, 0x18, 0x10, 0x03,
    0x06, 0x00, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x13, 0x1a
  };

  unsigned char opb9[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x04,
    0x40, 0x01, 0x00, 0x00, 0x04, 0x00, 0x6e, 0x41, 0x20, 0x24,
    0x06, 0x00, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x13, 0x1a
  };

  unsigned char opb10[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x04,
    0x40, 0x01, 0x00, 0x00, 0x04, 0x00, 0x6e, 0x41, 0x60, 0x4f,
    0x06, 0x00, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x93, 0x1a
  };

  unsigned char opc5[16] = {
    0x05, 0x00, 0x00, 0x70, 0x00, 0x00, 0x60, 0x2f, 0x2f, 0x00,
    0x00, 0x30, 0x0c, 0xc3, 0xa4, 0x00
  };

  unsigned char opc6[16] = {
    0x01, 0x00, 0x00, 0x70, 0x00, 0x00, 0x60, 0x2f, 0x2f, 0x00,
    0x88, 0x48, 0x0c, 0x83, 0xa4, 0x00
  };

  unsigned char opc7[16] = {
    0x05, 0x00, 0x00, 0x70, 0x00, 0x00, 0x60, 0x2f, 0x2f, 0x00,
    0xec, 0x4e, 0x0c, 0xc3, 0xa4, 0x00
  };

  unsigned char opd2[8] = {
    0x06, 0xf4, 0xff, 0x81, 0x3d, 0x00, 0x00, 0x30
  };
}
#endif

static SANE_Status
cwritev_opc1_lamp_ctrl (UMAX_Handle * scan, UMAX_Lamp_State state)
{
  unsigned char opc1[16] = {
    0x01, 0x00, 0x01, 0x70, 0x00, 0x00, 0x60, 0x2f, 0x13, 0x05,
    0x00, 0x00, 0x00, 0x80, 0xf0, 0x00
  };

  DBG (9, "cwritev_opc1: set lamp state = %s\n",
       (state == UMAX_LAMP_OFF) ? "off" : "on");
  opc1[14] = (state == UMAX_LAMP_OFF) ? 0x90 : 0xf0;
  return cwritev (scan, CMD_2, 16, opc1, NULL);
}

/* Used by umaxinit and finish_scan */

static SANE_Status
cwritev_opb3_restore (UMAX_Handle * scan)
{
  unsigned char opb3[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x03,
    0xc1, 0x80, 0x00, 0x00, 0x04, 0x00, 0x16, 0x80, 0x15, 0x78,
    0x03, 0x03, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x1b, 0x1a,
  };

  SANE_Status res;

  DBG (9, "cwritev_opb3_restore:\n");
  CHK (cwritev (scan, CMD_8, 35, opb3, NULL));
  CHK (csend (scan, CMD_40));
  return SANE_STATUS_GOOD;
}

/* Used in move and get_caldata */
static unsigned char ope[8] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
};

/*
This function seems to perform various things. First, it loads a default
gamma information (which is used for the calibration scan), returns the
head to the park position, and turns the lamp on. This function used to
be split up into two parts, umaxinit and umaxinit2.
*/

static SANE_Status
umaxinit (UMAX_Handle * scan)
{
  unsigned char opb[34] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x03,
    0xc1, 0x80, 0x60, 0x20, 0x00, 0x00, 0x16, 0x41, 0xe0, 0xac,
    0x03, 0x03, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0xf0,
  };
  unsigned char opb1[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x03,
    0xc1, 0x80, 0x00, 0x20, 0x02, 0x00, 0x16, 0x41, 0xe0, 0xac,
    0x03, 0x03, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x1a
  };
  unsigned char opb2[35] = {
    0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x03,
    0xc1, 0x80, 0x00, 0x20, 0x02, 0x00, 0x16, 0x41, 0xe0, 0xac,
    0x03, 0x03, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x1a,
  };
  unsigned char opb4[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x03,
    0xc1, 0x80, 0x60, 0x20, 0x00, 0x00, 0x16, 0x41, 0xe0, 0xac,
    0x03, 0x03, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0xf3, 0x1b
  };
  unsigned char opbx[35];
  unsigned char opc[16] = {
    0x02, 0x80, 0x00, 0x70, 0x00, 0x00, 0x60, 0x2f, 0x2f, 0x07,
    0x00, 0x00, 0x00, 0x80, 0xf0, 0x00
  };
  unsigned char opcx[16];
  unsigned char opd[8] = {
    0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa
  };

  SANE_Status res;
  UMAX_Status_Byte s;
  unsigned char ramp[800];
  int i;
  unsigned char *p;

  DBG (3, "umaxinit called\n");

  CHK (csend (scan, CMD_0));
  CHK (xxxops (scan));

  CHK (cwritev (scan, CMD_8, 34, opb, &s));
  CHK (cread (scan, CMD_8, 35, opbx, &s));

  CHK (cwritev (scan, CMD_8, 35, opb1, &s));

  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (4, "umaxinit: checkpoint 2:\n");

  /* The following code appears to send three 256 entry, 8-bit gamma tables
   * to the scanner
   */
  p = ramp;
  *p++ = 0;
  *p++ = 0;
  *p++ = 0;
  for (i = 0; i < 256; ++i)
    *p++ = i;
  for (i = 0; i < 256; ++i)
    *p++ = i;
  for (i = 0; i < 256; ++i)
    *p++ = i;
  *p++ = 0xaa;
  *p++ = 0xaa;

  res = cwritev (scan, CMD_4, p - ramp, ramp, &s);
  if (res != SANE_STATUS_GOOD)
    {
      DBG (4, "umaxinit: Writing ramp 1 failed (is this a 2000U?)\n");
    }
  CHK (cwritev (scan, CMD_8, 35, opb1, &s));

  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (4, "umaxinit: checkpoint 3:\n");

  /* The following code appears to send a 256 entry, 16-bit gamma table
   * to the scanner
   */
  p = ramp;
  for (i = 0; i < 256; ++i)
    {
      *p++ = i;
      *p++ = 0;
    }

  res = cwrite (scan, CMD_4, p - ramp, ramp, &s);
  if (res != SANE_STATUS_GOOD)
    {
      DBG (4, "umaxinit: Writing ramp 2 failed (is this a 2000U?)\n");
    }
  CHK (cwritev (scan, CMD_8, 35, opb2, &s));

  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (4, "umaxinit: checkpoint 4:\n");

  /* The following code appears to send a 256 entry, 16-bit gamma table
   * to the scanner.
   */
  p = ramp;
  for (i = 0; i < 256; ++i)
    {
      *p++ = i;
      *p++ = 4;
    }

  res = cwritev (scan, CMD_4, p - ramp, ramp, &s);
  if (res != SANE_STATUS_GOOD)
    {
      DBG (4, "umaxinit: Writing ramp 3 failed (is this a 2000U?)\n");
    }
  CHK (cwritev (scan, CMD_8, 35, opb1, &s));

  CHK (cwritev (scan, CMD_2, 16, opc, NULL));
  CHK (cwritev (scan, CMD_1, 8, opd, NULL));
  CHK (csend (scan, CMD_0));
  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (4, "umaxinit: checkpoint 5: s = %#x\n", s);

  if ((s & 0x40) == 0)
    {
      DBG (4, "umaxinit: turning on lamp and restoring\n");
      CHK (cwritev_opc1_lamp_ctrl (scan, UMAX_LAMP_ON));
      CHK (cwritev_opb3_restore (scan));

      for (i = 0; i < 60; ++i)
        {
          CHK (cread (scan, CMD_2, 0, NULL, &s));
          DBG (4, "umaxinit: s = %#x\n", s);
          if ((s & 0x40) != 0)
            break;
          DBG (4, "umaxinit: sleeping\n");
          usleep (500000);
        }
    }

  DBG (4, "umaxinit: checkpoint 6\n");

  CHK (csend (scan, CMD_0));

  /* The following stuff used to be in umaxinit2() */

  DBG (4, "umaxinit: checkpoint 7\n");

  CHK (xxxops (scan));
  CHK (csend (scan, CMD_0));
  CHK (xxxops (scan));

  CHK (cwritev (scan, CMD_8, 34, opb4, &s));
  CHK (cread (scan, CMD_8, 35, opbx, &s));
  CHK (cread (scan, CMD_2, 16, opcx, &s));

  CHK (cwritev (scan, CMD_2, 16, opc, NULL));
  CHK (cwritev (scan, CMD_1, 8, opd, NULL));
  CHK (csend (scan, CMD_0));
  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (4, "umaxinit: checkpoint 8: s = %d\n", s);

  return SANE_STATUS_GOOD;
}

static SANE_Status
move (UMAX_Handle * scan, int distance, UMAX_Speed fine)
{
  unsigned char opc4[16] = {
    0x01, XXXX, XXXX, XXXX, 0x00, 0x00, 0x60, 0x2f, XXXX, XXXX,
    0x00, 0x00, 0x00, 0x80, XXXX, 0x00
  };
  unsigned char opb5[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x04,
    0x40, 0x01, 0x00, 0x00, 0x04, 0x00, 0x6e, 0xf6, 0x79, 0xbf,
    0x06, 0x00, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x13, 0x1a
  };
  unsigned char opb7[35] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x6e, 0xf6, 0x79, 0xbf,
    0x01, 0x00, 0x00, 0x00, 0x46, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x13, 0x1a
  };
  unsigned char ope2[3] = {
    0x00, 0xff, 0x8f
  };
  unsigned char buf[512 + PAD];

  SANE_Status res;
  UMAX_Status_Byte s;

  SANE_Bool rev = distance < 0;
  int skip = (rev ? -distance : distance) - 1;

  DBG (9, "move: distance = %d, scanner_ypos = %d\n", distance,
       scan->scanner_ypos);

  PAD_ARRAY (buf, 512);

  if (distance == 0)
    return SANE_STATUS_GOOD;

  opc4[1] = skip << 6;
  opc4[2] = skip >> 2;
  opc4[3] = (rev ? 0x20 : 0x70) + ((skip >> 10) & 0xf);
  opc4[9] = rev ? 0x01 : 0x05;

  if (fine == UMAX_FINE)
    {
      opc4[8]  = 0x2f;
      opc4[14] = 0xa4;
    }
  else
    {
      opc4[8]  = 0x17;
      opc4[14] = 0xac;
    }

  scan->scanner_ypos +=
    (fine == UMAX_FINE ? distance : 2 * distance + (rev ? -1 : 1));
  scan->scanner_ypos = (scan->scanner_ypos + (rev ? 0 : 3)) & ~3;

  CHK (cwrite (scan, CMD_2, 16, opc4, &s));
  CHK (cwrite (scan, CMD_8, 35, rev ? opb7 : opb5, &s));

  CHK (cread (scan, CMD_2, 0, NULL, &s));
  DBG (10, "move: checkpoint 1: s = %d\n", s);

  CHK (csend (scan, CMD_0));
  if (rev)
    {
      CHK (cwrite (scan, CMD_4, 3, ope2, &s));
    }
  else
    {
      CHK (cwrite (scan, CMD_4, 8, ope, &s));
    }

  CHK (csend (scan, CMD_40));

  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (10, "move: checkpoint 2: s = %d\n", s);

  CHK (cread (scan, CMD_2, 0, NULL, &s));
  DBG (10, "move: checkpoint 3: s = %d\n", s);

  CHK (cread (scan, CMD_4, 512, buf, &s));

  CHK_ARRAY (buf, 512);

  return res;
}

static SANE_Status
get_pixels (UMAX_Handle * scan, unsigned char *op2, unsigned char *op8,
            unsigned char *op1, unsigned char *op4, int len, int zpos,
            unsigned char *buf)
{
  SANE_Status res;
  UMAX_Status_Byte s;

  DBG (9, "get_pixels: len = %d, zpos = %d\n", len, zpos);

  if (zpos == 0)
    CHK (csend (scan, CMD_0));

  CHK (cwrite (scan, CMD_2, 16, op2, &s));
  CHK (cwrite (scan, CMD_8, 35, op8, &s));
  CHK (cwrite (scan, CMD_1, 8, op1, &s));
  CHK (cread (scan, CMD_2, 0, NULL, &s));

  if (zpos == 1)
    CHK (csend (scan, CMD_0));

  CHK (cwrite (scan, CMD_4, 8, op4, &s));
  CHK (csend (scan, CMD_40));
  CHK (cread (scan, CMD_2, 0, NULL, &s));

  CHK (cread (scan, CMD_2, 0, NULL, &s));

  CHK (cread (scan, CMD_4, len, buf, &s));
  return SANE_STATUS_GOOD;
}

static int
locate_black_stripe (unsigned char *img, int w, int h)
{
  int epos, ecnt, x, y;
  unsigned char *p;

  epos = 0;
  ecnt = 0;
  p = img;
  for (x = 0; x < w; ++x, ++p)
    {
      int d, dmax = 0, dpos = 0;
      unsigned char *q = img + x;
      for (y = 1; y < h; ++y, q += w)
        {
          d = q[0] - q[w];
          if (d > dmax)
            {
              dmax = d;
              dpos = y;
            }
        }
      if (dmax > 0)
        {
          epos += dpos;
          ++ecnt;
        }
    }
  if (ecnt == 0)
    epos = 70;
  else
    epos = (epos + ecnt / 2) / ecnt;
  return epos;
}

static SANE_Status
find_zero (UMAX_Handle * scan)
{
  unsigned char opc3[16] = {
    0xb4, 0x00, 0x00, 0x70, 0x00, 0x00, 0x60, 0x2f, 0x2f, 0x05,
    0x00, 0x00, 0x00, 0x80, 0xa4, 0x00
  };
  unsigned char ope1[8] = {
    0x00, 0x00, 0x00, 0xaa, 0xcc, 0xee, 0x80, 0xff
  };
  unsigned char opb6[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x04,
    0x40, 0x01, 0x00, 0x00, 0x04, 0x00, 0x6e, 0xfb, 0xc4, 0xe5,
    0x06, 0x00, 0x00, 0x60, 0x4d, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x13, 0x1a
  };
  unsigned char opd1[8] = {
    0x06, 0xf4, 0xff, 0x81, 0x3d, 0x00, 0x08, 0x00
  };

  SANE_Status res;
  int s;
  unsigned char *img;

  DBG (9, "find_zero:\n");

  img = malloc (54000);
  if (img == 0)
    {
      DBG (1, "out of memory (need 54000)\n");
      return SANE_STATUS_NO_MEM;
    }

  CHK (csend (scan, CMD_0));
  CHK (get_pixels (scan, opc3, opb6, opd1, ope1, 54000, 1, img));

#ifdef DEBUG_CALIBRATION
  {
    int w = 300, h = 180;
    FILE *f2 = fopen ("find_zero.pgm", "wb");
    fprintf (f2, "P5\n%d %d\n255\n", w, h);
    fwrite (img, 1, w * h, f2);
    fclose (f2);
  }
#endif

  s = locate_black_stripe (img, 300, 180);
  scan->scanner_yorg = scan->scanner_ypos + s + 64;
  scan->scanner_ypos += 180 + 3;
  scan->scanner_ypos &= ~3;

  free (img);
  return SANE_STATUS_GOOD;
}

#if 0
static SANE_Status
calib (UMAX_Handle * scan)
{
  unsigned char buf[65536];
  opc5[11] = 0x30;
  opd2[7]  = 0x30;
  CHK (get_pixels (scan, opc5, opb8, opd2, ope, 24, 0, buff));

  opc5[11] = 0x40;
  opd2[7]  = 0x40;
  CHK (get_pixels (scan, opc5, opb8, opd2, ope, 24, 0, buff));

  opd2[6] = 8;
  opd2[7] = 0x30;
  CHK (get_pixels (scan, opc6, opb9, opd2, ope, 0x200, 1, buff));

  opc7[10] = 0xec;
  opd2[6]  = 0xc;
  opd2[7]  = 0x40;
  CHK (get_pixels (scan, opc7, opb10, opd2, ope, 5300, 1, buff));

  opc7[10] = 0xed;
  opd2[6]  = 0xd;
  CHK (get_pixels (scan, opc7, opb10, opd2, ope, 5300, 0, buff));
  return SANE_STATUS_GOOD;
}
#endif

/*
Format of caldata:

   5100 bytes of CCD calibration values
   5100 bytes of CCD calibration values
   5100 bytes of CCD calibration values
   256 bytes of gamma data for blue
   256 bytes of gamma data for green
   256 bytes of gamma data for red
   2 bytes of extra information

*/
static SANE_Status
get_caldata (UMAX_Handle * scan, int color)
{
  unsigned char opc9[16] = {
    XXXX, 0x00, 0x00, 0x70, 0x00, 0x00, 0x60, 0x00, 0x17, 0x05,
    0xec, 0x4e, 0x0c, XXXX, 0xac
  };
  unsigned char opb11[35] = {
    0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x04,
    0x40, 0x01, 0x00, 0x00, 0x04, 0x00, 0x6e, 0xad, 0xa0, 0x49,
    0x06, 0x00, 0x00, XXXX, XXXX, 0xa0, 0x00, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, 0x93, 0x1b
  };
  unsigned char opd4[8] = {
    0x06, 0xf4, 0xff, 0x81, 0x3d, 0x00, XXXX, XXXX
  };
  SANE_Status res;

  unsigned char *p;
  int h  = 66;
  int w  = color ? 3 * 5100 : 5100;
  int x0 = color ? 0 : 5100;
  int l  = w * h;
  int i, x, y;

  PAD_ARRAY (scan->caldata, 16070);

  DBG (9, "get_caldata: color = %d\n", color);

  p = malloc (l);
  if (p == 0)
    {
      DBG (1, "out of memory (need %d)\n", l);
      return SANE_STATUS_NO_MEM;
    }
  memset (scan->caldata, 0, 3 * 5100);

  CHK (csend (scan, CMD_0));
  opc9[0] = h + 4;
  if (color)
    {
      opc9[13]  = 0x03;
      opb11[23] = 0xc4;
      opb11[24] = 0x5c;
      opd4[6]   = 0x08;
      opd4[7]   = 0x00;
    }
  else
    {
      opc9[13]  = 0xc3;
      opb11[23] = 0xec;
      opb11[24] = 0x54;
      opd4[6]   = 0x0c;
      opd4[7]   = 0x40;
    }

  /* Do a test scan of the calibration strip (which is located
   * under the scanner's lid */

  CHK (get_pixels (scan, opc9, opb11, opd4, ope, l, 0, p));

#ifdef DEBUG_CALIBRATION
  {
    FILE *f2 = fopen ("calibration.pgm", "wb");
    fprintf (f2, "P5\n%d %d\n255\n", w, h);
    fwrite (p, 1, w * h, f2);
    fclose (f2);
  }
#endif

  scan->scanner_ypos += (h + 4) * 2 + 3;
  scan->scanner_ypos &= ~3;

  /* The following loop computes the gain for each of the CCD pixel
   * elements.
   */
  for (x = 0; x < w; ++x)
    {
      int t = 0, gn;
      double av, gain;

      for (y = 0; y < h; ++y)
        t += p[x + y * w];
      av = (double) t / h;
      gain = 250 / av;
      gn = (int) ((gain - 0.984) * 102.547 + 0.5);
      if (gn < 0)
        gn = 0;
      else if (gn > 255)
        gn = 255;
      scan->caldata[x + x0] = gn;
    }

  /* Gamma table for blue */

  for (i = 0; i < 256; ++i)
    scan->caldata[i + 3 * 5100 + 0] = i;

  /* Gamma table for green */

  for (i = 0; i < 256; ++i)
    scan->caldata[i + 3 * 5100 + 256] = i;

  /* Gamma table for red */

  for (i = 0; i < 256; ++i)
    scan->caldata[i + 3 * 5100 + 512] = i;

  free (p);

  CHK_ARRAY (scan->caldata, 16070);
  return SANE_STATUS_GOOD;
}

static SANE_Status
send_scan_parameters (UMAX_Handle * scan)
{
  SANE_Status res;
  UMAX_Status_Byte s;

  /* Appears to correspond to opscan in umax_pp_low.c */
  unsigned char opbgo[35] = {
    0x00, 0x00, 0xb0, 0x4f, 0xd8, 0xe7, 0xfa, 0x10, 0xef, 0xc4,
    0x3c, 0x71, 0x0f, 0x00, 0x04, 0x00, 0x6e, XXXX, XXXX, XXXX,
    0xc4, 0x7e, 0x00, XXXX, XXXX, 0xa0, 0x0a, 0x8b, 0x49, 0x4a,
    0xd0, 0x68, 0xdf, XXXX, 0x1a
  };

  /* Appears to correspond to opsc53 in umax_pp_low.c */
  unsigned char opcgo[16] = {
    XXXX, XXXX, XXXX, XXXX, 0xec, 0x03, XXXX, XXXX, XXXX, XXXX,
    0xec, 0x4e, XXXX, XXXX, XXXX
  };

  /* Appears to correspond to opsc04 in umax_pp_low.c */
  unsigned char opdgo[8] = {
    0x06, 0xf4, 0xff, 0x81, 0x3d, 0x00, XXXX, XXXX
  };

  unsigned char subsamp[9] = {
    0xff, 0xff, 0xaa, 0xaa, 0x88, 0x88, 0x88, 0x88, 0x80
  };

  const int xend =
    scan->xskip + scan->w * scan->xsamp + (scan->xsamp + 1) / 2;
  const int ytot = scan->hexp * scan->ysamp + 12;

  opbgo[17] = scan->xskip;
  opbgo[18] = ((scan->xskip >> 8) & 0xf) + (xend << 4);
  opbgo[19] = xend >> 4;
  opbgo[33] = 0x33 + ((xend & 0x1000) >> 5) + ((scan->xskip & 0x1000) >> 6);

  /* bytes per line */

  opbgo[23] = scan->color ? 0xc6 : 0x77;
  opbgo[24] = scan->color ? 0x5b : 0x4a;

  /* Scan height */

  opcgo[0] = ytot;
  opcgo[1] = ((ytot >> 8) & 0x3f) + (scan->yskip << 6);
  opcgo[2] = scan->yskip >> 2;
  opcgo[3] = 0x50 + ((scan->yskip >> 10) & 0xf);

  /* This is what used to be here:

     opcgo[6] = bh == h? 0: 0x60;       // a guess

     I replaced it with what umax_pp_low.c uses, since it
     made more sense
   */
  opcgo[6]  = (scan->ydpi <= 300) ? 0x00 : 0x60;
  opcgo[8]  = (scan->ydpi <= 300) ? 0x17 : 0x2F;
  opcgo[9]  = (scan->ydpi >= 300) ? 0x05 : 0x07;
  opcgo[14] = (scan->ydpi == 600) ? 0xa4 : 0xac;

  opcgo[7]  = scan->color ? 0x2F : 0x40;
  opcgo[12] = scan->color ? 0x10 : 0x0C;
  opcgo[13] = scan->color ? 0x04 : 0xc3;

  opdgo[6]  = scan->color ? 0x88 : 0x8c;
  opdgo[7]  = scan->color ? 0x00 : 0x40;

  DBG (3, "send_scan_parameters: xskip = %d, yskip = %d\n", scan->xskip,
       scan->yskip);

  CHK (csend (scan, CMD_0));
  CHK (csend (scan, CMD_0));
  CHK (cwritev (scan, CMD_2, 16, opcgo, &s));
  CHK (cwritev (scan, CMD_8, 35, opbgo, &s));
  CHK (cwritev (scan, CMD_1, 8, opdgo, &s));
  CHK (cread (scan, CMD_2, 0, NULL, &s));
  DBG (4, "send_scan_parameters: checkpoint 1: s = %d\n", s);

  /* Loads the new calibration data (that was computed by get_caldata) into the
     scanner */

  scan->caldata[16068] = subsamp[scan->xsamp];
  scan->caldata[16069] = subsamp[scan->ysamp];
  CHK (cwrite (scan, CMD_4, 16070, scan->caldata, &s));

  CHK (csend (scan, CMD_40));
  CHK (cread (scan, CMD_2, 0, NULL, &s));

  DBG (4, "send_scan_parameters: checkpoint 2: s = %d\n", s);

  return SANE_STATUS_GOOD;
}

static SANE_Status
read_raw_data (UMAX_Handle * scan, unsigned char *data, int len)
{
  SANE_Status res;
  UMAX_Status_Byte s;

  CHK (cread (scan, CMD_2, 0, NULL, &s));
  CHK (cread (scan, CMD_4, len, data, &s));

  return SANE_STATUS_GOOD;
}

static SANE_Status
read_raw_strip_color (UMAX_Handle * scan)
{
  /**
     yres = 75   => ydpi = 150 => ysamp = 2 => yoff_scale = 8
     yres = 150  => ydpi = 150 => ysamp = 1 => yoff_scale = 4
     yres = 300  => ydpi = 300 => ysamp = 1 => yoff_scale = 2
     yres = 600  => ydpi = 600 => ysamp = 1 => yoff_scale = 1
  */

  const int yoff_scale = 600 * scan->ysamp / scan->ydpi;
  const int linelen = 3 * scan->w;

  /*
     yoff_scale = 8  =>  roff = 5  * w,  goff = 1  * w,  boff = 0 * w, hextra = 1
     yoff_scale = 4  =>  roff = 8  * w,  goff = 4  * w,  boff = 0 * w, hextra = 2
     yoff_scale = 2  =>  roff = 14 * w,  goff = 7  * w,  boff = 0 * w, hextra = 4
     yoff_scale = 1  =>  roff = 26 * w,  goff = 13 * w,  boff = 0 * w, hextra = 8
   */

  const int hextra = 8 / yoff_scale;

  SANE_Status res;
  int lines_to_read = scan->hexp;

  DBG (9, "read_raw_strip_color: hexp = %d, bh = %d\n", scan->hexp, scan->bh);

  if (scan->maxh == -1)
    {
      DBG (10, "read_raw_strip_color: filling buffer for the first time\n");
      if (lines_to_read > scan->bh)
        lines_to_read = scan->bh;

      CHK (read_raw_data (scan, scan->p, lines_to_read * linelen));
      scan->maxh = lines_to_read - hextra;
    }
  else
    {
      DBG (10, "read_raw_strip_color: reading new rows into buffer\n");
      memmove (scan->p, scan->p + (scan->bh - hextra) * linelen,
               hextra * linelen);

      if (lines_to_read > (scan->bh - hextra))
        lines_to_read = scan->bh - hextra;

      CHK (read_raw_data
           (scan, scan->p + hextra * linelen, lines_to_read * linelen));
      scan->maxh = lines_to_read;
    }

  scan->hexp -= lines_to_read;
  scan->x = 0;
  scan->y = 0;

  return SANE_STATUS_GOOD;
}

static SANE_Status
read_raw_strip_gray (UMAX_Handle * scan)
{
  const int linelen = scan->w;

  SANE_Status res;

  int lines_to_read = scan->bh;

  DBG (9, "read_raw_strip_gray: hexp = %d\n", scan->hexp);

  if (lines_to_read > scan->hexp)
    lines_to_read = scan->hexp;
  scan->hexp -= lines_to_read;

  CHK (read_raw_data (scan, scan->p, lines_to_read * linelen));

  scan->maxh = lines_to_read;
  scan->x = 0;
  scan->y = 0;

  return SANE_STATUS_GOOD;
}

static SANE_Status
read_raw_strip (UMAX_Handle * scan)
{
  if (scan->color)
      return read_raw_strip_color (scan);
  else
      return read_raw_strip_gray (scan);
}

static SANE_Status
UMAX_set_scan_parameters (UMAX_Handle * scan,
                          const int color,
                          const int xo,
                          const int yo,
                          const int w,
                          const int h, const int xres, const int yres)
{

  /* Validate the input parameters */

  int left = xo;
  int top = yo;
  int right = xo + w * 600 / xres;
  int bottom = yo + h * 600 / yres;

  DBG (2, "UMAX_set_scan_parameters:\n");
  DBG (2, "color = %d             \n", color);
  DBG (2, "xo    = %d, yo     = %d\n", xo, yo);
  DBG (2, "w     = %d, h      = %d\n", w, h);
  DBG (2, "xres  = %d, yres   = %d\n", xres, yres);
  DBG (2, "left  = %d, top    = %d\n", left, top);
  DBG (2, "right = %d, bottom = %d\n", right, bottom);

  if ((left < 0) || (right > UMAX_MAX_WIDTH))
    return SANE_STATUS_INVAL;

  if ((top < 0) || (bottom > UMAX_MAX_HEIGHT))
    return SANE_STATUS_INVAL;

  if (((right - left) < 10) || ((bottom - top) < 10))
    return SANE_STATUS_INVAL;

  if ((xres != 75) && (xres != 150) && (xres != 300) && (xres != 600))
    return SANE_STATUS_INVAL;

  if ((yres != 75) && (yres != 150) && (yres != 300) && (yres != 600))
    return SANE_STATUS_INVAL;

  /* If we get this far, begin initializing the data
     structure
   */

  scan->color = color;
  scan->w = w;
  scan->h = h;
  scan->xo = xo;
  scan->yo = yo;

  /*
     The scanner has a fixed X resolution of 600 dpi, but
     supports three choices for the Y resolution. We must
     choose an appropriate physical resolution and the
     corresponding sampling value.

     It is not clear to me why the choice depends on
     whether we are scanning in color or not, but the
     original code did this and I didn't want to mess
     with it.

     Physical X resolution choice:
     xres = 75   =>  xdpi = 600  (xsamp = 8)
     xres = 150  =>  xdpi = 600  (xsamp = 4)
     xres = 300  =>  xdpi = 600  (xsamp = 2)
     xres = 600  =>  xdpi = 600  (xsamp = 1)

     Physical Y resolution choice (if color):
     yres = 75    =>  ydpi = 150  (ysamp = 2)
     yres = 150   =>  ydpi = 150  (ysamp = 1)
     yres = 300   =>  ydpi = 300  (ysamp = 1)
     yres = 600   =>  ydpi = 600  (ysamp = 1)

     Physical Y resolution choice (if not color):
     yres = 75    =>  ydpi = 300  (ysamp = 4)
     yres = 150   =>  ydpi = 300  (ysamp = 2)
     yres = 300   =>  ydpi = 300  (ysamp = 1)
     yres = 600   =>  ydpi = 600  (ysamp = 1)
   */

  scan->xdpi = 600;
  if (yres <= 150 && color)
    scan->ydpi = 150;
  else if (yres > 300)
    scan->ydpi = 600;
  else
    scan->ydpi = 300;

  scan->xsamp = scan->xdpi / xres;
  scan->ysamp = scan->ydpi / yres;

  return SANE_STATUS_GOOD;
}

static SANE_Status
UMAX_start_scan (UMAX_Handle * scan)
{
  SANE_Status res;
  int linelen;
  int yd;

  DBG (3, "UMAX_start_scan called\n");

  if (scan->color)
    {
      const int yoff_scale = 600 * scan->ysamp / scan->ydpi;
      const int hextra = 8 / yoff_scale;

      linelen = 3 * scan->w;
      scan->hexp = scan->h + hextra;
    }
  else
    {
      linelen = scan->w;
      scan->hexp = scan->h;
    }

  scan->bh = BUFFER_SIZE / linelen;

  scan->p = malloc (scan->bh * linelen);
  if (scan->p == 0)
    return SANE_STATUS_NO_MEM;

  DBG (4, "UMAX_start_scan: bh = %d, linelen = %d\n", scan->bh, linelen);

  scan->maxh = -1;
  scan->done = 0;

  /* Initialize the scanner and position the scan head */

  CHK (umaxinit (scan));

  /* This scans in the black and white calibration strip that
   * is located under the scanner's lid. The scan of that strip
   * is used to pick correct values for the CCD calibration
   * values
   */

  scan->scanner_ypos = 0;
  CHK (move (scan, 196, UMAX_NOT_FINE));
  CHK (find_zero (scan));
  CHK (move (scan, scan->scanner_yorg - 232 - scan->scanner_ypos, UMAX_FINE));
  CHK (get_caldata (scan, scan->color));

  /* This moves the head back to the starting position */

  yd = scan->scanner_yorg + scan->yo - scan->scanner_ypos;
  if (yd < 0)
    CHK (move (scan, yd, UMAX_FINE));
  if (yd > 300)
    CHK (move (scan, (yd - 20) / 2, UMAX_NOT_FINE));
  yd = scan->scanner_yorg + scan->yo - scan->scanner_ypos;

  scan->yskip = yd / (600 / scan->ydpi);
  scan->xskip = scan->xo / (600 / scan->xdpi);

  /* Read in the first chunk of raw data */

  CHK (send_scan_parameters (scan));
  CHK (read_raw_strip (scan));

  DBG (4, "UMAX_start_scan successful\n");

  return SANE_STATUS_GOOD;
}


static SANE_Status
UMAX_set_lamp_state (UMAX_Handle * scan, UMAX_Lamp_State state)
{
  SANE_Status res;

  DBG (3, "UMAX_set_lamp_state: state = %d\n", (int) state);

  CHK (csend (scan, CMD_0));
  CHK (cwritev_opc1_lamp_ctrl (scan, state));
  return SANE_STATUS_GOOD;
}

static SANE_Status
UMAX_park_head (UMAX_Handle * scan)
{
  SANE_Status res;
  UMAX_Status_Byte s;
  int i;

  DBG (3, "UMAX_park_head called\n");

  CHK (csend (scan, CMD_0));
  /* WARNING: Must call cwritev_opc1_lamp_ctrl before cwritev_opb3_restore,
   * otherwise the head moves the wrong way and makes ugly grinding noises. */
  CHK (cwritev_opc1_lamp_ctrl (scan, UMAX_LAMP_ON));
  CHK (cwritev_opb3_restore (scan));

  for (i = 0; i < 60; ++i)
    {
      CHK (cread (scan, CMD_2, 0, NULL, &s));
      DBG (4, "UMAX_park_head: s = %#x\n", s);
      if ((s & 0x40) != 0)
        break;
      DBG (4, "UMAX_park_head: sleeping\n");
      usleep (500000);
    }

  scan->scanner_ypos = 0;

  return SANE_STATUS_GOOD;
}

static SANE_Status
UMAX_finish_scan (UMAX_Handle * scan)
{
  DBG (3, "UMAX_finish_scan:\n");

  if (scan->p)
    free (scan->p);
  scan->p = NULL;
  return SANE_STATUS_GOOD;
}

static SANE_Status
UMAX_get_rgb (UMAX_Handle * scan, unsigned char *rgb)
{

  if (scan->color)
    {
      const int linelen = 3 * scan->w;
      const int yoff_scale = 600 * scan->ysamp / scan->ydpi;
      const int roff = (8 / yoff_scale * 3 + 2) * scan->w;
      const int goff = (4 / yoff_scale * 3 + 1) * scan->w;
      const int boff = 0;

      unsigned char *base = scan->p + (scan->y * linelen) + scan->x;

#ifdef DEBUG_BOUNDS
      if ((base + roff) >= (scan->p + (scan->bh * linelen)))
        DBG (1, "UMAX_get_rgb: Overrun in get_rgb (roff)!\n");

      if ((base + goff) >= (scan->p + (scan->bh * linelen)))
        DBG (1, "UMAX_get_rgb: Overrun in get_rgb (goff)!\n");

      if ((base + boff) >= (scan->p + (scan->bh * linelen)))
        DBG (1, "UMAX_get_rgb: Overrun in get_rgb (boff)!\n");
#endif

      rgb[0] = base[roff];
      rgb[1] = base[goff];
      rgb[2] = base[boff];
    }
  else
    {
      const int linelen = scan->w;
      unsigned char *base = scan->p + (scan->y * linelen) + scan->x;

#ifdef DEBUG_BOUNDS
      if (base >= (scan->p + (scan->bh * linelen)))
        DBG (1, "UMAX_get_rgb: Overrun in get_rgb (gray)!\n");
#endif

      rgb[0] = base[0];
      rgb[1] = base[0];
      rgb[2] = base[0];
    }

  if (!(((scan->x + 1) == scan->w) && ((scan->y + 1) == scan->maxh)))
    {
      ++scan->x;
      if (scan->x == scan->w)
        {
          ++scan->y;
          scan->x = 0;
        }
      return SANE_STATUS_GOOD;
    }

  if (scan->hexp <= 0)
    {
      DBG (4, "UMAX_get_rgb: setting done flag\n");
      scan->done = 1;
      return SANE_STATUS_GOOD;
    }

  return read_raw_strip (scan);
}

static SANE_Status
UMAX_close_device (UMAX_Handle * scan)
{
  DBG (3, "UMAX_close_device:\n");
  sanei_usb_close (scan->fd);
  return SANE_STATUS_GOOD;
}

static SANE_Status
UMAX_open_device (UMAX_Handle * scan, const char *dev)
{
  SANE_Word vendor;
  SANE_Word product;
  SANE_Status res;

  DBG (3, "UMAX_open_device: `%s'\n", dev);

  res = sanei_usb_open (dev, &scan->fd);
  if (res != SANE_STATUS_GOOD)
    {
      DBG (1, "UMAX_open_device: couldn't open device `%s': %s\n", dev,
           sane_strstatus (res));
      return res;
    }

#ifndef NO_AUTODETECT
  /* We have opened the device. Check that it is a USB scanner. */
  if (sanei_usb_get_vendor_product (scan->fd, &vendor, &product) !=
      SANE_STATUS_GOOD)
    {
      DBG (1, "UMAX_open_device: sanei_usb_get_vendor_product failed\n");
      /* This is not a USB scanner, or SANE or the OS doesn't support it. */
      sanei_usb_close (scan->fd);
      scan->fd = -1;
      return SANE_STATUS_UNSUPPORTED;
    }

  /* Make sure we have a UMAX scanner */
  if (vendor != 0x1606)
    {
      DBG (1, "UMAX_open_device: incorrect vendor\n");
      sanei_usb_close (scan->fd);
      scan->fd = -1;
      return SANE_STATUS_UNSUPPORTED;
    }

  /* Now check whether it is a scanner we know about */
  switch (product)
    {
    case ASTRA_2000U:
      /* The UMAX Astra 2000U is only partially supported by
         this driver. Expect severe color problems! :)
       */
      DBG (1,
           "UMAX_open_device: Scanner is a 2000U. Expect color problems :)\n");
      scan->model = ASTRA_2000U;
      break;
     case ASTRA_2100U:
      /* The UMAX Astra 2100U is only partially supported by
         this driver. Expect severe color problems! :) [???]
       */
      DBG (1,
           "UMAX_open_device: Scanner is a 2100U. Expect color problems :)\n");
      scan->model = ASTRA_2100U;
      break;
    case ASTRA_1220U:
      scan->model = ASTRA_1220U;
      break;
    default:
      DBG (1, "UMAX_open_device: unknown product number\n");
      sanei_usb_close (scan->fd);
      scan->fd = -1;
      return SANE_STATUS_UNSUPPORTED;
    }
#endif

  res = csend (scan, CMD_0);
  if (res != SANE_STATUS_GOOD)
    UMAX_close_device (scan);
  CHK (res);

  res = xxxops (scan);
  if (res != SANE_STATUS_GOOD)
    UMAX_close_device (scan);
  CHK (res);

  return SANE_STATUS_GOOD;
}

static const char *
UMAX_get_device_name (UMAX_Handle * scan)
{
  switch (scan->model)
    {
    case ASTRA_1220U:
      return "Astra 1220U";
    case ASTRA_2000U:
      return "Astra 2000U";
    case ASTRA_2100U:
      return "Astra 2100U";
    }
  return "Unknown";
}
