/* sane - Scanner Access Now Easy.

   Copyright (C) 2003 Oliver Rauch
   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
   Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de>
   Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
   Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
   Copyright (C) 2007 Luke <iceyfor@gmail.com>
   Copyright (C) 2011 Alexey Osipov <simba@lerlan.ru> for HP2400 description
                      and tuning

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

#undef BACKEND_NAME
#define BACKEND_NAME genesys_gl646

#include "genesys_gl646.h"

/**
 * returns the value hold by a 3 word register
 * @param regs register set from which reading the value
 * @param regnum number of the register to read
 * @return 24 bit value of the register
 */
static uint32_t
gl646_get_triple_reg (Genesys_Register_Set * regs, int regnum)
{
  Genesys_Register_Set *r = NULL;
  uint32_t ret = 0;

  r = sanei_genesys_get_address (regs, regnum);
  ret = r->value;
  r = sanei_genesys_get_address (regs, regnum + 1);
  ret = (ret << 8) + r->value;
  r = sanei_genesys_get_address (regs, regnum + 2);
  ret = (ret << 8) + r->value;

  return ret;
}

/**
 * returns the value hold by a 2 word register
 * @param regs register set from which reading the value
 * @param regnum number of the register to read
 * @return 16 bit value of the register
 */
static uint32_t
gl646_get_double_reg (Genesys_Register_Set * regs, int regnum)
{
  Genesys_Register_Set *r = NULL;
  uint32_t ret = 0;

  r = sanei_genesys_get_address (regs, regnum);
  ret = r->value;
  r = sanei_genesys_get_address (regs, regnum + 1);
  ret = (ret << 8) + r->value;

  return ret;
}

/* Write to many registers */
static SANE_Status
gl646_bulk_write_register (Genesys_Device * dev,
			   Genesys_Register_Set * reg, size_t elems)
{
  SANE_Status status;
  uint8_t outdata[8];
  uint8_t buffer[GENESYS_MAX_REGS * 2];
  size_t size;
  unsigned int i;

  /* handle differently sized register sets, reg[0x00] may be the last one */
  i = 0;
  while ((i < elems) && (reg[i].address != 0))
    i++;
  elems = i;
  size = i * 2;

  DBG (DBG_io, "gl646_bulk_write_register (elems= %lu, size = %lu)\n",
       (u_long) elems, (u_long) size);


  outdata[0] = BULK_OUT;
  outdata[1] = BULK_REGISTER;
  outdata[2] = 0x00;
  outdata[3] = 0x00;
  outdata[4] = (size & 0xff);
  outdata[5] = ((size >> 8) & 0xff);
  outdata[6] = ((size >> 16) & 0xff);
  outdata[7] = ((size >> 24) & 0xff);

  status =
    sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_BUFFER,
			   VALUE_BUFFER, INDEX, sizeof (outdata), outdata);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_bulk_write_register: failed while writing command: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* copy registers and values in data buffer */
  for (i = 0; i < size; i += 2)
    {
      buffer[i] = reg[i / 2].address;
      buffer[i + 1] = reg[i / 2].value;
    }

  status = sanei_usb_write_bulk (dev->dn, buffer, &size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_bulk_write_register: failed while writing bulk data: %s\n",
	   sane_strstatus (status));
      return status;
    }

  if (DBG_LEVEL >= DBG_io2)
    {
      for (i = 0; i < size; i += 2)
	{
	  DBG (DBG_io2, "reg[0x%02x] = 0x%02x\n", buffer[i], buffer[i + 1]);
	}
      /* when full size, decode register content */
      if (elems > 60)
	{
	  DBG (DBG_io2, "DPISET   =%d\n",
	       gl646_get_double_reg (reg, REG_DPISET));
	  DBG (DBG_io2, "DUMMY    =%d\n",
	       sanei_genesys_get_address (reg, REG_DUMMY)->value);
	  DBG (DBG_io2, "STRPIXEL =%d\n",
	       gl646_get_double_reg (reg, REG_STRPIXEL));
	  DBG (DBG_io2, "ENDPIXEL =%d\n",
	       gl646_get_double_reg (reg, REG_ENDPIXEL));
	  DBG (DBG_io2, "LINCNT   =%d\n",
	       gl646_get_triple_reg (reg, REG_LINCNT));
	  DBG (DBG_io2, "MAXWD    =%d\n",
	       gl646_get_triple_reg (reg, REG_MAXWD));
	  DBG (DBG_io2, "LPERIOD  =%d\n",
	       gl646_get_double_reg (reg, REG_LPERIOD));
	  DBG (DBG_io2, "FEEDL    =%d\n",
	       gl646_get_triple_reg (reg, REG_FEEDL));
	}
    }

  DBG (DBG_io, "gl646_bulk_write_register: wrote %lu bytes, %lu registers\n",
       (u_long) size, (u_long) elems);
  return status;
}

/* Write bulk data (e.g. shading, gamma) */
static SANE_Status
gl646_bulk_write_data (Genesys_Device * dev, uint8_t addr,
		       uint8_t * data, size_t len)
{
  SANE_Status status;
  size_t size;
  uint8_t outdata[8];

  DBG (DBG_io, "gl646_bulk_write_data writing %lu bytes\n", (u_long) len);

  status =
    sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_REGISTER,
			   VALUE_SET_REGISTER, INDEX, 1, &addr);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_bulk_write_data failed while setting register: %s\n",
	   sane_strstatus (status));
      return status;
    }

  while (len)
    {
      if (len > BULKOUT_MAXSIZE)
	size = BULKOUT_MAXSIZE;
      else
	size = len;

      outdata[0] = BULK_OUT;
      outdata[1] = BULK_RAM;
      outdata[2] = 0x00;
      outdata[3] = 0x00;
      outdata[4] = (size & 0xff);
      outdata[5] = ((size >> 8) & 0xff);
      outdata[6] = ((size >> 16) & 0xff);
      outdata[7] = ((size >> 24) & 0xff);

      status =
	sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_BUFFER,
			       VALUE_BUFFER, INDEX, sizeof (outdata),
			       outdata);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_bulk_write_data failed while writing command: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      status = sanei_usb_write_bulk (dev->dn, data, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_bulk_write_data failed while writing bulk data: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      DBG (DBG_io2,
	   "gl646_bulk_write_data wrote %lu bytes, %lu remaining\n",
	   (u_long) size, (u_long) (len - size));

      len -= size;
      data += size;
    }

  DBG (DBG_io, "gl646_bulk_write_data: end\n");

  return status;
}

/**
 * reads value from gpio endpoint
 */
static SANE_Status
gl646_gpio_read (SANE_Int dn, uint8_t * value)
{
  return sanei_usb_control_msg (dn, REQUEST_TYPE_IN,
				REQUEST_REGISTER, GPIO_READ, INDEX, 1, value);
}

/**
 * writes the given value to gpio endpoint
 */
static SANE_Status
gl646_gpio_write (SANE_Int dn, uint8_t value)
{
  DBG (DBG_proc, "gl646_gpio_write(0x%02x)\n", value);
  return sanei_usb_control_msg (dn, REQUEST_TYPE_OUT,
				REQUEST_REGISTER, GPIO_WRITE,
				INDEX, 1, &value);
}

/**
 * writes the given value to gpio output enable endpoint
 */
static SANE_Status
gl646_gpio_output_enable (SANE_Int dn, uint8_t value)
{
  DBG (DBG_proc, "gl646_gpio_output_enable(0x%02x)\n", value);
  return sanei_usb_control_msg (dn, REQUEST_TYPE_OUT,
				REQUEST_REGISTER, GPIO_OUTPUT_ENABLE,
				INDEX, 1, &value);
}

/* Read bulk data (e.g. scanned data) */
static SANE_Status
gl646_bulk_read_data (Genesys_Device * dev, uint8_t addr,
		      uint8_t * data, size_t len)
{
  SANE_Status status;
  size_t size;
  uint8_t outdata[8];

  DBG (DBG_io, "gl646_bulk_read_data: requesting %lu bytes\n", (u_long) len);

  /* write requested size */
  status =
    sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_REGISTER,
			   VALUE_SET_REGISTER, INDEX, 1, &addr);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_bulk_read_data failed while setting register: %s\n",
	   sane_strstatus (status));
      return status;
    }

  outdata[0] = BULK_IN;
  outdata[1] = BULK_RAM;
  outdata[2] = 0x00;
  outdata[3] = 0x00;
  outdata[4] = (len & 0xff);
  outdata[5] = ((len >> 8) & 0xff);
  outdata[6] = ((len >> 16) & 0xff);
  outdata[7] = ((len >> 24) & 0xff);

  status =
    sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_BUFFER,
			   VALUE_BUFFER, INDEX, sizeof (outdata), outdata);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_bulk_read_data failed while writing command: %s\n",
	   sane_strstatus (status));
      return status;
    }

  while (len)
    {
      if (len > GL646_BULKIN_MAXSIZE)
	size = GL646_BULKIN_MAXSIZE;
      else
	size = len;

      DBG (DBG_io2,
	   "gl646_bulk_read_data: trying to read %lu bytes of data\n",
	   (u_long) size);
      status = sanei_usb_read_bulk (dev->dn, data, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_bulk_read_data failed while reading bulk data: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      DBG (DBG_io2,
	   "gl646_bulk_read_data read %lu bytes, %lu remaining\n",
	   (u_long) size, (u_long) (len - size));

      len -= size;
      data += size;
    }

  if (dev->model->is_sheetfed == SANE_TRUE)
    {
      gl646_detect_document_end (dev);
    }

  DBG (DBG_io, "gl646_bulk_read_data: end\n");

  return status;
}

#if 0
static SANE_Status
read_triple_reg (Genesys_Device * dev, int index, unsigned int *words)
{
  SANE_Status status;
  uint8_t value;

  DBG (DBG_proc, "read_triple_reg\n");

  RIE (sanei_genesys_read_register (dev, index + 2, &value));
  *words = value;
  RIE (sanei_genesys_read_register (dev, index + 1, &value));
  *words += (value * 256);
  RIE (sanei_genesys_read_register (dev, index, &value));
  if (dev->model->asic_type == GENESYS_GL646)
    *words += ((value & 0x03) * 256 * 256);
  else
    *words += ((value & 0x0f) * 256 * 256);

  DBG (DBG_proc, "read_triple_reg: value=%d\n", *words);
  return status;
}
#endif


static SANE_Bool
gl646_get_fast_feed_bit (Genesys_Register_Set * regs)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, 0x02);
  if (r && (r->value & REG02_FASTFED))
    return SANE_TRUE;
  return SANE_FALSE;
}

static SANE_Bool
gl646_get_filter_bit (Genesys_Register_Set * regs)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, 0x04);
  if (r && (r->value & REG04_FILTER))
    return SANE_TRUE;
  return SANE_FALSE;
}

static SANE_Bool
gl646_get_lineart_bit (Genesys_Register_Set * regs)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, 0x04);
  if (r && (r->value & REG04_LINEART))
    return SANE_TRUE;
  return SANE_FALSE;
}

static SANE_Bool
gl646_get_bitset_bit (Genesys_Register_Set * regs)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, 0x04);
  if (r && (r->value & REG04_BITSET))
    return SANE_TRUE;
  return SANE_FALSE;
}

static SANE_Bool
gl646_get_gain4_bit (Genesys_Register_Set * regs)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, 0x06);
  if (r && (r->value & REG06_GAIN4))
    return SANE_TRUE;
  return SANE_FALSE;
}

static SANE_Bool
gl646_test_buffer_empty_bit (SANE_Byte val)
{
  if (val & REG41_BUFEMPTY)
    return SANE_TRUE;
  return SANE_FALSE;
}

static SANE_Bool
gl646_test_motor_flag_bit (SANE_Byte val)
{
  if (val & REG41_MOTMFLG)
    return SANE_TRUE;
  return SANE_FALSE;
}

static void
gl646_set_triple_reg (Genesys_Register_Set * regs, int regnum, uint32_t value)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, regnum);
  r->value = LOBYTE (HIWORD (value));
  r = sanei_genesys_get_address (regs, regnum + 1);
  r->value = HIBYTE (LOWORD (value));
  r = sanei_genesys_get_address (regs, regnum + 2);
  r->value = LOBYTE (LOWORD (value));
}

static void
gl646_set_double_reg (Genesys_Register_Set * regs, int regnum, uint16_t value)
{
  Genesys_Register_Set *r = NULL;

  r = sanei_genesys_get_address (regs, regnum);
  r->value = HIBYTE (LOWORD (value));
  r = sanei_genesys_get_address (regs, regnum + 1);
  r->value = LOBYTE (LOWORD (value));
}

/**
 * decodes and prints content of status (0x41) register
 * @param val value read from reg41
 */
static void
print_status (uint8_t val)
{
  char msg[80];

  sprintf (msg, "%s%s%s%s%s%s%s%s",
	   val & REG41_PWRBIT ? "PWRBIT " : "",
	   val & REG41_BUFEMPTY ? "BUFEMPTY " : "",
	   val & REG41_FEEDFSH ? "FEEDFSH " : "",
	   val & REG41_SCANFSH ? "SCANFSH " : "",
	   val & REG41_HOMESNR ? "HOMESNR " : "",
	   val & REG41_LAMPSTS ? "LAMPSTS " : "",
	   val & REG41_FEBUSY ? "FEBUSY " : "",
	   val & REG41_MOTMFLG ? "MOTMFLG" : "");
  DBG (DBG_info, "status=%s\n", msg);
}

/**
 * start scanner's motor
 * @param dev scanner's device
 */
static SANE_Status
gl646_start_motor (Genesys_Device * dev)
{
  return sanei_genesys_write_register (dev, 0x0f, 0x01);
}


/**
 * stop scanner's motor
 * @param dev scanner's device
 */
static SANE_Status
gl646_stop_motor (Genesys_Device * dev)
{
  return sanei_genesys_write_register (dev, 0x0f, 0x00);
}


/**
 * find the lowest resolution for the sensor in the given mode.
 * @param sensor id of the sensor
 * @param color true is color mode
 * @return the closest resolution for the sensor and mode
 */
static int
get_lowest_resolution (int sensor, SANE_Bool color)
{
  int i, nb;
  int dpi;

  i = 0;
  dpi = 9600;
  nb = sizeof (sensor_master) / sizeof (Sensor_Master);
  while (i < nb)
    {
      /* computes distance and keep mode if it is closer than previous */
      if (sensor == sensor_master[i].sensor
	  && sensor_master[i].color == color)
	{
	  if (sensor_master[i].dpi < dpi)
	    {
	      dpi = sensor_master[i].dpi;
	    }
	}
      i++;
    }
  DBG (DBG_info, "get_lowest_resolution: %d\n", dpi);
  return dpi;
}

/**
 * find the closest match in mode tables for the given resolution and scan mode.
 * @param sensor id of the sensor
 * @param required required resolution
 * @param color true is color mode
 * @return the closest resolution for the sensor and mode
 */
static int
get_closest_resolution (int sensor, int required, SANE_Bool color)
{
  int i, nb;
  int dist, dpi;

  i = 0;
  dpi = 0;
  dist = 9600;
  nb = sizeof (sensor_master) / sizeof (Sensor_Master);
  while (i < nb)
    {
      /* exit on perfect match */
      if (sensor == sensor_master[i].sensor
	  && sensor_master[i].dpi == required
	  && sensor_master[i].color == color)
	{
	  DBG (DBG_info, "get_closest_resolution: match found for %d\n",
	       required);
	  return required;
	}
      /* computes distance and keep mode if it is closer than previous */
      if (sensor == sensor_master[i].sensor
	  && sensor_master[i].color == color)
	{
	  if (abs (sensor_master[i].dpi - required) < dist)
	    {
	      dpi = sensor_master[i].dpi;
	      dist = abs (sensor_master[i].dpi - required);
	    }
	}
      i++;
    }
  DBG (DBG_info, "get_closest_resolution: closest match for %d is %d\n",
       required, dpi);
  return dpi;
}

/**
 * Computes if sensor will be set up for half ccd pixels for the given
 * scan mode.
 * @param sensor id of the sensor
 * @param required required resolution
 * @param color true is color mode
 * @return SANE_TRUE if half ccd is used
 */
static SANE_Bool
is_half_ccd (int sensor, int required, SANE_Bool color)
{
  int i, nb;

  i = 0;
  nb = sizeof (sensor_master) / sizeof (Sensor_Master);
  while (i < nb)
    {
      /* exit on perfect match */
      if (sensor == sensor_master[i].sensor
	  && sensor_master[i].dpi == required
	  && sensor_master[i].color == color)
	{
	  DBG (DBG_io, "is_half_ccd: match found for %d (half_ccd=%d)\n",
	       required, sensor_master[i].half_ccd);
	  return sensor_master[i].half_ccd;
	}
      i++;
    }
  DBG (DBG_info, "is_half_ccd: failed to find match for %d dpi\n", required);
  return SANE_FALSE;
}

/**
 * Returns the cksel values used by the required scan mode.
 * @param sensor id of the sensor
 * @param required required resolution
 * @param color true is color mode
 * @return cksel value for mode
 */
static int
get_cksel (int sensor, int required, SANE_Bool color)
{
  int i, nb;

  i = 0;
  nb = sizeof (sensor_master) / sizeof (Sensor_Master);
  while (i < nb)
    {
      /* exit on perfect match */
      if (sensor == sensor_master[i].sensor
	  && sensor_master[i].dpi == required
	  && sensor_master[i].color == color)
	{
	  DBG (DBG_io, "get_cksel: match found for %d (cksel=%d)\n",
	       required, sensor_master[i].cksel);
	  return sensor_master[i].cksel;
	}
      i++;
    }
  DBG (DBG_error, "get_cksel: failed to find match for %d dpi\n", required);
  /* fail safe fallback */
  return 1;
}

/**
 * Setup register and motor tables for a scan at the
 * given resolution and color mode. TODO try to not use any filed from
 * the device.
 * @param dev          pointer to a struct describing the device
 * @param regs         register set to fill
 * @param scan_settings scan's settings
 * @param slope_table1 first motor table to fill
 * @param slope_table2 second motor table to fill
 * @param resolution   dpi of the scan
 * @param move         distance to move (at scan's dpi) before scan
 * @param linecnt      number of lines to scan at scan's dpi
 * @param startx       start of scan area on CCD at CCD's optical resolution
 * @param endx         end of scan area on CCD at CCD's optical resolution
 * @param color        SANE_TRUE is color scan
 * @param depth        1, 8 or 16 bits data sample
 * @return SANE_STATUS_GOOD if registers could be set, SANE_STATUS_INVAL if
 * conditions can't be met.
 * @note No harcoded SENSOR or MOTOR 'names' should be present and
 * registers are set from settings tables and flags related
 * to the hardware capabilities.
 * */
static SANE_Status
gl646_setup_registers (Genesys_Device * dev,
		       Genesys_Register_Set * regs,
		       Genesys_Settings scan_settings,
		       uint16_t * slope_table1,
		       uint16_t * slope_table2,
		       SANE_Int resolution,
		       uint32_t move,
		       uint32_t linecnt,
		       uint16_t startx,
		       uint16_t endx, SANE_Bool color,
                       SANE_Int depth)
{
  SANE_Status status = SANE_STATUS_GOOD;
  int i, nb;
  Sensor_Master *sensor = NULL;
  Motor_Master *motor = NULL;
  Sensor_Settings *settings = NULL;
  Genesys_Register_Set *r;
  unsigned int used1, used2, vfinal;
  unsigned int bpp;   /**> bytes per pixel */
  uint32_t z1, z2;
  uint16_t ex, sx;
  int channels = 1, stagger, words_per_line, max_shift;
  size_t requested_buffer_size;
  size_t read_buffer_size;
  SANE_Bool half_ccd = SANE_FALSE;
  SANE_Int xresolution;
  int feedl;

  DBG (DBG_proc, "gl646_setup_registers: start\n");
  DBG (DBG_info, "gl646_setup_registers: startx=%d, endx=%d, linecnt=%d\n",
       startx, endx, linecnt);

  /* x resolution is capped by sensor's capability */
  if (resolution > dev->sensor.optical_res)
    {
      xresolution = dev->sensor.optical_res;
    }
  else
    {
      xresolution = resolution;
    }

  /* for the given resolution, search for master
   * sensor mode setting */
  i = 0;
  nb = sizeof (sensor_master) / sizeof (Sensor_Master);
  while (i < nb)
    {
      if (dev->model->ccd_type == sensor_master[i].sensor
	  && sensor_master[i].dpi == xresolution
	  && sensor_master[i].color == color)
	{
	  sensor = &sensor_master[i];
	}
      i++;
    }
  if (sensor == NULL)
    {
      DBG (DBG_error,
	   "gl646_setup_registers: unable to find settings for sensor %d at %d dpi color=%d\n",
	   dev->model->ccd_type, xresolution, color);
      return SANE_STATUS_INVAL;
    }

  /* for the given resolution, search for master
   * motor mode setting */
  i = 0;
  nb = sizeof (motor_master) / sizeof (Motor_Master);
  while (i < nb)
    {
      if (dev->model->motor_type == motor_master[i].motor
	  && motor_master[i].dpi == resolution
	  && motor_master[i].color == color)
	{
	  motor = &motor_master[i];
	}
      i++;
    }
  if (motor == NULL)
    {
      DBG (DBG_error,
	   "gl646_setup_registers: unable to find settings for motor %d at %d dpi, color=%d\n",
	   dev->model->motor_type, resolution, color);
      return SANE_STATUS_INVAL;
    }

  /* now we can search for the specific sensor settings */
  i = 0;
  nb = sizeof (sensor_settings) / sizeof (Sensor_Settings);
  while (i < nb)
    {
      if (sensor->sensor == sensor_settings[i].sensor
	  && sensor->cksel == sensor_settings[i].cksel)
	{
	  settings = &sensor_settings[i];
	}
      i++;
    }
  if (settings == NULL)
    {
      DBG (DBG_error,
	   "gl646_setup_registers: unable to find settings for sensor %d with '%d' ccd timing\n",
	   sensor->sensor, sensor->cksel);
      return SANE_STATUS_INVAL;
    }

  /* half_ccd if manual clock programming or dpi is half dpiset */
  half_ccd = sensor->half_ccd;

  /* now apply values from settings to registers */
  if (sensor->regs_0x10_0x15 != NULL)
    {
      for (i = 0; i < 6; i++)
	{
	  r = sanei_genesys_get_address (regs, 0x10 + i);
	  r->value = sensor->regs_0x10_0x15[i];
	}
    }
  else
    {
      for (i = 0; i < 6; i++)
	{
	  r = sanei_genesys_get_address (regs, 0x10 + i);
	  r->value = 0;
	}
    }

  for (i = 0; i < 4; i++)
    {
      r = sanei_genesys_get_address (regs, 0x08 + i);
      if (half_ccd == SANE_TRUE)
	r->value = settings->manual_0x08_0x0b[i];
      else
	r->value = settings->regs_0x08_0x0b[i];
    }

  for (i = 0; i < 8; i++)
    {
      r = sanei_genesys_get_address (regs, 0x16 + i);
      r->value = settings->regs_0x16_0x1d[i];
    }

  for (i = 0; i < 13; i++)
    {
      r = sanei_genesys_get_address (regs, 0x52 + i);
      r->value = settings->regs_0x52_0x5e[i];
    }
  if (half_ccd == SANE_TRUE)
    {
      for (i = 0; i < 7; i++)
	{
	  r = sanei_genesys_get_address (regs, 0x52 + i);
	  r->value = settings->manual_0x52_0x58[i];
	}
    }

  /* now generate slope tables : we are not using generate_slope_table3 yet */
  sanei_genesys_generate_slope_table (slope_table1, motor->steps1,
				      motor->steps1 + 1, motor->vend1,
				      motor->vstart1, motor->vend1,
				      motor->steps1, motor->g1, &used1,
				      &vfinal);
  sanei_genesys_generate_slope_table (slope_table2, motor->steps2,
				      motor->steps2 + 1, motor->vend2,
				      motor->vstart2, motor->vend2,
				      motor->steps2, motor->g2, &used2,
				      &vfinal);

  if (color == SANE_TRUE)
    channels = 3;
  else
    channels = 1;

  /* R01 */
  /* now setup other registers for final scan (ie with shading enabled) */
  /* watch dog + shading + scan enable */
  regs[reg_0x01].value |= REG01_DOGENB | REG01_DVDSET | REG01_SCAN;
  if (dev->model->is_cis == SANE_TRUE)
    regs[reg_0x01].value |= REG01_CISSET;
  else
    regs[reg_0x01].value &= ~REG01_CISSET;

  /* if device has no calibration, don't enable shading correction */
  if (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)
    {
      regs[reg_0x01].value &= ~REG01_DVDSET;
    }

  regs[reg_0x01].value &= ~REG01_FASTMOD;
  if (motor->fastmod)
    regs[reg_0x01].value |= REG01_FASTMOD;

  /* R02 */
  /* allow moving when buffer full by default */
  if (dev->model->is_sheetfed == SANE_FALSE)
    dev->reg[reg_0x02].value &= ~REG02_ACDCDIS;
  else
    dev->reg[reg_0x02].value |= REG02_ACDCDIS;

  /* setup motor power and direction */
  regs[reg_0x02].value |= REG02_MTRPWR;
  regs[reg_0x02].value &= ~REG02_MTRREV;

  /* fastfed enabled (2 motor slope tables) */
  if (motor->fastfed)
    regs[reg_0x02].value |= REG02_FASTFED;
  else
    regs[reg_0x02].value &= ~REG02_FASTFED;

  /* step type */
  regs[reg_0x02].value &= ~REG02_STEPSEL;
  switch (motor->steptype)
    {
    case FULL_STEP:
      break;
    case HALF_STEP:
      regs[reg_0x02].value |= 1;
      break;
    case QUATER_STEP:
      regs[reg_0x02].value |= 2;
      break;
    default:
      regs[reg_0x02].value |= 3;
      break;
    }

  /* if sheetfed, no AGOHOME */
  if (dev->model->is_sheetfed == SANE_TRUE)
    {
      regs[reg_0x02].value &= ~REG02_AGOHOME;
    }
  else
    {
      regs[reg_0x02].value |= REG02_AGOHOME;
    }

  /* R03 */
  regs[reg_0x03].value &= ~REG03_AVEENB;
  /* regs[reg_0x03].value |= REG03_AVEENB; */
  regs[reg_0x03].value &= ~REG03_LAMPDOG;

  /* select XPA */
  regs[reg_0x03].value &= ~REG03_XPASEL;
  if (scan_settings.scan_method == SCAN_METHOD_TRANSPARENCY)
    {
      regs[reg_0x03].value |= REG03_XPASEL;
    }

  /* R04 */
  /* monochrome / color scan */
  switch (depth)
    {
    case 1:
      regs[reg_0x04].value &= ~REG04_BITSET;
      regs[reg_0x04].value |= REG04_LINEART;
      break;
    case 8:
      regs[reg_0x04].value &= ~(REG04_LINEART | REG04_BITSET);
      break;
    case 16:
      regs[reg_0x04].value &= ~REG04_LINEART;
      regs[reg_0x04].value |= REG04_BITSET;
      break;
    }

  /* R05 */
  regs[reg_0x05].value &= ~REG05_DPIHW;
  switch (dev->sensor.optical_res)
    {
    case 600:
      regs[reg_0x05].value |= REG05_DPIHW_600;
      break;
    case 1200:
      regs[reg_0x05].value |= REG05_DPIHW_1200;
      break;
    case 2400:
      regs[reg_0x05].value |= REG05_DPIHW_2400;
      break;
    default:
      regs[reg_0x05].value |= REG05_DPIHW;
    }

  /* gamma enable for scans */
  if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA)
    regs[reg_0x05].value |= REG05_GMM14BIT;
  
  regs[reg_0x05].value &= ~REG05_GMMENB;

  /* true CIS gray if needed */
  if (dev->model->is_cis == SANE_TRUE && color == SANE_FALSE
      && dev->settings.true_gray)
    {
      regs[reg_0x05].value |= REG05_LEDADD;
    }
  else
    {
      regs[reg_0x05].value &= ~REG05_LEDADD;
    }

  /* cktoggle, ckdelay and cksel at once, cktdelay=2 => half_ccd for md5345 */
  regs[reg_0x18].value = sensor->r18;

  /* manual CCD/2 clock programming => half_ccd for hp2300 */
  regs[reg_0x1d].value = sensor->r1d;

  /* HP2400 1200dpi mode tuning */

  if (dev->model->ccd_type == CCD_HP2400)
    {
      /* reset count of dummy lines to zero */
      regs[reg_0x1e].value &= ~REG1E_LINESEL;
      if (scan_settings.xres >= 1200)
        {
          /* there must be one dummy line */
          regs[reg_0x1e].value |= 1 & REG1E_LINESEL;

          /* GPO12 need to be set to zero */
          regs[reg_0x66].value &= ~0x20;
        }
        else
        {
          /* set GPO12 back to one */
          regs[reg_0x66].value |= 0x20;
        }
    }

  /* motor steps used */
  regs[reg_0x21].value = motor->steps1;
  regs[reg_0x22].value = motor->fwdbwd;
  regs[reg_0x23].value = motor->fwdbwd;
  regs[reg_0x24].value = motor->steps1;

  /* scanned area height must be enlarged by max color shift needed */
  max_shift=sanei_genesys_compute_max_shift(dev,channels,scan_settings.yres,0);

  /* we adjust linecnt according to real motor dpi */
  linecnt = (linecnt * motor->ydpi) / scan_settings.yres + max_shift;

  /* at QUATER_STEP lines are 'staggered' and need correction */
  stagger = 0;
  if ((!half_ccd) && (dev->model->flags & GENESYS_FLAG_STAGGERED_LINE))
    {
      /* for HP3670, stagger happens only at >=1200 dpi */
      if ((dev->model->motor_type != MOTOR_HP3670
	   && dev->model->motor_type != MOTOR_HP2400)
	  || scan_settings.yres >= dev->sensor.optical_res)
	{
	  stagger = (4 * scan_settings.yres) / dev->motor.base_ydpi;
	}
    }
  linecnt += stagger;

  DBG (DBG_info, "gl646_setup_registers :  max_shift=%d, stagger=%d lines\n",
       max_shift, stagger);

  /* CIS scanners read one line per color channel
   * since gray mode use 'add' we also read 3 channels even not in
   * color mode */
  if (dev->model->is_cis == SANE_TRUE)
    {
      gl646_set_triple_reg (regs, REG_LINCNT, linecnt * 3);
      linecnt *= channels;
    }
  else
    {
      gl646_set_triple_reg (regs, REG_LINCNT, linecnt);
    }

  /* scanner's x coordinates are expressed in physical DPI but they must be divided by cksel */
  sx = startx / sensor->cksel;
  ex = endx / sensor->cksel;
  if (half_ccd == SANE_TRUE)
    {
      sx /= 2;
      ex /= 2;
    }
  gl646_set_double_reg (regs, REG_STRPIXEL, sx);
  gl646_set_double_reg (regs, REG_ENDPIXEL, ex);
  DBG (DBG_info, "gl646_setup_registers: startx=%d, endx=%d, half_ccd=%d\n",
       sx, ex, half_ccd);

  /* words_per_line must be computed according to the scan's resolution */
  /* in fact, words_per_line _gives_ the actual scan resolution */
  words_per_line = (((endx - startx) * sensor->xdpi) / dev->sensor.optical_res);
  bpp=depth/8;
  if (depth == 1)
    {
      words_per_line = (words_per_line+7)/8 ;
      bpp=1;
    }
  else
    {
      words_per_line *= bpp;
    }
  dev->bpl = words_per_line;
  words_per_line *= channels;
  dev->wpl = words_per_line;

  DBG (DBG_info, "gl646_setup_registers: wpl=%d\n", words_per_line);
  gl646_set_triple_reg (regs, REG_MAXWD, words_per_line);

  gl646_set_double_reg (regs, REG_DPISET, sensor->dpiset);
  gl646_set_double_reg (regs, REG_LPERIOD, sensor->exposure);

  /* move distance must be adjusted to take into account the extra lines
   * read to reorder data */
  feedl = move;
  if (stagger + max_shift > 0 && feedl != 0)
    {
      if (feedl >
	  ((max_shift + stagger) * dev->motor.optical_ydpi) / motor->ydpi)
	feedl =
	  feedl -
	  ((max_shift + stagger) * dev->motor.optical_ydpi) / motor->ydpi;
    }

  /* we assume all scans are done with 2 tables */
  /*
     feedl = feed_steps - fast_slope_steps*2 -
     (slow_slope_steps >> scan_step_type); */
  /* but head has moved due to shading calibration => dev->scanhead_position_in_steps */
  if (feedl > 0)
    {
      /* take into account the distance moved during calibration */
      /* feedl -= dev->scanhead_position_in_steps; */
      DBG (DBG_info, "gl646_setup_registers: initial move=%d\n", feedl);
      DBG (DBG_info, "gl646_setup_registers: scanhead_position_in_steps=%d\n",
	   dev->scanhead_position_in_steps);

      /* TODO clean up this when I'll fully understand.
       * for now, special casing each motor */
      switch (dev->model->motor_type)
	{
	case MOTOR_5345:
	  switch (motor->ydpi)
	    {
	    case 200:
	      feedl -= 70;
	      break;
	    case 300:
	      feedl -= 70;
	      break;
	    case 400:
	      feedl += 130;
	      break;
	    case 600:
	      feedl += 160;
	      break;
	    case 1200:
	      feedl += 160;
	      break;
	    case 2400:
	      feedl += 180;
	      break;
	    default:
	      break;
	    }
	  break;
	case MOTOR_HP2300:
	  switch (motor->ydpi)
	    {
	    case 75:
	      feedl -= 180;
	      break;
	    case 150:
	      feedl += 0;
	      break;
	    case 300:
	      feedl += 30;
	      break;
	    case 600:
	      feedl += 35;
	      break;
	    case 1200:
	      feedl += 45;
	      break;
	    default:
	      break;
	    }
	  break;
	case MOTOR_HP2400:
	  switch (motor->ydpi)
	    {
	    case 150:
	      feedl += 150;
	      break;
	    case 300:
	      feedl += 220;
	      break;
	    case 600:
	      feedl += 260;
	      break;
	    case 1200:
	      feedl += 280; /* 300 */
	      break;
	    case 50:
	      feedl += 0;
	      break;
	    case 100:
	      feedl += 100;
	      break;
	    default:
	      break;
	    }
	  break;

	  /* theorical value */
	default:
	  if (motor->fastfed)
	    {
	      feedl =
		feedl - 2 * motor->steps2 -
		(motor->steps1 >> motor->steptype);
	    }
	  else
	    {
	      feedl = feedl - (motor->steps1 >> motor->steptype);
	    }
	  break;
	}
      /* security */
      if (feedl < 0)
	feedl = 0;
    }

  DBG (DBG_info, "gl646_setup_registers: final move=%d\n", feedl);
  gl646_set_triple_reg (regs, REG_FEEDL, feedl);

  regs[reg_0x65].value = motor->mtrpwm;

  sanei_genesys_calculate_zmode2 (regs[reg_0x02].value & REG02_FASTFED,
				  sensor->exposure,
				  slope_table1,
				  motor->steps1,
				  move, motor->fwdbwd, &z1, &z2);

  /* no z1/z2 for sheetfed scanners */
  if (dev->model->is_sheetfed == SANE_TRUE)
    {
      z1 = 0;
      z2 = 0;
    }
  gl646_set_double_reg (regs, REG_Z1MOD, z1);
  gl646_set_double_reg (regs, REG_Z2MOD, z2);
  regs[reg_0x6b].value = motor->steps2;
  regs[reg_0x6c].value =
    (regs[reg_0x6c].value & REG6C_TGTIME) | ((z1 >> 13) & 0x38) | ((z2 >> 16)
								   & 0x07);

  RIE (write_control (dev, xresolution));

  /* setup analog frontend */
  RIE (gl646_set_fe (dev, AFE_SET, xresolution));

  /* now we're done with registers setup values used by data transfer */
  /* we setup values needed for the data transfer */

  /* we must use a round number of words_per_line */
  requested_buffer_size = 8 * words_per_line;
  read_buffer_size =
    2 * requested_buffer_size +
    ((max_shift + stagger) * scan_settings.pixels * channels * depth) / 8;

  RIE (sanei_genesys_buffer_free (&(dev->read_buffer)));
  RIE (sanei_genesys_buffer_alloc (&(dev->read_buffer), read_buffer_size));

  RIE (sanei_genesys_buffer_free (&(dev->lines_buffer)));
  RIE (sanei_genesys_buffer_alloc (&(dev->lines_buffer), read_buffer_size));

  RIE (sanei_genesys_buffer_free (&(dev->shrink_buffer)));
  RIE (sanei_genesys_buffer_alloc (&(dev->shrink_buffer),
				   requested_buffer_size));

  RIE (sanei_genesys_buffer_free (&(dev->out_buffer)));
  RIE (sanei_genesys_buffer_alloc (&(dev->out_buffer),
				   8 * scan_settings.pixels * channels * bpp));

  /* scan bytes to read */
  dev->read_bytes_left = words_per_line * linecnt;

  DBG (DBG_info,
       "gl646_setup_registers: physical bytes to read = %lu\n",
       (u_long) dev->read_bytes_left);
  dev->read_active = SANE_TRUE;

  dev->current_setup.pixels =
    ((endx - startx) * sensor->xdpi) / dev->sensor.optical_res;
  dev->current_setup.lines = linecnt;
  dev->current_setup.depth = depth;
  dev->current_setup.channels = channels;
  dev->current_setup.exposure_time = sensor->exposure;
  dev->current_setup.xres = sensor->xdpi;
  dev->current_setup.yres = motor->ydpi;
  dev->current_setup.half_ccd = half_ccd;
  dev->current_setup.stagger = stagger;
  dev->current_setup.max_shift = max_shift + stagger;

  /* total_bytes_to_read is the number of byte to send to frontend
   * total_bytes_read is the number of bytes sent to frontend
   * read_bytes_left is the number of bytes to read from the scanner
   */
  dev->total_bytes_read = 0;
  if (depth == 1)
    dev->total_bytes_to_read =
      ((scan_settings.pixels * scan_settings.lines) / 8 +
       (((scan_settings.pixels * scan_settings.lines) % 8) ? 1 : 0)) *
      channels;
  else
    dev->total_bytes_to_read =
      scan_settings.pixels * scan_settings.lines * channels * bpp;

  DBG (DBG_proc, "gl646_setup_registers: end\n");
  return SANE_STATUS_GOOD;
}


/** copy sensor specific settings */
/* *dev  : device infos
   *regs : regiters to be set
   extended : do extended set up
   half_ccd: set up for half ccd resolution
   all registers 08-0B, 10-1D, 52-5E are set up. They shouldn't
   appear anywhere else but in register init
*/
static void
gl646_setup_sensor (Genesys_Device * dev, Genesys_Register_Set * regs)
{
  Genesys_Register_Set *r;
  int i;

  DBG (DBG_proc, "gl646_setup_sensor: start\n");
  for (i = 0; i < 4; i++)
    {
      r = sanei_genesys_get_address (regs, 0x08 + i);
      r->value = dev->sensor.regs_0x08_0x0b[i];
    }

  for (i = 0; i < 14; i++)
    {
      r = sanei_genesys_get_address (regs, 0x10 + i);
      r->value = dev->sensor.regs_0x10_0x1d[i];
    }

  for (i = 0; i < 13; i++)
    {
      r = sanei_genesys_get_address (regs, 0x52 + i);
      r->value = dev->sensor.regs_0x52_0x5e[i];
    }
  DBG (DBG_proc, "gl646_setup_sensor: end\n");

}

/** Test if the ASIC works
 */
static SANE_Status
gl646_asic_test (Genesys_Device * dev)
{
  SANE_Status status;
  uint8_t val;
  uint8_t *data;
  uint8_t *verify_data;
  size_t size, verify_size;
  unsigned int i;

  DBG (DBG_proc, "gl646_asic_test: start\n");

  /* set and read exposure time, compare if it's the same */
  status = sanei_genesys_write_register (dev, 0x38, 0xde);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to write register: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = sanei_genesys_write_register (dev, 0x39, 0xad);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to write register: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = sanei_genesys_read_register (dev, 0x4e, &val);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to read register: %s\n",
	   sane_strstatus (status));
      return status;
    }
  if (val != 0xde)		/* value of register 0x38 */
    {
      DBG (DBG_error, "gl646_asic_test: register contains invalid value\n");
      return SANE_STATUS_IO_ERROR;
    }

  status = sanei_genesys_read_register (dev, 0x4f, &val);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to read register: %s\n",
	   sane_strstatus (status));
      return status;
    }
  if (val != 0xad)		/* value of register 0x39 */
    {
      DBG (DBG_error, "gl646_asic_test: register contains invalid value\n");
      return SANE_STATUS_IO_ERROR;
    }

  /* ram test: */
  size = 0x40000;
  verify_size = size + 0x80;
  /* todo: looks like the read size must be a multiple of 128?
     otherwise the read doesn't succeed the second time after the scanner has
     been plugged in. Very strange. */

  data = (uint8_t *) malloc (size);
  if (!data)
    {
      DBG (DBG_error, "gl646_asic_test: could not allocate memory\n");
      return SANE_STATUS_NO_MEM;
    }

  verify_data = (uint8_t *) malloc (verify_size);
  if (!verify_data)
    {
      free (data);
      DBG (DBG_error, "gl646_asic_test: could not allocate memory\n");
      return SANE_STATUS_NO_MEM;
    }

  for (i = 0; i < (size - 1); i += 2)
    {
      data[i] = i / 512;
      data[i + 1] = (i / 2) % 256;
    }

  status = sanei_genesys_set_buffer_address (dev, 0x0000);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to set buffer address: %s\n",
	   sane_strstatus (status));
      free (data);
      free (verify_data);
      return status;
    }

  status = gl646_bulk_write_data (dev, 0x3c, data, size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to bulk write data: %s\n",
	   sane_strstatus (status));
      free (data);
      free (verify_data);
      return status;
    }

  status = sanei_genesys_set_buffer_address (dev, 0x0000);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to set buffer address: %s\n",
	   sane_strstatus (status));
      free (data);
      free (verify_data);
      return status;
    }

  status =
    gl646_bulk_read_data (dev, 0x45, (uint8_t *) verify_data, verify_size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_asic_test: failed to bulk read data: %s\n",
	   sane_strstatus (status));
      free (data);
      free (verify_data);
      return status;
    }

  /* i + 2 is needed as the changed address goes into effect only after one
     data word is sent. */
  for (i = 0; i < size; i++)
    {
      if (verify_data[i + 2] != data[i])
	{
	  DBG (DBG_error, "gl646_asic_test: data verification error\n");
	  free (data);
	  free (verify_data);
	  return SANE_STATUS_IO_ERROR;
	}
    }

  free (data);
  free (verify_data);

  DBG (DBG_info, "gl646_asic_test: end\n");

  return SANE_STATUS_GOOD;
}

/* returns the max register bulk size */
static int
gl646_bulk_full_size (void)
{
  return GENESYS_GL646_MAX_REGS;
}

/**
 * Set all registers to default values after init
 * @param dev scannerr's device to set
 */
static void
gl646_init_regs (Genesys_Device * dev)
{
  int nr, addr;

  DBG (DBG_proc, "gl646_init_regs\n");

  nr = 0;
  memset (dev->reg, 0, GENESYS_MAX_REGS * sizeof (Genesys_Register_Set));

  for (addr = 1; addr <= 0x0b; addr++)
    dev->reg[nr++].address = addr;
  for (addr = 0x10; addr <= 0x29; addr++)
    dev->reg[nr++].address = addr;
  for (addr = 0x2c; addr <= 0x39; addr++)
    dev->reg[nr++].address = addr;
  for (addr = 0x3d; addr <= 0x3f; addr++)
    dev->reg[nr++].address = addr;
  for (addr = 0x52; addr <= 0x5e; addr++)
    dev->reg[nr++].address = addr;
  for (addr = 0x60; addr <= 0x6d; addr++)
    dev->reg[nr++].address = addr;

  dev->reg[reg_0x01].value = 0x20 /*0x22 */ ;	/* enable shading, CCD, color, 1M */
  dev->reg[reg_0x02].value = 0x30 /*0x38 */ ;	/* auto home, one-table-move, full step */
  if (dev->model->motor_type == MOTOR_5345)
    dev->reg[reg_0x02].value |= 0x01;	/* half-step */
  switch (dev->model->motor_type)
    {
    case MOTOR_5345:
      dev->reg[reg_0x02].value |= 0x01;	/* half-step */
      break;
    case MOTOR_XP200:
      /* for this sheetfed scanner, no AGOHOME, nor backtracking */
      dev->reg[reg_0x02].value = 0x50;
      break;
    default:
      break;
    }
  dev->reg[reg_0x03].value = 0x1f /*0x17 */ ;	/* lamp on */
  dev->reg[reg_0x04].value = 0x13 /*0x03 */ ;	/* 8 bits data, 16 bits A/D, color, Wolfson fe *//* todo: according to spec, 0x0 is reserved? */
  switch (dev->model->dac_type)
    {
    case DAC_AD_XP200:
      dev->reg[reg_0x04].value = 0x12;
      break;
    default:
      /* Wolfson frontend */
      dev->reg[reg_0x04].value = 0x13;
      break;
    }

  dev->reg[reg_0x05].value = 0x00;	/* 12 bits gamma, disable gamma, 24 clocks/pixel */
  switch (dev->sensor.optical_res)
    {
    case 600:
      dev->reg[reg_0x05].value |= REG05_DPIHW_600;
      break;
    case 1200:
      dev->reg[reg_0x05].value |= REG05_DPIHW_1200;
      break;
    case 2400:
      dev->reg[reg_0x05].value |= REG05_DPIHW_2400;
      break;
    default:
      dev->reg[reg_0x05].value |= REG05_DPIHW;
      break;
    }
  if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA)
    dev->reg[reg_0x05].value |= REG05_GMM14BIT;
  if (dev->model->dac_type == DAC_AD_XP200)
    dev->reg[reg_0x05].value |= 0x01;	/* 12 clocks/pixel */

  if (dev->model->ccd_type == CCD_HP2300)
    dev->reg[reg_0x06].value = 0x00;	/* PWRBIT off, shading gain=4, normal AFE image capture */
  else
    dev->reg[reg_0x06].value = 0x18;	/* PWRBIT on, shading gain=8, normal AFE image capture */


  gl646_setup_sensor (dev, dev->reg);

  dev->reg[reg_0x1e].value = 0xf0;	/* watch-dog time */

  switch (dev->model->ccd_type)
    {
    case CCD_HP2300:
      dev->reg[reg_0x1e].value = 0xf0;
      dev->reg[reg_0x1f].value = 0x10;
      dev->reg[reg_0x20].value = 0x20;
      break;
    case CCD_HP2400:
      dev->reg[reg_0x1e].value = 0x80;
      dev->reg[reg_0x1f].value = 0x10;
      dev->reg[reg_0x20].value = 0x20;
      break;
    case CCD_HP3670:
      dev->reg[reg_0x19].value = 0x2a;
      dev->reg[reg_0x1e].value = 0x80;
      dev->reg[reg_0x1f].value = 0x10;
      dev->reg[reg_0x20].value = 0x20;
      break;
    case CIS_XP200:
      dev->reg[reg_0x1e].value = 0x10;
      dev->reg[reg_0x1f].value = 0x01;
      dev->reg[reg_0x20].value = 0x50;
      break;
    default:
      dev->reg[reg_0x1f].value = 0x01;
      dev->reg[reg_0x20].value = 0x50;
      break;
    }

  dev->reg[reg_0x21].value = 0x08 /*0x20 */ ;	/* table one steps number for forward slope curve of the acc/dec */
  dev->reg[reg_0x22].value = 0x10 /*0x08 */ ;	/* steps number of the forward steps for start/stop */
  dev->reg[reg_0x23].value = 0x10 /*0x08 */ ;	/* steps number of the backward steps for start/stop */
  dev->reg[reg_0x24].value = 0x08 /*0x20 */ ;	/* table one steps number backward slope curve of the acc/dec */
  dev->reg[reg_0x25].value = 0x00;	/* scan line numbers (7000) */
  dev->reg[reg_0x26].value = 0x00 /*0x1b */ ;
  dev->reg[reg_0x27].value = 0xd4 /*0x58 */ ;
  dev->reg[reg_0x28].value = 0x01;	/* PWM duty for lamp control */
  dev->reg[reg_0x29].value = 0xff;

  dev->reg[reg_0x2c].value = 0x02;	/* set resolution (600 DPI) */
  dev->reg[reg_0x2d].value = 0x58;
  dev->reg[reg_0x2e].value = 0x78;	/* set black&white threshold high level */
  dev->reg[reg_0x2f].value = 0x7f;	/* set black&white threshold low level */

  dev->reg[reg_0x30].value = 0x00;	/* begin pixel position (16) */
  dev->reg[reg_0x31].value = dev->sensor.dummy_pixel /*0x10 */ ;	/* TGW + 2*TG_SHLD + x  */
  dev->reg[reg_0x32].value = 0x2a /*0x15 */ ;	/* end pixel position (5390) */
  dev->reg[reg_0x33].value = 0xf8 /*0x0e */ ;	/* TGW + 2*TG_SHLD + y   */
  dev->reg[reg_0x34].value = dev->sensor.dummy_pixel;
  dev->reg[reg_0x35].value = 0x01 /*0x00 */ ;	/* set maximum word size per line, for buffer full control (10800) */
  dev->reg[reg_0x36].value = 0x00 /*0x2a */ ;
  dev->reg[reg_0x37].value = 0x00 /*0x30 */ ;
  dev->reg[reg_0x38].value = HIBYTE (dev->settings.exposure_time) /*0x2a */ ;	/* line period (exposure time = 11000 pixels) */
  dev->reg[reg_0x39].value = LOBYTE (dev->settings.exposure_time) /*0xf8 */ ;
  dev->reg[reg_0x3d].value = 0x00;	/* set feed steps number of motor move */
  dev->reg[reg_0x3e].value = 0x00;
  dev->reg[reg_0x3f].value = 0x01 /*0x00 */ ;

  dev->reg[reg_0x60].value = 0x00;	/* Z1MOD, 60h:61h:(6D b5:b3), remainder for start/stop */
  dev->reg[reg_0x61].value = 0x00;	/* (21h+22h)/LPeriod */
  dev->reg[reg_0x62].value = 0x00;	/* Z2MODE, 62h:63h:(6D b2:b0), remainder for start scan */
  dev->reg[reg_0x63].value = 0x00;	/* (3Dh+3Eh+3Fh)/LPeriod for one-table mode,(21h+1Fh)/LPeriod */
  dev->reg[reg_0x64].value = 0x00;	/* motor PWM frequency */
  dev->reg[reg_0x65].value = 0x00;	/* PWM duty cycle for table one motor phase (63 = max) */
  if (dev->model->motor_type == MOTOR_5345)
    dev->reg[reg_0x65].value = 0x02;	/* PWM duty cycle for table one motor phase (63 = max) */
  dev->reg[reg_0x66].value = dev->gpo.value[0];
  dev->reg[reg_0x67].value = dev->gpo.value[1];
  dev->reg[reg_0x68].value = dev->gpo.enable[0];
  dev->reg[reg_0x69].value = dev->gpo.enable[1];

  switch (dev->model->motor_type)
    {
    case MOTOR_HP2300:
    case MOTOR_HP2400:
      dev->reg[reg_0x6a].value = 0x7f;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6b].value = 0x78;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6d].value = 0x7f;
      break;
    case MOTOR_5345:
      dev->reg[reg_0x6a].value = 0x42;	/* table two fast moving step type, PWM duty for table two */
      dev->reg[reg_0x6b].value = 0xff;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6d].value = 0x41;	/* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */
      break;
    case MOTOR_XP200:
      dev->reg[reg_0x6a].value = 0x7f;	/* table two fast moving step type, PWM duty for table two */
      dev->reg[reg_0x6b].value = 0x08;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6d].value = 0x01;	/* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */
      break;
    case MOTOR_HP3670:
      dev->reg[reg_0x6a].value = 0x41;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6b].value = 0xc8;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6d].value = 0x7f;
      break;
    default:
      dev->reg[reg_0x6a].value = 0x40;	/* table two fast moving step type, PWM duty for table two */
      dev->reg[reg_0x6b].value = 0xff;	/* table two steps number for acc/dec */
      dev->reg[reg_0x6d].value = 0x01;	/* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */
      break;
    }
  dev->reg[reg_0x6c].value = 0x00;	/* peroid times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE (one period time) */
}


/* Send slope table for motor movement
   slope_table in machine byte order
*/
static SANE_Status
gl646_send_slope_table (Genesys_Device * dev, int table_nr,
			uint16_t * slope_table, int steps)
{
  int dpihw;
  int start_address;
  SANE_Status status;
  uint8_t *table;
#ifdef WORDS_BIGENDIAN
  int i;
#endif

  DBG (DBG_proc,
       "gl646_send_slope_table (table_nr = %d, steps = %d)=%d .. %d\n",
       table_nr, steps, slope_table[0], slope_table[steps - 1]);

  dpihw = dev->reg[reg_0x05].value >> 6;

  if (dpihw == 0)		/* 600 dpi */
    start_address = 0x08000;
  else if (dpihw == 1)		/* 1200 dpi */
    start_address = 0x10000;
  else if (dpihw == 2)		/* 2400 dpi */
    start_address = 0x1f800;
  else				/* reserved */
    return SANE_STATUS_INVAL;

#ifdef WORDS_BIGENDIAN
  table = (uint8_t *) malloc (steps * 2);
  for (i = 0; i < steps; i++)
    {
      table[i * 2] = slope_table[i] & 0xff;
      table[i * 2 + 1] = slope_table[i] >> 8;
    }
#else
  table = (uint8_t *) slope_table;
#endif

  status =
    sanei_genesys_set_buffer_address (dev, start_address + table_nr * 0x100);
  if (status != SANE_STATUS_GOOD)
    {
#ifdef WORDS_BIGENDIAN
      free (table);
#endif
      DBG (DBG_error,
	   "gl646_send_slope_table: failed to set buffer address: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gl646_bulk_write_data (dev, 0x3c, (uint8_t *) table, steps * 2);
  if (status != SANE_STATUS_GOOD)
    {
#ifdef WORDS_BIGENDIAN
      free (table);
#endif
      DBG (DBG_error,
	   "gl646_send_slope_table: failed to send slope table: %s\n",
	   sane_strstatus (status));
      return status;
    }

#ifdef WORDS_BIGENDIAN
  free (table);
#endif
  DBG (DBG_proc, "gl646_send_slope_table: end\n");
  return status;
}

/* Set values of Analog Device type frontend */
static SANE_Status
gl646_set_ad_fe (Genesys_Device * dev, uint8_t set)
{
  SANE_Status status = SANE_STATUS_GOOD;
  int i;
  uint16_t val;

  DBG (DBG_proc, "gl646_set_ad_fe(): start\n");
  if (set == AFE_INIT)
    {
      DBG (DBG_proc, "gl646_set_ad_fe(): setting DAC %u\n",
	   dev->model->dac_type);

      /* sets to default values */
      sanei_genesys_init_fe (dev);

      /* write them to analog frontend */
      val = dev->frontend.reg[0];
      status = sanei_genesys_fe_write_data (dev, 0x00, val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_set_ad_fe: failed to write reg0: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      val = dev->frontend.reg[1];
      status = sanei_genesys_fe_write_data (dev, 0x01, val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_set_ad_fe: failed to write reg1: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }
  if (set == AFE_SET)
    {
      for (i = 0; i < 3; i++)
	{
	  val = dev->frontend.gain[i];
	  status = sanei_genesys_fe_write_data (dev, 0x02 + i, val);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_set_ad_fe: failed to write gain %d: %s\n", i,
		   sane_strstatus (status));
	      return status;
	    }
	}
      for (i = 0; i < 3; i++)
	{
	  val = dev->frontend.offset[i];
	  status = sanei_genesys_fe_write_data (dev, 0x05 + i, val);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_set_ad_fe: failed to write offset %d: %s\n", i,
		   sane_strstatus (status));
	      return status;
	    }
	}
    }
  /*
     if (set == AFE_POWER_SAVE)
     {
     status =
     sanei_genesys_fe_write_data (dev, 0x00, dev->frontend.reg[0] | 0x04);
     if (status != SANE_STATUS_GOOD)
     {
     DBG (DBG_error, "gl646_set_ad_fe: failed to write reg0: %s\n",
     sane_strstatus (status));
     return status;
     }
     } */
  DBG (DBG_proc, "gl646_set_ad_fe(): end\n");

  return status;
}

/** set up analog frontend
 * set up analog frontend
 * @param dev device to set up
 * @param set action from AFE_SET, AFE_INIT and AFE_POWERSAVE
 * @param dpi resolution of the scan since it affects settings
 * @return SANE_STATUS_GOOD if evrithing OK
 */
static SANE_Status
gl646_wm_hp3670 (Genesys_Device * dev, uint8_t set, int dpi)
{
  SANE_Status status = SANE_STATUS_GOOD;
  int i;

  DBGSTART;
  switch (set)
    {
    case AFE_INIT:
      status = sanei_genesys_fe_write_data (dev, 0x04, 0x80);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: reset failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      usleep (200000UL);
      RIE (sanei_genesys_write_register (dev, 0x50, 0x00));
      sanei_genesys_init_fe (dev);
      status = sanei_genesys_fe_write_data (dev, 0x01, dev->frontend.reg[1]);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: writing reg1 failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      status = sanei_genesys_fe_write_data (dev, 0x02, dev->frontend.reg[2]);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: writing reg2 failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      status = gl646_gpio_output_enable (dev->dn, 0x07);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: failed to enable GPIO: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      break;
    case AFE_POWER_SAVE:
      status = sanei_genesys_fe_write_data (dev, 0x01, 0x06);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: writing reg1 failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      status = sanei_genesys_fe_write_data (dev, 0x06, 0x0f);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: writing reg6 failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      return status;
      break;
    default:			/* AFE_SET */
      /* mode setup */
      i = dev->frontend.reg[3];
      if (dpi > dev->sensor.optical_res / 2)
	{
	  /* fe_reg_0x03 must be 0x12 for 1200 dpi in DAC_WOLFSON_HP3670.
	   * DAC_WOLFSON_HP2400 in 1200 dpi mode works well with
	   * fe_reg_0x03 set to 0x32 or 0x12 but not to 0x02 */
	  i = 0x12;
	}
      status = sanei_genesys_fe_write_data (dev, 0x03, i);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_wm_hp3670: writing reg3 failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      /* offset and sign (or msb/lsb ?) */
      for (i = 0; i < 3; i++)
	{
	  status =
	    sanei_genesys_fe_write_data (dev, 0x20 + i,
					 dev->frontend.offset[i]);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_wm_hp3670: writing offset%d failed: %s\n", i,
		   sane_strstatus (status));
	      return status;
	    }
	  status = sanei_genesys_fe_write_data (dev, 0x24 + i, dev->frontend.sign[i]);	/* MSB/LSB ? */
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error, "gl646_wm_hp3670: writing sign%d failed: %s\n",
		   i, sane_strstatus (status));
	      return status;
	    }
	}

      /* gain */
      for (i = 0; i < 3; i++)
	{
	  status =
	    sanei_genesys_fe_write_data (dev, 0x28 + i,
					 dev->frontend.gain[i]);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error, "gl646_wm_hp3670: writing gain%d failed: %s\n",
		   i, sane_strstatus (status));
	      return status;
	    }
	}
    }

  DBGCOMPLETED;
  return status;
}

/** Set values of analog frontend
 * @param dev device to set
 * @param set action to execute
 * @param dpi dpi to setup the AFE
 * @return error or SANE_STATUS_GOOD */
#ifndef UNIT_TESTING
static
#endif
  SANE_Status
gl646_set_fe (Genesys_Device * dev, uint8_t set, int dpi)
{
  SANE_Status status;
  int i;
  uint8_t val;

  DBG (DBG_proc, "gl646_set_fe (%s,%d)\n",
       set == AFE_INIT ? "init" : set == AFE_SET ? "set" : set ==
       AFE_POWER_SAVE ? "powersave" : "huh?", dpi);

  /* Analog Device type frontend */
  if ((dev->reg[reg_0x04].value & REG04_FESET) == 0x02)
    return gl646_set_ad_fe (dev, set);

  /* Wolfson type frontend */
  if ((dev->reg[reg_0x04].value & REG04_FESET) != 0x03)
    {
      DBG (DBG_proc, "gl646_set_fe(): unsupported frontend type %d\n",
	   dev->reg[reg_0x04].value & REG04_FESET);
      return SANE_STATUS_UNSUPPORTED;
    }

  /* per frontend function to keep code clean */
  switch (dev->model->dac_type)
    {
    case DAC_WOLFSON_HP3670:
    case DAC_WOLFSON_HP2400:
      return gl646_wm_hp3670 (dev, set, dpi);
      break;
    default:
      DBG (DBG_proc, "gl646_set_fe(): using old method\n");
      break;
    }

  /* initialize analog frontend */
  if (set == AFE_INIT)
    {
      DBG (DBG_proc, "gl646_set_fe(): setting DAC %u\n",
	   dev->model->dac_type);
      sanei_genesys_init_fe (dev);

      /* reset only done on init */
      status = sanei_genesys_fe_write_data (dev, 0x04, 0x80);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_set_fe: init fe failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      /* enable GPIO for some models */
      if (dev->model->ccd_type == CCD_HP2300)
	{
	  val = 0x07;
	  status = gl646_gpio_output_enable (dev->dn, val);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_set_fe: failed to enable GPIO: %s\n",
		   sane_strstatus (status));
	      return status;
	    }
	}
      return status;
    }

  /* set fontend to power saving mode */
  if (set == AFE_POWER_SAVE)
    {
      status = sanei_genesys_fe_write_data (dev, 0x01, 0x02);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_set_fe: writing data failed: %s\n",
	       sane_strstatus (status));
	}
      return status;
    }

  /* here starts AFE_SET */
  /* TODO :  base this test on cfg reg3 or a CCD family flag to be created */
  /* if (dev->model->ccd_type != CCD_HP2300
     && dev->model->ccd_type != CCD_HP3670
     && dev->model->ccd_type != CCD_HP2400) */
  {
    status = sanei_genesys_fe_write_data (dev, 0x00, dev->frontend.reg[0]);
    if (status != SANE_STATUS_GOOD)
      {
	DBG (DBG_error, "gl646_set_fe: writing reg0 failed: %s\n",
	     sane_strstatus (status));
	return status;
      }
    status = sanei_genesys_fe_write_data (dev, 0x02, dev->frontend.reg[2]);
    if (status != SANE_STATUS_GOOD)
      {
	DBG (DBG_error, "gl646_set_fe: writing reg2 failed: %s\n",
	     sane_strstatus (status));
	return status;
      }
  }

  /* start with reg3 */
  status = sanei_genesys_fe_write_data (dev, 0x03, dev->frontend.reg[3]);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_set_fe: writing reg3 failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  switch (dev->model->ccd_type)
    {
    default:
      for (i = 0; i < 3; i++)
	{
	  status =
	    sanei_genesys_fe_write_data (dev, 0x24 + i,
					 dev->frontend.sign[i]);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error, "gl646_set_fe: writing sign[%d] failed: %s\n",
		   i, sane_strstatus (status));
	      return status;
	    }

	  status =
	    sanei_genesys_fe_write_data (dev, 0x28 + i,
					 dev->frontend.gain[i]);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error, "gl646_set_fe: writing gain[%d] failed: %s\n",
		   i, sane_strstatus (status));
	      return status;
	    }

	  status =
	    sanei_genesys_fe_write_data (dev, 0x20 + i,
					 dev->frontend.offset[i]);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_set_fe: writing offset[%d] failed: %s\n", i,
		   sane_strstatus (status));
	      return status;
	    }
	}
      break;
      /* just can't have it to work ....
         case CCD_HP2300:
         case CCD_HP2400:
         case CCD_HP3670:

         status =
         sanei_genesys_fe_write_data (dev, 0x23, dev->frontend.offset[1]);
         if (status != SANE_STATUS_GOOD)
         {
         DBG (DBG_error,
         "gl646_set_fe: writing offset[1] failed: %s\n",
         sane_strstatus (status));
         return status;
         }
         status = sanei_genesys_fe_write_data (dev, 0x28, dev->frontend.gain[1]);
         if (status != SANE_STATUS_GOOD)
         {
         DBG (DBG_error, "gl646_set_fe: writing gain[1] failed: %s\n",
         sane_strstatus (status));
         return status;
         }
         break; */
    }

  /* end with reg1 */
  status = sanei_genesys_fe_write_data (dev, 0x01, dev->frontend.reg[1]);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_set_fe: writing reg1 failed: %s\n",
	   sane_strstatus (status));
      return status;
    }


  DBG (DBG_proc, "gl646_set_fe: end\n");

  return SANE_STATUS_GOOD;
}

/** Set values of analog frontend
 * this this the public interface, the gl646 as to use one more
 * parameter to work effectively, hence the redirection
 * @param dev device to set
 * @param set action to execute
 * @return error or SANE_STATUS_GOOD */
#ifndef UNIT_TESTING
static
#endif
  SANE_Status
gl646_public_set_fe (Genesys_Device * dev, uint8_t set)
{
  return gl646_set_fe (dev, set, dev->settings.yres);
}

static void
gl646_set_motor_power (Genesys_Register_Set * regs, SANE_Bool set)
{
  if (set)
    {
      sanei_genesys_set_reg_from_set (regs, 0x02,
				      sanei_genesys_read_reg_from_set (regs,
								       0x02) |
				      REG02_MTRPWR);
    }
  else
    {
      sanei_genesys_set_reg_from_set (regs, 0x02,
				      sanei_genesys_read_reg_from_set (regs,
								       0x02) &
				      ~REG02_MTRPWR);
    }
}

static void
gl646_set_lamp_power (Genesys_Device * dev,
		      Genesys_Register_Set * regs, SANE_Bool set)
{
  if (dev)
    {
      if (set)
	{
	  sanei_genesys_set_reg_from_set (regs, 0x03,
					  sanei_genesys_read_reg_from_set
					  (regs, 0x03) | REG03_LAMPPWR);
	}
      else
	{
	  sanei_genesys_set_reg_from_set (regs, 0x03,
					  sanei_genesys_read_reg_from_set
					  (regs, 0x03) & ~REG03_LAMPPWR);
	}
    }
}

/**
 * enters or leaves power saving mode
 * limited to AFE for now.
 * @param dev scanner's device
 * @param enable SANE_TRUE to enable power saving, SANE_FALSE to leave it
 * @return allways SANE_STATUS_GOOD
 */
GENESYS_STATIC
SANE_Status
gl646_save_power (Genesys_Device * dev, SANE_Bool enable)
{

  DBGSTART;
  DBG (DBG_info, "gl646_save_power: enable = %d\n", enable);

  if (enable)
    {
      /* gl646_set_fe (dev, AFE_POWER_SAVE); */
    }
  else
    {
      gl646_set_fe (dev, AFE_INIT, 0);
    }

  DBGCOMPLETED;
  return SANE_STATUS_GOOD;
}

static SANE_Status
gl646_set_powersaving (Genesys_Device * dev, int delay /* in minutes */ )
{
  SANE_Status status = SANE_STATUS_GOOD;
  Genesys_Register_Set local_reg[6];
  int rate, exposure_time, tgtime, time;

  DBG (DBG_proc, "gl646_set_powersaving (delay = %d)\n", delay);

  local_reg[0].address = 0x01;
  local_reg[0].value = sanei_genesys_read_reg_from_set (dev->reg, 0x01);	/* disable fastmode */

  local_reg[1].address = 0x03;
  local_reg[1].value = sanei_genesys_read_reg_from_set (dev->reg, 0x03);	/* Lamp power control */

  local_reg[2].address = 0x05;
  local_reg[2].value = sanei_genesys_read_reg_from_set (dev->reg, 0x05) & ~REG05_BASESEL;	/* 24 clocks/pixel */

  local_reg[3].address = 0x38;	/* line period low */
  local_reg[3].value = 0x00;

  local_reg[4].address = 0x39;	/* line period high */
  local_reg[4].value = 0x00;

  local_reg[5].address = 0x6c;	/* period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE */
  local_reg[5].value = 0x00;

  if (!delay)
    local_reg[1].value = local_reg[1].value & 0xf0;	/* disable lampdog and set lamptime = 0 */
  else if (delay < 20)
    local_reg[1].value = (local_reg[1].value & 0xf0) | 0x09;	/* enable lampdog and set lamptime = 1 */
  else
    local_reg[1].value = (local_reg[1].value & 0xf0) | 0x0f;	/* enable lampdog and set lamptime = 7 */

  time = delay * 1000 * 60;	/* -> msec */
  exposure_time =
    (uint32_t) (time * 32000.0 /
		(24.0 * 64.0 * (local_reg[1].value & REG03_LAMPTIM) *
		 1024.0) + 0.5);
  /* 32000 = system clock, 24 = clocks per pixel */
  rate = (exposure_time + 65536) / 65536;
  if (rate > 4)
    {
      rate = 8;
      tgtime = 3;
    }
  else if (rate > 2)
    {
      rate = 4;
      tgtime = 2;
    }
  else if (rate > 1)
    {
      rate = 2;
      tgtime = 1;
    }
  else
    {
      rate = 1;
      tgtime = 0;
    }

  local_reg[5].value |= tgtime << 6;
  exposure_time /= rate;

  if (exposure_time > 65535)
    exposure_time = 65535;

  local_reg[3].value = exposure_time / 256;	/* highbyte */
  local_reg[4].value = exposure_time & 255;	/* lowbyte */

  status = gl646_bulk_write_register (dev, local_reg,
				      sizeof (local_reg) /
				      sizeof (local_reg[0]));
  if (status != SANE_STATUS_GOOD)
    DBG (DBG_error,
	 "gl646_set_powersaving: Failed to bulk write registers: %s\n",
	 sane_strstatus (status));

  DBG (DBG_proc, "gl646_set_powersaving: end\n");
  return status;
}


/**
 * loads document into scanner
 * currently only used by XP200
 * bit2 (0x04) of gpio is paper event (document in/out) on XP200
 * HOMESNR is set if no document in front of sensor, the sequence of events is
 * paper event -> document is in the sheet feeder
 * HOMESNR becomes 0 -> document reach sensor
 * HOMESNR becomes 1 ->document left sensor
 * paper event -> document is out
 */
#ifndef UNIT_TESTING
static
#endif
  SANE_Status
gl646_load_document (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  Genesys_Register_Set regs[11];
  unsigned int used, vfinal, count;
  uint16_t slope_table[255];
  uint8_t val;

  DBG (DBG_proc, "gl646_load_document: start\n");

  /* no need to load document is flatbed scanner */
  if (dev->model->is_sheetfed == SANE_FALSE)
    {
      DBG (DBG_proc, "gl646_load_document: nothing to load\n");
      DBG (DBG_proc, "gl646_load_document: end\n");
      return SANE_STATUS_GOOD;
    }

  status = sanei_genesys_get_status (dev, &val);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_load_document: failed to read status: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* HOMSNR is set if a document is inserted */
  if ((val & REG41_HOMESNR))
    {
      /* if no document, waits for a paper event to start loading */
      /* with a 60 seconde minutes timeout                        */
      count = 0;
      do
	{
	  status = gl646_gpio_read (dev->dn, &val);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_load_document: failed to read paper sensor %s\n",
		   sane_strstatus (status));
	      return status;
	    }
	  DBG (DBG_info, "gl646_load_document: GPIO=0x%02x\n", val);
	  if ((val & 0x04) != 0x04)
	    {
	      DBG (DBG_warn, "gl646_load_document: no paper detected\n");
	    }
	  usleep (200000UL);	/* sleep 200 ms */
	  count++;
	}
      while (((val & 0x04) != 0x04) && (count < 300));	/* 1 min time out */
      if (count == 300)
	{
	  DBG (DBG_error,
	       "gl646_load_document: timeout waiting for document\n");
	  return SANE_STATUS_NO_DOCS;
	}
    }

  /* set up to fast move before scan then move until document is detected */
  regs[0].address = 0x01;
  regs[0].value = 0x90;

  /* AGOME, 2 slopes motor moving */
  regs[1].address = 0x02;
  regs[1].value = 0x79;

  /* motor feeding steps to 0 */
  regs[2].address = 0x3d;
  regs[2].value = 0;
  regs[3].address = 0x3e;
  regs[3].value = 0;
  regs[4].address = 0x3f;
  regs[4].value = 0;

  /* 50 fast moving steps */
  regs[5].address = 0x6b;
  regs[5].value = 50;

  /* set GPO */
  regs[6].address = 0x66;
  regs[6].value = 0x30;

  /* stesp NO */
  regs[7].address = 0x21;
  regs[7].value = 4;
  regs[8].address = 0x22;
  regs[8].value = 1;
  regs[9].address = 0x23;
  regs[9].value = 1;
  regs[10].address = 0x24;
  regs[10].value = 4;

  /* generate slope table 2 */
  sanei_genesys_generate_slope_table (slope_table,
				      50,
				      51,
				      2400,
				      6000, 2400, 50, 0.25, &used, &vfinal);
/* document loading:
 * send regs
 * start motor
 * wait e1 status to become e0
 */
  status = gl646_send_slope_table (dev, 1, slope_table, 50);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_load_document: failed to send slope table 1: %s\n",
	   sane_strstatus (status));
      return status;
    }
  status =
    gl646_bulk_write_register (dev, regs, sizeof (regs) / sizeof (regs[0]));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_load_document: failed to bulk write registers: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gl646_start_motor (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_load_document: failed to start motor: %s\n",
	   sane_strstatus (status));
      return SANE_STATUS_IO_ERROR;
    }

  count = 0;
  do
    {
      status = sanei_genesys_get_status (dev, &val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_load_document: failed to read status: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      usleep (200000UL);	/* sleep 200 ms */
      count++;
    }
  while ((val & REG41_MOTMFLG) && (count < 300));
  if (count == 300)
    {
      DBG (DBG_error, "gl646_load_document: can't load document\n");
      return SANE_STATUS_JAMMED;
    }

  /* when loading OK, document is here */
  dev->document = SANE_TRUE;

  /* set up to idle */
  regs[1].value = 0x71;
  regs[4].value = 1;
  regs[5].value = 8;
  status =
    gl646_bulk_write_register (dev, regs, sizeof (regs) / sizeof (regs[0]));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_load_document: failed to bulk write idle registers: %s\n",
	   sane_strstatus (status));
      return status;
    }

  DBG (DBG_proc, "gl646_load_document: end\n");

  return status;
}

/**
 * detects end of document and adjust current scan
 * to take it into account
 * used by sheetfed scanners
 */
static SANE_Status
gl646_detect_document_end (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  uint8_t val, gpio;
  unsigned int bytes_left, lines;

  DBG (DBG_proc, "gl646_detect_document_end: start\n");

  /* test for document presence */
  RIE (sanei_genesys_get_status (dev, &val));
  if (DBG_LEVEL > DBG_info)
    {
      print_status (val);
    }
  status = gl646_gpio_read (dev->dn, &gpio);
  DBG (DBG_info, "gl646_detect_document_end: GPIO=0x%02x\n", gpio);

  /* detect document event. There one event when the document go in,
   * then another when it leaves */
  if ((dev->document == SANE_TRUE) && (gpio & 0x04)
      && (dev->total_bytes_read > 0))
    {
      DBG (DBG_info, "gl646_detect_document_end: no more document\n");
      dev->document = SANE_FALSE;

      /* adjust number of bytes to read:
       * total_bytes_to_read is the number of byte to send to frontend
       * total_bytes_read is the number of bytes sent to frontend
       * read_bytes_left is the number of bytes to read from the scanner
       */
      DBG (DBG_io, "gl646_detect_document_end: total_bytes_to_read=%lu\n",
	   (u_long) dev->total_bytes_to_read);
      DBG (DBG_io, "gl646_detect_document_end: total_bytes_read   =%lu\n",
	   (u_long) dev->total_bytes_read);
      DBG (DBG_io, "gl646_detect_document_end: read_bytes_left    =%lu\n",
	   (u_long) dev->read_bytes_left);

      /* amount of data available from scanner is what to scan */
      status = sanei_genesys_read_valid_words (dev, &bytes_left);

      /* we add the number of lines needed to read the last part of the document in */
      lines =
	(SANE_UNFIX (dev->model->y_offset) * dev->current_setup.yres) /
	MM_PER_INCH;
      DBG (DBG_io, "gl646_detect_document_end: adding %d line to flush\n",
	   lines);
      bytes_left += lines * dev->wpl;
      if (dev->current_setup.depth > 8)
	{
	  bytes_left = 2 * bytes_left;
	}
      if (dev->current_setup.channels > 1)
	{
	  bytes_left = 3 * bytes_left;
	}
      if (bytes_left < dev->read_bytes_left)
	{
	  dev->total_bytes_to_read = dev->total_bytes_read + bytes_left;
	  dev->read_bytes_left = bytes_left;
	}
      DBG (DBG_io, "gl646_detect_document_end: total_bytes_to_read=%lu\n",
	   (u_long) dev->total_bytes_to_read);
      DBG (DBG_io, "gl646_detect_document_end: total_bytes_read   =%lu\n",
	   (u_long) dev->total_bytes_read);
      DBG (DBG_io, "gl646_detect_document_end: read_bytes_left    =%lu\n",
	   (u_long) dev->read_bytes_left);
    }
  DBG (DBG_proc, "gl646_detect_document_end: end\n");

  return status;
}

/**
 * eject document from the feeder
 * currently only used by XP200
 * TODO we currently rely on AGOHOME not being set for sheetfed scanners,
 * maybe check this flag in eject to let the document being eject automaticaly
 */
static SANE_Status
gl646_eject_document (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  Genesys_Register_Set regs[11];
  unsigned int used, vfinal, count;
  uint16_t slope_table[255];
  uint8_t gpio, state;

  DBG (DBG_proc, "gl646_eject_document: start\n");

  /* at the end there will be noe more document */
  dev->document = SANE_FALSE;

  /* first check for document event */
  status = gl646_gpio_read (dev->dn, &gpio);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to read paper sensor %s\n",
	   sane_strstatus (status));
      return status;
    }
  DBG (DBG_info, "gl646_eject_document: GPIO=0x%02x\n", gpio);

  /* test status : paper event + HOMESNR -> no more doc ? */
  status = sanei_genesys_get_status (dev, &state);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to read status: %s\n",
	   sane_strstatus (status));
      return status;
    }
  DBG (DBG_info, "gl646_eject_document: state=0x%02x\n", state);
  if (DBG_LEVEL > DBG_info)
    {
      print_status (state);
    }

  /* HOMSNR=0 if no document inserted */
  if ((state & REG41_HOMESNR) != 0)
    {
      dev->document = SANE_FALSE;
      DBG (DBG_info, "gl646_eject_document: no more document to eject\n");
      DBG (DBG_proc, "gl646_eject_document: end\n");
      return status;
    }

  /* there is a document inserted, eject it */
  status = sanei_genesys_write_register (dev, 0x01, 0xb0);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to write register: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* wait for motor to stop */
  do
    {
      usleep (200000UL);
      status = sanei_genesys_get_status (dev, &state);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_eject_document: failed to read status: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }
  while (state & REG41_MOTMFLG);

  /* set up to fast move before scan then move until document is detected */
  regs[0].address = 0x01;
  regs[0].value = 0xb0;

  /* AGOME, 2 slopes motor moving , eject 'backward' */
  regs[1].address = 0x02;
  regs[1].value = 0x5d;

  /* motor feeding steps to 119880 */
  regs[2].address = 0x3d;
  regs[2].value = 1;
  regs[3].address = 0x3e;
  regs[3].value = 0xd4;
  regs[4].address = 0x3f;
  regs[4].value = 0x48;

  /* 60 fast moving steps */
  regs[5].address = 0x6b;
  regs[5].value = 60;

  /* set GPO */
  regs[6].address = 0x66;
  regs[6].value = 0x30;

  /* stesp NO */
  regs[7].address = 0x21;
  regs[7].value = 4;
  regs[8].address = 0x22;
  regs[8].value = 1;
  regs[9].address = 0x23;
  regs[9].value = 1;
  regs[10].address = 0x24;
  regs[10].value = 4;

  /* generate slope table 2 */
  sanei_genesys_generate_slope_table (slope_table,
				      60,
				      61,
				      1600,
				      10000, 1600, 60, 0.25, &used, &vfinal);
/* document eject:
 * send regs
 * start motor
 * wait c1 status to become c8 : HOMESNR and ~MOTFLAG
 */
  status = gl646_send_slope_table (dev, 1, slope_table, 60);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to send slope table 1: %s\n",
	   sane_strstatus (status));
      return status;
    }
  status =
    gl646_bulk_write_register (dev, regs, sizeof (regs) / sizeof (regs[0]));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to bulk write registers: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gl646_start_motor (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to start motor: %s\n",
	   sane_strstatus (status));
      return SANE_STATUS_IO_ERROR;
    }

  /* loop until paper sensor tells paper is out, and till motor is running */
  /* use a 30 timeout */
  count = 0;
  do
    {
      status = sanei_genesys_get_status (dev, &state);
      print_status (state);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_eject_document: failed to read status: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      usleep (200000UL);	/* sleep 200 ms */
      count++;
    }
  while (((state & REG41_HOMESNR) == 0) && (count < 150));

  /* read GPIO on exit */
  status = gl646_gpio_read (dev->dn, &gpio);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_eject_document: failed to read paper sensor %s\n",
	   sane_strstatus (status));
      return status;
    }
  DBG (DBG_info, "gl646_eject_document: GPIO=0x%02x\n", gpio);

  DBG (DBG_proc, "gl646_eject_document: end\n");
  return status;
}

/* Send the low-level scan command */
static SANE_Status
gl646_begin_scan (Genesys_Device * dev, Genesys_Register_Set * reg,
		  SANE_Bool start_motor)
{
  SANE_Status status;
  Genesys_Register_Set local_reg[3];

  DBG (DBG_proc, "gl646_begin_scan\n");

  local_reg[0].address = 0x03;
  local_reg[0].value = sanei_genesys_read_reg_from_set (reg, 0x03);

  local_reg[1].address = 0x01;
  local_reg[1].value = sanei_genesys_read_reg_from_set (reg, 0x01) | REG01_SCAN;	/* set scan bit */

  local_reg[2].address = 0x0f;
  if (start_motor)
    local_reg[2].value = 0x01;
  else
    local_reg[2].value = 0x00;	/* do not start motor yet */

  status = gl646_bulk_write_register (dev, local_reg,
				      sizeof (local_reg) /
				      sizeof (local_reg[0]));

  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_begin_scan: failed to bulk write registers: %s\n",
	   sane_strstatus (status));
      return status;
    }

  DBG (DBG_proc, "gl646_begin_scan: end\n");

  return status;
}


/* Send the stop scan command */
static SANE_Status
end_scan (Genesys_Device * dev, Genesys_Register_Set * reg,
	  SANE_Bool check_stop, SANE_Bool eject)
{
  SANE_Status status = SANE_STATUS_GOOD;
  int i = 0;
  uint8_t val, scanfsh = 0;

  DBG (DBG_proc, "end_scan (check_stop = %d, eject = %d)\n", check_stop,
       eject);

  /* we need to compute scanfsh before cancelling scan */
  if (dev->model->is_sheetfed == SANE_TRUE)
    {
      status = sanei_genesys_get_status (dev, &val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "end_scan: failed to read register: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      if (val & REG41_SCANFSH)
	scanfsh = 1;
      if (DBG_LEVEL > DBG_io2)
	{
	  print_status (val);
	}
    }

  /* ends scan */
  val = sanei_genesys_read_reg_from_set (reg, 0x01);
  val &= ~REG01_SCAN;
  sanei_genesys_set_reg_from_set (reg, 0x01, val);
  status = sanei_genesys_write_register (dev, 0x01, val);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "end_scan: failed to write register 01: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* for sheetfed scanners, we may have to eject document */
  if (dev->model->is_sheetfed == SANE_TRUE)
    {
      if (eject == SANE_TRUE && dev->document == SANE_TRUE)
	{
	  status = gl646_eject_document (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error, "end_scan: failed to eject document\n");
	      return status;
	    }
	}
      if (check_stop)
	{
	  for (i = 0; i < 30; i++)	/* do not wait longer than wait 3 seconds */
	    {
	      status = sanei_genesys_get_status (dev, &val);
	      if (status != SANE_STATUS_GOOD)
		{
		  DBG (DBG_error,
		       "end_scan: failed to read register: %s\n",
		       sane_strstatus (status));
		  return status;
		}
	      if (val & REG41_SCANFSH)
		scanfsh = 1;
	      if (DBG_LEVEL > DBG_io2)
		{
		  print_status (val);
		}

	      if (!(val & REG41_MOTMFLG) && (val & REG41_FEEDFSH) && scanfsh)
		{
		  DBG (DBG_proc, "end_scan: scanfeed finished\n");
		  break;	/* leave for loop */
		}

	      usleep (10000UL);	/* sleep 100 ms */
	    }
	}
    }
  else				/* flat bed scanners */
    {
      if (check_stop)
	{
	  for (i = 0; i < 300; i++)	/* do not wait longer than wait 30 seconds */
	    {
	      status = sanei_genesys_get_status (dev, &val);
	      if (status != SANE_STATUS_GOOD)
		{
		  DBG (DBG_error,
		       "end_scan: failed to read register: %s\n",
		       sane_strstatus (status));
		  return status;
		}
	      if (val & REG41_SCANFSH)
		scanfsh = 1;
	      if (DBG_LEVEL > DBG_io)
		{
		  print_status (val);
		}

	      if (!(val & REG41_MOTMFLG) && (val & REG41_FEEDFSH) && scanfsh)
		{
		  DBG (DBG_proc, "end_scan: scanfeed finished\n");
		  break;	/* leave while loop */
		}

	      if ((!(val & REG41_MOTMFLG)) && (val & REG41_HOMESNR))
		{
		  DBG (DBG_proc, "end_scan: head at home\n");
		  break;	/* leave while loop */
		}

	      usleep (10000UL);	/* sleep 100 ms */
	    }
	}
    }

  DBG (DBG_proc, "end_scan: end (i=%u)\n", i);

  return status;
}

/* Send the stop scan command */
static SANE_Status
gl646_end_scan (Genesys_Device * dev, Genesys_Register_Set * reg,
		SANE_Bool check_stop)
{
  return end_scan (dev, reg, check_stop, SANE_FALSE);
}

/**
 * parks head
 * @param dev scanner's device
 * @param wait_until_home true if the function waits until head parked
 */
GENESYS_STATIC
SANE_Status
gl646_slow_back_home (Genesys_Device * dev, SANE_Bool wait_until_home)
{
  SANE_Status status;
  Genesys_Settings settings;
  uint8_t val;
  int i;
  int loop = 0;

  DBG (DBG_proc, "gl646_slow_back_home: start , wait_until_home = %d\n",
       wait_until_home);

  status = sanei_genesys_get_status (dev, &val);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_slow_back_home: failed to read home sensor: %s\n",
	   sane_strstatus (status));
      return status;
    }
  if (DBG_LEVEL > DBG_io)
    {
      print_status (val);
    }

  dev->scanhead_position_in_steps = 0;

  if (val & REG41_HOMESNR)	/* is sensor at home? */
    {
      DBG (DBG_info, "gl646_slow_back_home: end since already at home\n");
      return SANE_STATUS_GOOD;
    }

  /* stop motor if needed */
  if (val & REG41_MOTMFLG)
    {
      status = gl646_stop_motor (dev);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_slow_back_home: failed to stop motor: %s\n",
	       sane_strstatus (status));
	  return SANE_STATUS_IO_ERROR;
	}
      usleep (200000UL);
    }

  /* when scanhead is moving then wait until scanhead stops or timeout */
  DBG (DBG_info, "gl646_slow_back_home: ensuring that motor is off\n");
  val = REG41_MOTMFLG;
  for (i = 400; i > 0 && (val & REG41_MOTMFLG); i--)	/* do not wait longer than 40 seconds, count down to get i = 0 when busy */
    {
      status = sanei_genesys_get_status (dev, &val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_slow_back_home: Failed to read home sensor & motor status: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      if (((val & (REG41_MOTMFLG | REG41_HOMESNR)) == REG41_HOMESNR))	/* at home and motor is off */
	{
	  DBG (DBG_info,
	       "gl646_slow_back_home: already at home and not moving\n");
	  return SANE_STATUS_GOOD;
	}
      usleep (100 * 1000);	/* sleep 100 ms (todo: fixed to really sleep 100 ms) */
    }

  if (!i)			/* the loop counted down to 0, scanner still is busy */
    {
      DBG (DBG_error,
	   "gl646_slow_back_home: motor is still on: device busy\n");
      return SANE_STATUS_DEVICE_BUSY;
    }

  /* setup for a backward scan of 65535 steps, with no actual data reading */
  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_COLOR;
  settings.xres = get_lowest_resolution (dev->model->ccd_type, SANE_FALSE);
  settings.yres = settings.xres;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels = 600;
  settings.lines = 1;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  status = setup_for_scan (dev, dev->reg, settings, SANE_TRUE, SANE_TRUE, SANE_TRUE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "%s: failed to setup for scan: %s\n", __func__,
	   sane_strstatus (status));
      DBGCOMPLETED;
      return status;
    }

  /* backward , no actual data scanned TODO more setup flags to avoid this register manipulations ? */
  dev->reg[reg_0x02].value |= REG02_MTRREV;
  dev->reg[reg_0x01].value &= ~REG01_SCAN;
  gl646_set_triple_reg (dev->reg, REG_FEEDL, 65535);

  /* sets frontend */
  status = gl646_set_fe (dev, AFE_SET, settings.xres);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "%s: failed to set frontend: %s\n", __func__,
	   sane_strstatus (status));
      DBGCOMPLETED;
      return status;
    }

  /* write scan registers */
  status = gl646_bulk_write_register (dev, dev->reg,
				      sizeof (dev->reg) /
				      sizeof (dev->reg[0]));
  if (status != SANE_STATUS_GOOD)
    DBG (DBG_error,
	 "gl646_slow_back_home: failed to bulk write registers: %s\n",
	 sane_strstatus (status));

  /* registers are restored to an iddl state, give up if no head to park */
  if (dev->model->is_sheetfed == SANE_TRUE)
    {
      DBG (DBG_proc, "gl646_slow_back_home: end \n");
      return SANE_STATUS_GOOD;
    }

  /* starts scan */
  status = gl646_begin_scan (dev, dev->reg, SANE_TRUE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_slow_back_home: failed to begin scan: \n");
      return status;
    }

  /* loop until head parked */
  if (wait_until_home)
    {
      while (loop < 300)		/* do not wait longer then 30 seconds */
	{
	  status = sanei_genesys_get_status (dev, &val);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "gl646_slow_back_home: Failed to read home sensor: %s\n",
		   sane_strstatus (status));
	      return status;
	    }

	  if (val & 0x08)	/* home sensor */
	    {
	      DBG (DBG_info, "gl646_slow_back_home: reached home position\n");
	      DBG (DBG_proc, "gl646_slow_back_home: end\n");
	      usleep (500000);	/* sleep 500 ms before returning */
	      return SANE_STATUS_GOOD;
	    }
	  usleep (100000);	/* sleep 100 ms */
          ++loop;
	}

      /* when we come here then the scanner needed too much time for this, so we better stop the motor */
      gl646_stop_motor (dev);
      end_scan (dev, dev->reg, SANE_TRUE, SANE_FALSE);
      DBG (DBG_error,
	   "gl646_slow_back_home: timeout while waiting for scanhead to go home\n");
      return SANE_STATUS_IO_ERROR;
    }


  DBG (DBG_info, "gl646_slow_back_home: scanhead is still moving\n");
  DBGCOMPLETED;
  return SANE_STATUS_GOOD;
}

/**
 * Automatically set top-left edge of the scan area by scanning an
 * area at 300 dpi from very top of scanner
 * @param dev  device stucture describing the scanner
 * @return SANE_STATUS_GOOD in cas of success, else failure code
 */
static SANE_Status
gl646_search_start_position (Genesys_Device * dev)
{
  SANE_Status status;
  unsigned char *data = NULL;
  Genesys_Settings settings;
  unsigned int resolution, x, y;

  DBG (DBG_proc, "gl646_search_start_position: start\n");

  /* we scan at 300 dpi */
  resolution = get_closest_resolution (dev->model->ccd_type, 300, SANE_FALSE);

  /* fill settings for a gray level scan */
  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_GRAY;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels = 600;
  settings.lines = dev->model->search_lines;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* scan the desired area */
  status =
    simple_scan (dev, settings, SANE_TRUE, SANE_TRUE, SANE_FALSE, &data);

  /* process data if scan is OK */
  if (status == SANE_STATUS_GOOD)
    {
      /* handle stagger case : reorder gray data and thus loose some lines */
      if (dev->current_setup.stagger > 0)
	{
	  DBG (DBG_proc, "gl646_search_start_position: 'un-staggering'\n");
	  for (y = 0; y < settings.lines - dev->current_setup.stagger; y++)
	    {
	      /* one point out of 2 is 'unaligned' */
	      for (x = 0; x < settings.pixels; x += 2)
		{
		  data[y * settings.pixels + x] =
		    data[(y + dev->current_setup.stagger) * settings.pixels +
			 x];
		}
	    }
	  /* correct line number */
	  settings.lines -= dev->current_setup.stagger;
	}
      if (DBG_LEVEL >= DBG_data)
	{
	  sanei_genesys_write_pnm_file ("search_position.pnm",
					data,
					settings.depth,
					1, settings.pixels, settings.lines);
	}
    }
  else
    {
      DBG (DBG_error, "gl646_search_start_position: simple_scan failed\n");
      free (data);
      DBGCOMPLETED;
      return status;
    }

  /* now search reference points on the data */
  status =
    sanei_genesys_search_reference_point (dev, data,
					  dev->sensor.CCD_start_xoffset,
					  resolution, settings.pixels,
					  settings.lines);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_search_start_position: failed to set search reference point: %s\n",
	   sane_strstatus (status));
    }

  free (data);
  DBGCOMPLETED;
  return status;
}

/**
 * internally overriden during effective calibration
 * sets up register for coarse gain calibration
 */
static SANE_Status
gl646_init_regs_for_coarse_calibration (Genesys_Device * dev)
{
  DBG (DBG_proc, "gl646_init_regs_for_coarse_calibration\n");
  DBG (DBG_proc, "gl646_init_register_for_coarse_calibration: end\n");

  /* to make compilers happy ... */
  if (!dev)
    {
      return SANE_STATUS_INVAL;
    }

  return SANE_STATUS_GOOD;
}


/**
 * init registers for shading calibration
 * we assume that scanner's head is on an area suiting shading calibration.
 * We scan a full scan width area by the shading line number for the device
 * at either at full sensor's resolution or half depending upon half_ccd
 * @param dev scanner's device
 * @return SANE_STATUS_GOOD if success, else error code
 */
static SANE_Status
gl646_init_regs_for_shading (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  Genesys_Settings settings;
  /* 1: no half_ccd, 2: use half number of pixels */
  int half_ccd = 1;
  int cksel = 1;

  DBG (DBG_proc, "gl646_init_register_for_shading: start\n");

  /* when shading all (full width) line, we must adapt to half_ccd case */
  if (dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE)
    {
      /* walk the master mode list to find if half_ccd */
      if (is_half_ccd (dev->model->ccd_type, dev->settings.xres, SANE_TRUE) ==
	  SANE_TRUE)
	{
	  half_ccd = 2;
	}
    }

  /* fill settings for scan : always a color scan */
  settings.scan_method = dev->settings.scan_method;
  settings.scan_mode = dev->settings.scan_mode;
  if (dev->model->is_cis == SANE_FALSE)
    {
      settings.scan_mode = SCAN_MODE_COLOR;
    }
  settings.xres = dev->sensor.optical_res / half_ccd;
  cksel = get_cksel (dev->model->ccd_type, dev->settings.xres, SANE_TRUE);
  settings.xres = settings.xres / cksel;
  settings.yres = settings.xres;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * settings.xres) / dev->sensor.optical_res;
  dev->calib_lines = dev->model->shading_lines;
  settings.lines = dev->calib_lines * (3 - half_ccd);
  settings.depth = 16;
  settings.color_filter = dev->settings.color_filter;

  settings.disable_interpolation = dev->settings.disable_interpolation;
  settings.threshold = dev->settings.threshold;
  settings.exposure_time = dev->settings.exposure_time;
  settings.dynamic_lineart = SANE_FALSE;

  /* keep account of the movement for final scan move */
  dev->scanhead_position_in_steps += settings.lines;

  /* we don't want top offset, but we need right margin to be the same
   * than the one for the final scan */
  status = setup_for_scan (dev, dev->reg, settings, SANE_TRUE, SANE_FALSE, SANE_FALSE);

  /* used when sending shading calibration data */
  dev->calib_pixels = settings.pixels;
  dev->calib_channels = dev->current_setup.channels;
  if (dev->model->is_cis == SANE_FALSE)
    {
      dev->calib_channels = 3;
    }

  /* no shading */
  dev->reg[reg_0x01].value &= ~REG01_DVDSET;
  dev->reg[reg_0x02].value |= REG02_ACDCDIS;	/* ease backtracking */
  dev->reg[reg_0x02].value &= ~(REG02_FASTFED | REG02_AGOHOME);
  dev->reg[reg_0x05].value &= ~REG05_GMMENB;
  gl646_set_motor_power (dev->reg, SANE_FALSE);

  /* TODO another flag to setup regs ? */
  /* enforce needed LINCNT, getting rid of extra lines for color reordering */
  if (dev->model->is_cis == SANE_FALSE)
    {
      gl646_set_triple_reg (dev->reg, REG_LINCNT, dev->calib_lines);
    }
  else
    {
      gl646_set_triple_reg (dev->reg, REG_LINCNT, dev->calib_lines * 3);
    }

  /* copy reg to calib_reg */
  memcpy (dev->calib_reg, dev->reg,
	  GENESYS_GL646_MAX_REGS * sizeof (Genesys_Register_Set));

  /* this is an hack to make calibration cache working .... */
  /* if we don't do this, cache will be identified at the shading calibration
   * dpi which is different from calibration one */
  dev->current_setup.xres = dev->settings.xres;
  DBG (DBG_info,
       "gl646_init_register_for_shading:\n\tdev->settings.xres=%d\n\tdev->settings.yres=%d\n",
       dev->settings.xres, dev->settings.yres);

  DBG (DBG_proc, "gl646_init_register_for_shading: end\n");
  return status;
}


/**
 * set up registers for the actual scan. The scan's parameters are given
 * through the device settings. It allocates the scan buffers.
 */
static SANE_Status
gl646_init_regs_for_scan (Genesys_Device * dev)
{
  SANE_Status status;

  DBGSTART;

  /* park head after calibration if needed */
  if (dev->scanhead_position_in_steps > 0
      && dev->settings.scan_method == SCAN_METHOD_FLATBED)
    {
      RIE(gl646_slow_back_home (dev, SANE_TRUE));
      dev->scanhead_position_in_steps = 0;
    }

  RIE(setup_for_scan (dev, dev->reg, dev->settings, SANE_FALSE, SANE_TRUE, SANE_TRUE));

  /* gamma is only enabled at final scan time */
  if (dev->settings.depth < 16)
    dev->reg[reg_0x05].value |= REG05_GMMENB;

  DBGCOMPLETED;
  return status;
}

/**
 * set up registers for the actual scan. The scan's parameters are given
 * through the device settings. It allocates the scan buffers.
 * @param dev scanner's device
 * @param regs     registers to set up
 * @param settings settings of scan
 * @param split SANE_TRUE if move to scan area is split from scan, SANE_FALSE is
 *              scan first moves to area
 * @param xcorrection take x geometry correction into account (fixed and detected offsets)
 * @param ycorrection take y geometry correction into account
 */
GENESYS_STATIC SANE_Status
setup_for_scan (Genesys_Device * dev,
                Genesys_Register_Set *regs,
                Genesys_Settings settings,
                SANE_Bool split,
                SANE_Bool xcorrection,
                SANE_Bool ycorrection)
{
  SANE_Status status = SANE_STATUS_GOOD;
  SANE_Bool color;
  SANE_Int depth;
  int channels;
  uint16_t startx = 0, endx, pixels;
  int move = 0;

  DBGSTART;
  DBG (DBG_info,
       "%s settings:\nResolution: %ux%uDPI\n"
       "Lines     : %u\nPixels    : %u\nStartpos  : %.3f/%.3f\nScan mode : %d\nScan method: %s\n\n",
       __func__,
       settings.xres, settings.yres, settings.lines, settings.pixels,
       settings.tl_x, settings.tl_y, settings.scan_mode,
       settings.scan_method == SCAN_METHOD_FLATBED ? "flatbed" : "XPA");

  if (settings.scan_mode == SCAN_MODE_COLOR)	/* single pass color */
    {
      channels = 3;
      color = SANE_TRUE;
    }
  else
    {
      channels = 1;
      color = SANE_FALSE;
    }

  depth=settings.depth;
  if (settings.scan_mode == SCAN_MODE_LINEART)
    {
      if (settings.dynamic_lineart == SANE_TRUE)
        {
          depth = 8;
        }
      else
        {
          /* XXX STEF XXX : why does the common layer never send depth=1 ? */
          depth = 1;
        }
    }

  /* compute distance to move */
  move = 0;
  /* XXX STEF XXX MD5345 -> optical_ydpi, other base_ydpi => half/full step ? */
  if (split == SANE_FALSE)
    {
      if (dev->model->is_sheetfed == SANE_FALSE)
	{
	  if (ycorrection == SANE_TRUE)
	    {
	      move =
		(SANE_UNFIX (dev->model->y_offset) *
		 dev->motor.optical_ydpi) / MM_PER_INCH;
	    }

	  /* add tl_y to base movement */
	  move += (settings.tl_y * dev->motor.optical_ydpi) / MM_PER_INCH;

	}
      else
	{
	  move += (settings.tl_y * dev->motor.optical_ydpi) / MM_PER_INCH;
	}

      DBG (DBG_info, "%s: move=%d steps\n", __func__, move);

      /* security check */
      if (move < 0)
	{
	  DBG (DBG_error, "%s: overriding negative move value %d\n", __func__, move);
	  move = 0;
	}
    }
  DBG (DBG_info, "%s: move=%d steps\n", __func__, move);

  /* pixels are allways given at full CCD optical resolution */
  /* use detected left margin and fixed value */
  if (xcorrection == SANE_TRUE)
    {
      if (dev->sensor.CCD_start_xoffset > 0)
	startx = dev->sensor.CCD_start_xoffset;
      else
	startx = dev->sensor.dummy_pixel;
      if (settings.scan_method == SCAN_METHOD_FLATBED)
	{
	  startx +=
	    ((SANE_UNFIX (dev->model->x_offset) * dev->sensor.optical_res) /
	     MM_PER_INCH);
	}
      else
	{
	  startx +=
	    ((SANE_UNFIX (dev->model->x_offset_ta) *
	      dev->sensor.optical_res) / MM_PER_INCH);
	}
    }
  else
    {
      /* startx cannot be below dummy pixel value */
      startx = dev->sensor.dummy_pixel;
    }

  /* add x coordinates : expressed in sensor max dpi */
  startx += (settings.tl_x * dev->sensor.optical_res) / MM_PER_INCH;

  /* stagger works with odd start cordinates */
  if (dev->model->flags & GENESYS_FLAG_STAGGERED_LINE)
    startx |= 1;

  pixels = (settings.pixels * dev->sensor.optical_res) / settings.xres;
  /* special requirement for 400 dpi on 1200 dpi sensors */
  if (settings.xres == 400)
    {
      pixels = (pixels / 6) * 6;
    }
  endx = startx + pixels;

  /* TODO check for pixel width overflow */

  /* set up correct values for scan (gamma and shading enabled) */
  status = gl646_setup_registers (dev,
				  regs,
				  settings,
				  dev->slope_table0,
				  dev->slope_table1,
				  settings.xres,
				  move,
				  settings.lines,
				  startx, endx, color,
                                  depth);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "%s: failed setup registers: %s\n", __func__, sane_strstatus (status));
      return status;
    }

  /* now post-process values for register and options fine tuning */

  /* select color filter based on settings */
  regs[reg_0x04].value &= ~REG04_FILTER;
  if (channels == 1)
    {
      switch (settings.color_filter)
	{
	  /* red */
	case 0:
	  regs[reg_0x04].value |= 0x04;
	  break;
	  /* green */
	case 1:
	  regs[reg_0x04].value |= 0x08;
	  break;
	  /* blue */
	case 2:
	  regs[reg_0x04].value |= 0x0c;
	  break;
	default:
	  break;
	}
    }

  /* send computed slope tables */
  status =
    gl646_send_slope_table (dev, 0, dev->slope_table0,
			    sanei_genesys_read_reg_from_set (regs, 0x21));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "%s: failed to send slope table 0: %s\n", __func__, sane_strstatus (status));
      return status;
    }

  status =
    gl646_send_slope_table (dev, 1, dev->slope_table1,
			    sanei_genesys_read_reg_from_set (regs, 0x6b));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "%s: failed to send slope table 1: %s\n", __func__, sane_strstatus (status));
      return status;
    }

  DBGCOMPLETED;
  return status;
}

/**
 * this function sen gamm table to ASIC
 */
static SANE_Status
gl646_send_gamma_table (Genesys_Device * dev)
{
  int size;
  int address;
  SANE_Status status;
  uint8_t *gamma;
  int bits;

  DBGSTART;

  /* gamma table size */
  if (dev->reg[reg_0x05].value & REG05_GMMTYPE)
    {
      size = 16384;
      bits = 14;
    }
  else
    {
      size = 4096;
      bits = 12;
    }

  /* allocate temporary gamma tables: 16 bits words, 3 channels */
  gamma = (uint8_t *) malloc (size * 2 * 3);
  if (gamma==NULL)
    {
      return SANE_STATUS_NO_MEM;
    }

  RIE(sanei_genesys_generate_gamma_buffer(dev, bits, size-1, size, gamma));

  /* table address */
  switch (dev->reg[reg_0x05].value >> 6)
    {
    case 0:			/* 600 dpi */
      address = 0x09000;
      break;
    case 1:			/* 1200 dpi */
      address = 0x11000;
      break;
    case 2:			/* 2400 dpi */
      address = 0x20000;
      break;
    default:
      free (gamma);
      return SANE_STATUS_INVAL;
    }

  /* send address */
  status = sanei_genesys_set_buffer_address (dev, address);
  if (status != SANE_STATUS_GOOD)
    {
      free (gamma);
      DBG (DBG_error,
	   "gl646_send_gamma_table: failed to set buffer address: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* send data */
  status = gl646_bulk_write_data (dev, 0x3c, (uint8_t *) gamma, size * 2 * 3);
  if (status != SANE_STATUS_GOOD)
    {
      free (gamma);
      DBG (DBG_error,
	   "gl646_send_gamma_table: failed to send gamma table: %s\n",
	   sane_strstatus (status));
      return status;
    }
  free (gamma);

  DBGCOMPLETED;
  return SANE_STATUS_GOOD;
}

/** @brief this function does the led calibration.
 * this function does the led calibration by scanning one line of the calibration
 * area below scanner's top on white strip. The scope of this function is
 * currently limited to the XP200
 */
static SANE_Status
gl646_led_calibration (Genesys_Device * dev)
{
  int total_size;
  uint8_t *line;
  unsigned int i, j;
  SANE_Status status = SANE_STATUS_GOOD;
  int val;
  unsigned int channels;
  int avg[3], avga, avge;
  int turn;
  char fn[20];
  uint16_t expr, expg, expb;
  Genesys_Settings settings;
  SANE_Int resolution;

  SANE_Bool acceptable = SANE_FALSE;

  DBG (DBG_proc, "gl646_led_calibration\n");
  if (!dev->model->is_cis)
    {
      DBG (DBG_proc,
	   "gl646_led_calibration: not a cis scanner, nothing to do...\n");
      return SANE_STATUS_GOOD;
    }

  /* get led calibration resolution */
  if (dev->settings.scan_mode == SCAN_MODE_COLOR)
    {
      resolution =
	get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res,
				SANE_TRUE);
      settings.scan_mode = SCAN_MODE_COLOR;
      channels = 3;
    }
  else
    {
      resolution =
	get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res,
				SANE_FALSE);
      settings.scan_mode = SCAN_MODE_GRAY;
      channels = 1;
    }

  /* offset calibration is always done in color mode */
  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res;
  settings.lines = 1;
  settings.depth = 16;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* colors * bytes_per_color * scan lines */
  total_size = settings.pixels * channels * 2 * 1;

  line = malloc (total_size);
  if (!line)
    {
      DBG (DBG_error, "gl646_led_calibration: failed to allocate %d bytes\n",
	   total_size);
      return SANE_STATUS_NO_MEM;
    }

/*
   we try to get equal bright leds here:

   loop:
     average per color
     adjust exposure times

  Sensor_Master uint8_t regs_0x10_0x15[6];
 */

  expr = (dev->sensor.regs_0x10_0x1d[0] << 8) | dev->sensor.regs_0x10_0x1d[1];
  expg = (dev->sensor.regs_0x10_0x1d[2] << 8) | dev->sensor.regs_0x10_0x1d[3];
  expb = (dev->sensor.regs_0x10_0x1d[4] << 8) | dev->sensor.regs_0x10_0x1d[5];

  turn = 0;

  do
    {

      dev->sensor.regs_0x10_0x1d[0] = (expr >> 8) & 0xff;
      dev->sensor.regs_0x10_0x1d[1] = expr & 0xff;
      dev->sensor.regs_0x10_0x1d[2] = (expg >> 8) & 0xff;
      dev->sensor.regs_0x10_0x1d[3] = expg & 0xff;
      dev->sensor.regs_0x10_0x1d[4] = (expb >> 8) & 0xff;
      dev->sensor.regs_0x10_0x1d[5] = expb & 0xff;

      DBG (DBG_info, "gl646_led_calibration: starting first line reading\n");

      status =
	simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line);
      if (status != SANE_STATUS_GOOD)
	{
          free(line);
	  DBG (DBG_error,
	       "gl646_led_calibration: failed to setup scan: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      if (DBG_LEVEL >= DBG_data)
	{
	  snprintf (fn, 20, "led_%02d.pnm", turn);
	  sanei_genesys_write_pnm_file (fn,
					line,
					16, channels, settings.pixels, 1);
	}

      acceptable = SANE_TRUE;

      for (j = 0; j < channels; j++)
	{
	  avg[j] = 0;
	  for (i = 0; i < settings.pixels; i++)
	    {
	      if (dev->model->is_cis)
		val =
		  line[i * 2 + j * 2 * settings.pixels + 1] * 256 +
		  line[i * 2 + j * 2 * settings.pixels];
	      else
		val =
		  line[i * 2 * channels + 2 * j + 1] * 256 +
		  line[i * 2 * channels + 2 * j];
	      avg[j] += val;
	    }

	  avg[j] /= settings.pixels;
	}

      DBG (DBG_info, "gl646_led_calibration: average: "
	   "%d,%d,%d\n", avg[0], avg[1], avg[2]);

      acceptable = SANE_TRUE;

      if (!acceptable)
	{
	  avga = (avg[0] + avg[1] + avg[2]) / 3;
	  expr = (expr * avga) / avg[0];
	  expg = (expg * avga) / avg[1];
	  expb = (expb * avga) / avg[2];

	  /* keep exposure time in a working window */
	  avge = (expr + expg + expb) / 3;
	  if (avge > 0x2000)
	    {
	      expr = (expr * 0x2000) / avge;
	      expg = (expg * 0x2000) / avge;
	      expb = (expb * 0x2000) / avge;
	    }
	  if (avge < 0x400)
	    {
	      expr = (expr * 0x400) / avge;
	      expg = (expg * 0x400) / avge;
	      expb = (expb * 0x400) / avge;
	    }
	}

      turn++;

    }
  while (!acceptable && turn < 100);

  DBG (DBG_info,
       "gl646_led_calibration: acceptable exposure: 0x%04x,0x%04x,0x%04x\n",
       expr, expg, expb);

  /* cleanup before return */
  free (line);

  DBGCOMPLETED;
  return status;
}

/**
 * average dark pixels of a scan
 */
static int
dark_average (uint8_t * data, unsigned int pixels, unsigned int lines,
	      unsigned int channels, unsigned int black)
{
  unsigned int i, j, k, average, count;
  unsigned int avg[3];
  uint8_t val;

  /* computes average value on black margin */
  for (k = 0; k < channels; k++)
    {
      avg[k] = 0;
      count = 0;
      for (i = 0; i < lines; i++)
	{
	  for (j = 0; j < black; j++)
	    {
	      val = data[i * channels * pixels + j + k];
	      avg[k] += val;
	      count++;
	    }
	}
      if (count)
	avg[k] /= count;
      DBG (DBG_info, "dark_average: avg[%d] = %d\n", k, avg[k]);
    }
  average = 0;
  for (i = 0; i < channels; i++)
    average += avg[i];
  average /= channels;
  DBG (DBG_info, "dark_average: average = %d\n", average);
  return average;
}


/** @brief calibration for AD frontend devices
 * we do simple scan until all black_pixels are higher than 0,
 * raising offset at each turn.
 */
static SANE_Status
ad_fe_offset_calibration (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  uint8_t *line;
  unsigned int channels;
  char title[32];
  int pass = 0;
  SANE_Int resolution;
  Genesys_Settings settings;
  unsigned int x, y, adr, min;
  unsigned int bottom, black_pixels;

  DBG (DBG_proc, "ad_fe_offset_calibration: start\n");
  resolution =
    get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res,
			    SANE_TRUE);
  channels = 3;
  black_pixels =
    (dev->sensor.black_pixels * resolution) / dev->sensor.optical_res;
  DBG (DBG_io2, "ad_fe_offset_calibration: black_pixels=%d\n", black_pixels);

  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_COLOR;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res;
  settings.lines = CALIBRATION_LINES;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* scan first line of data with no gain */
  dev->frontend.gain[0] = 0;
  dev->frontend.gain[1] = 0;
  dev->frontend.gain[2] = 0;

  /* scan with no move */
  bottom = 1;
  do
    {
      pass++;
      dev->frontend.offset[0] = bottom;
      dev->frontend.offset[1] = bottom;
      dev->frontend.offset[2] = bottom;
      status =
	simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line);
      if (status != SANE_STATUS_GOOD)
	{
          free(line);
	  DBG (DBG_error,
	       "ad_fe_offset_calibration: failed to scan first line\n");
	  return status;
	}
      if (DBG_LEVEL >= DBG_data)
	{
	  sprintf (title, "offset%03d.pnm", (int)bottom);
	  sanei_genesys_write_pnm_file (title, line, 8, channels,
					settings.pixels, settings.lines);
	}

      min = 0;
      for (y = 0; y < settings.lines; y++)
	{
	  for (x = 0; x < black_pixels; x++)
	    {
	      adr = (x + y * settings.pixels) * channels;
	      if (line[adr] > min)
		min = line[adr];
	      if (line[adr + 1] > min)
		min = line[adr + 1];
	      if (line[adr + 2] > min)
		min = line[adr + 2];
	    }
	}

      free (line);
      DBG (DBG_io2, "ad_fe_offset_calibration: pass=%d, min=%d\n", pass, min);
      bottom++;
    }
  while (pass < 128 && min == 0);
  if (pass == 128)
    {
      DBG (DBG_error,
	   "ad_fe_offset_calibration: failed to find correct offset\n");
      return SANE_STATUS_INVAL;
    }

  DBG (DBG_info, "ad_fe_offset_calibration: offset=(%d,%d,%d)\n",
       dev->frontend.offset[0], dev->frontend.offset[1],
       dev->frontend.offset[2]);
  DBG (DBG_proc, "ad_fe_offset_calibration: end\n");
  return status;
}

#define DARK_TARGET 8
/**
 * This function does the offset calibration by scanning one line of the calibration
 * area below scanner's top. There is a black margin and the remaining is white.
 * genesys_search_start() must have been called so that the offsets and margins
 * are already known.
 * @param dev scanner's device
 * @return SANE_STATUS_GOOD if success, else error code is failure
*/
static SANE_Status
gl646_offset_calibration (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  uint8_t *first_line, *second_line;
  unsigned int channels;
  char title[32];
  int pass = 0, avg;
  SANE_Int resolution;
  Genesys_Settings settings;
  int topavg, bottomavg;
  int top, bottom, black_pixels;

  /* Analog Device fronted have a different calibration */
  if (dev->model->dac_type == DAC_AD_XP200)
    {
      return ad_fe_offset_calibration (dev);
    }

  DBG (DBG_proc, "gl646_offset_calibration: start\n");

  /* setup for a RGB scan, one full sensor's width line */
  /* resolution is the one from the final scan          */
  if (dev->settings.xres > dev->sensor.optical_res)
    {
      resolution =
	get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res,
				SANE_TRUE);
    }
  else
    {
      resolution =
	get_closest_resolution (dev->model->ccd_type, dev->settings.xres,
				SANE_TRUE);
    }
  channels = 3;
  black_pixels =
    (dev->sensor.black_pixels * resolution) / dev->sensor.optical_res;
  DBG (DBG_io2, "gl646_offset_calibration: black_pixels=%d\n", black_pixels);

  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_COLOR;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res;
  settings.lines = CALIBRATION_LINES;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* scan first line of data with no gain, but with offset from
   * last calibration */
  dev->frontend.gain[0] = 0;
  dev->frontend.gain[1] = 0;
  dev->frontend.gain[2] = 0;

  /* scan with no move */
  bottom = 90;
  dev->frontend.offset[0] = bottom;
  dev->frontend.offset[1] = bottom;
  dev->frontend.offset[2] = bottom;
  status =
    simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE,
		 &first_line);
  if (status != SANE_STATUS_GOOD)
    {
      free(first_line);
      DBG (DBG_error,
	   "gl646_offset_calibration: failed to scan first line\n");
      return status;
    }
  if (DBG_LEVEL >= DBG_data)
    {
      sprintf (title, "offset%03d.pnm", bottom);
      sanei_genesys_write_pnm_file (title, first_line, 8, channels,
				    settings.pixels, settings.lines);
    }
  bottomavg =
    dark_average (first_line, settings.pixels, settings.lines, channels,
		  black_pixels);
  free (first_line);
  DBG (DBG_io2, "gl646_offset_calibration: bottom avg=%d\n", bottomavg);

  /* now top value */
  top = 231;
  dev->frontend.offset[0] = top;
  dev->frontend.offset[1] = top;
  dev->frontend.offset[2] = top;
  status =
    simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE,
		 &second_line);
  if (status != SANE_STATUS_GOOD)
    {
      free(second_line);
      DBG (DBG_error,
	   "gl646_offset_calibration: failed to scan first line\n");
      return status;
    }

  if (DBG_LEVEL >= DBG_data)
    {
      sprintf (title, "offset%03d.pnm", top);
      sanei_genesys_write_pnm_file (title, second_line, 8, channels,
				    settings.pixels, settings.lines);
    }
  topavg =
    dark_average (second_line, settings.pixels, settings.lines, channels,
		  black_pixels);
  free (second_line);
  DBG (DBG_io2, "gl646_offset_calibration: top avg=%d\n", topavg);

  /* loop until acceptable level */
  while ((pass < 32) && (top - bottom > 1))
    {
      pass++;

      /* settings for new scan */
      dev->frontend.offset[0] = (top + bottom) / 2;
      dev->frontend.offset[1] = (top + bottom) / 2;
      dev->frontend.offset[2] = (top + bottom) / 2;

      /* scan with no move */
      status =
	simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE,
		     &second_line);
      if (status != SANE_STATUS_GOOD)
	{
          free(second_line);
	  DBG (DBG_error,
	       "gl646_offset_calibration: failed to scan first line\n");
	  return status;
	}

      if (DBG_LEVEL >= DBG_data)
	{
	  sprintf (title, "offset%03d.pnm", dev->frontend.offset[1]);
	  sanei_genesys_write_pnm_file (title, second_line, 8, channels,
					settings.pixels, settings.lines);
	}

      avg =
	dark_average (second_line, settings.pixels, settings.lines, channels,
		      black_pixels);
      DBG (DBG_info, "gl646_offset_calibration: avg=%d offset=%d\n", avg,
	   dev->frontend.offset[1]);
      free (second_line);

      /* compute new boundaries */
      if (topavg == avg)
	{
	  topavg = avg;
	  top = dev->frontend.offset[1];
	}
      else
	{
	  bottomavg = avg;
	  bottom = dev->frontend.offset[1];
	}
    }

  /* in case of debug do a final scan to get result */
  if (DBG_LEVEL >= DBG_data)
    {
      status =
	simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE,
		     &second_line);
      if (status != SANE_STATUS_GOOD)
	{
          free(second_line);
	  DBG (DBG_error,
	       "gl646_offset_calibration: failed to scan final line\n");
	  return status;
	}
      sanei_genesys_write_pnm_file ("offset-final.pnm", second_line, 8,
				    channels, settings.pixels,
				    settings.lines);
      free (second_line);
    }

  DBG (DBG_info, "gl646_offset_calibration: offset=(%d,%d,%d)\n",
       dev->frontend.offset[0], dev->frontend.offset[1],
       dev->frontend.offset[2]);
  DBG (DBG_proc, "gl646_offset_calibration: end\n");
  return status;
}

/** @brief gain calibration for Analog Device frontends
 * Alternative coarse gain calibration
 */
static SANE_Status
ad_fe_coarse_gain_calibration (Genesys_Device * dev, int dpi)
{
  uint8_t *line;
  unsigned int i, channels, val;
  unsigned int size, count, resolution, pass;
  SANE_Status status = SANE_STATUS_GOOD;
  float average;
  Genesys_Settings settings;
  char title[32];

  DBGSTART;

  /* setup for a RGB scan, one full sensor's width line */
  /* resolution is the one from the final scan          */
  resolution = get_closest_resolution (dev->model->ccd_type, dpi, SANE_TRUE);
  channels = 3;
  settings.scan_mode = SCAN_MODE_COLOR;

  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res;
  settings.lines = CALIBRATION_LINES;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  size = channels * settings.pixels * settings.lines;

  /* start gain value */
  dev->frontend.gain[0] = 1;
  dev->frontend.gain[1] = 1;
  dev->frontend.gain[2] = 1;

  average = 0;
  pass = 0;

  /* loop until each channel raises to acceptable level */
  while ((average < dev->sensor.gain_white_ref) && (pass < 30))
    {
      /* scan with no move */
      status =
	simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line);
      if (status != SANE_STATUS_GOOD)
	{
          free(line);
	  DBG (DBG_error,
	       "ad_fe_coarse_gain_calibration: failed to scan first line\n");
	  return status;
	}

      /* log scanning data */
      if (DBG_LEVEL >= DBG_data)
	{
	  sprintf (title, "alternative_coarse%02d.pnm", (int)pass);
	  sanei_genesys_write_pnm_file (title, line, 8,
					channels, settings.pixels,
					settings.lines);
	}
      pass++;

      /* computes white average */
      average = 0;
      count = 0;
      for (i = 0; i < size; i++)
	{
	  val = line[i];
	  average += val;
	  count++;
	}
      average = average / count;

      /* adjusts gain for the channel */
      if (average < dev->sensor.gain_white_ref)
	dev->frontend.gain[0]++;
      dev->frontend.gain[1] = dev->frontend.gain[0];
      dev->frontend.gain[2] = dev->frontend.gain[0];

      DBG (DBG_proc,
	   "ad_fe_coarse_gain_calibration: average = %.2f, gain = %d\n",
	   average, dev->frontend.gain[0]);
      free (line);
    }

  DBG (DBG_info, "ad_fe_coarse_gain_calibration: gains=(%d,%d,%d)\n",
       dev->frontend.gain[0], dev->frontend.gain[1], dev->frontend.gain[2]);
  DBGCOMPLETED;
  return status;
}

/**
 * Alternative coarse gain calibration
 * this on uses the settings from offset_calibration. First scan moves so
 * we can go to calibration area for XPA.
 * @param dev device for scan
 * @param dpi resolutnio to calibrate at
 */
static SANE_Status
gl646_coarse_gain_calibration (Genesys_Device * dev, int dpi)
{
  uint8_t *line;
  unsigned int i, j, k, channels, val, maximum, idx;
  unsigned int count, resolution, pass;
  SANE_Status status = SANE_STATUS_GOOD;
  float average[3];
  Genesys_Settings settings;
  char title[32];

  if (dev->model->ccd_type == CIS_XP200)
    {
      return ad_fe_coarse_gain_calibration (dev, dev->sensor.optical_res);
    }
  DBGSTART;

  /* setup for a RGB scan, one full sensor's width line */
  /* resolution is the one from the final scan          */
  channels = 3;

  /* we are searching a sensor resolution */
  if (dpi > dev->sensor.optical_res)
    {
      resolution = dev->sensor.optical_res;
    }
  else
    {
      resolution =
	get_closest_resolution (dev->model->ccd_type, dev->settings.xres,
				SANE_TRUE);
    }

  settings.scan_method = dev->settings.scan_method;
  settings.scan_mode = SCAN_MODE_COLOR;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_y = 0;
  if (settings.scan_method == SCAN_METHOD_FLATBED)
    {
      settings.tl_x = 0;
      settings.pixels = (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res;
    }
  else
    {
      settings.tl_x = SANE_UNFIX (dev->model->x_offset_ta);
      settings.pixels = (SANE_UNFIX (dev->model->x_size_ta) * resolution) / MM_PER_INCH;
    }
  settings.lines = CALIBRATION_LINES;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* start gain value */
  dev->frontend.gain[0] = 1;
  dev->frontend.gain[1] = 1;
  dev->frontend.gain[2] = 1;

  if (channels > 1)
    {
      average[0] = 0;
      average[1] = 0;
      average[2] = 0;
      idx = 0;
    }
  else
    {
      average[0] = 255;
      average[1] = 255;
      average[2] = 255;
      idx = dev->settings.color_filter;
      average[idx] = 0;
    }
  pass = 0;

  /* loop until each channel raises to acceptable level */
  while (((average[0] < dev->sensor.gain_white_ref)
	  || (average[1] < dev->sensor.gain_white_ref)
	  || (average[2] < dev->sensor.gain_white_ref)) && (pass < 30))
    {
      /* scan with no move */
      status =
	simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line);
      if (status != SANE_STATUS_GOOD)
	{
          free(line);
	  DBG (DBG_error, "%s: failed to scan first line\n", __func__);
	  return status;
	}

      /* log scanning data */
      if (DBG_LEVEL >= DBG_data)
	{
	  sprintf (title, "coarse_gain%02d.pnm", (int)pass);
	  sanei_genesys_write_pnm_file (title, line, 8,
					channels, settings.pixels,
					settings.lines);
	}
      pass++;

      /* average high level for each channel and compute gain
         to reach the target code
         we only use the central half of the CCD data         */
      for (k = idx; k < idx + channels; k++)
	{
	  /* we find the maximum white value, so we can deduce a threshold
	     to average white values */
	  maximum = 0;
	  for (i = 0; i < settings.lines; i++)
	    {
	      for (j = 0; j < settings.pixels; j++)
		{
		  val = line[i * channels * settings.pixels + j + k];
		  if (val > maximum)
		    maximum = val;
		}
	    }

	  /* threshold */
	  maximum *= 0.9;

	  /* computes white average */
	  average[k] = 0;
	  count = 0;
	  for (i = 0; i < settings.lines; i++)
	    {
	      for (j = 0; j < settings.pixels; j++)
		{
		  /* averaging only white points allow us not to care about dark margins */
		  val = line[i * channels * settings.pixels + j + k];
		  if (val > maximum)
		    {
		      average[k] += val;
		      count++;
		    }
		}
	    }
	  average[k] = average[k] / count;

	  /* adjusts gain for the channel */
	  if (average[k] < dev->sensor.gain_white_ref)
	    dev->frontend.gain[k]++;

	  DBG (DBG_proc,
	       "%s: channel %d, average = %.2f, gain = %d\n", __func__,
	       k, average[k], dev->frontend.gain[k]);
	}
      free (line);
    }

  if (channels < 3)
    {
      dev->frontend.gain[1] = dev->frontend.gain[0];
      dev->frontend.gain[2] = dev->frontend.gain[0];
    }

  DBG (DBG_info, "%s: gains=(%d,%d,%d)\n", __func__,
       dev->frontend.gain[0], dev->frontend.gain[1], dev->frontend.gain[2]);
  DBGCOMPLETED;
  return status;
}

/**
 * sets up the scanner's register for warming up. We scan 2 lines without moving.
 *
 */
static SANE_Status
gl646_init_regs_for_warmup (Genesys_Device * dev,
			    Genesys_Register_Set * local_reg,
			    int *channels, int *total_size)
{
  SANE_Status status = SANE_STATUS_GOOD;
  Genesys_Settings settings;
  int resolution, lines;

  DBG (DBG_proc, "gl646_init_regs_for_warmup: start\n");

  sanei_genesys_init_fe (dev);

  resolution = get_closest_resolution (dev->model->ccd_type, 300, SANE_FALSE);

  /* set up for a half width 2 lines gray scan without moving */
  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_GRAY;
  settings.xres = resolution;
  settings.yres = resolution;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res;
  settings.lines = 2;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* setup for scan */
  status = setup_for_scan (dev, dev->reg, settings, SANE_TRUE, SANE_FALSE, SANE_FALSE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_init_regs_for_warmup: setup_for_scan failed (%s)\n",
	   sane_strstatus (status));
      return status;
    }

  /* we are not going to move, so clear these bits */
  dev->reg[reg_0x02].value &= ~(REG02_FASTFED | REG02_AGOHOME);

  /* don't enable any correction for this scan */
  dev->reg[reg_0x01].value &= ~REG01_DVDSET;

  /* copy to local_reg */
  memcpy (local_reg, dev->reg, (GENESYS_GL646_MAX_REGS + 1) * sizeof (Genesys_Register_Set));

  /* turn off motor during this scan */
  gl646_set_motor_power (local_reg, SANE_FALSE);

  /* returned value to higher level warmup function */
  *channels = 1;
  lines = gl646_get_triple_reg (local_reg, REG_LINCNT) + 1;
  *total_size = lines * settings.pixels;

  /* now registers are ok, write them to scanner */
  RIE (gl646_set_fe (dev, AFE_SET, settings.xres));
  RIE (gl646_bulk_write_register (dev, local_reg, GENESYS_GL646_MAX_REGS));

  DBGCOMPLETED;
  return status;
}


/*
 * this function moves head without scanning, forward, then backward
 * so that the head goes to park position.
 * as a by-product, also check for lock
 */
static SANE_Status
gl646_repark_head (Genesys_Device * dev)
{
  SANE_Status status;
  Genesys_Settings settings;
  unsigned int expected, steps;

  DBG (DBG_proc, "gl646_repark_head: start\n");

  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_COLOR;
  settings.xres =
    get_closest_resolution (dev->model->ccd_type, 75, SANE_FALSE);
  settings.yres = settings.xres;
  settings.tl_x = 0;
  settings.tl_y = 5;
  settings.pixels = 600;
  settings.lines = 4;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  status = setup_for_scan (dev, dev->reg, settings, SANE_FALSE, SANE_FALSE, SANE_FALSE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_repark_head: failed to setup for scan: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* TODO seems wrong ... no effective scan */
  dev->reg[reg_0x01].value &= ~REG01_SCAN;

  status = gl646_bulk_write_register (dev, dev->reg, GENESYS_GL646_MAX_REGS);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_repark_head: failed to send registers: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* start scan */
  status = gl646_begin_scan (dev, dev->reg, SANE_TRUE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "gl646_repark_head: failed to begin scan: \n");
      return status;
    }

  expected = gl646_get_triple_reg (dev->reg, REG_FEEDL);
  do
    {
      usleep (100 * 1000);
      status = sanei_genesys_read_feed_steps (dev, &steps);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_repark_head: failed to read feed steps: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }
  while (steps < expected);

  /* toggle motor flag, put an huge step number and redo move backward */
  status = gl646_slow_back_home (dev, 1);
  DBG (DBG_proc, "gl646_repark_head: end\n");
  return status;
}

/* *
 * initialize ASIC : registers, motor tables, and gamma tables
 * then ensure scanner's head is at home
 * @param dev device description of the scanner to initailize
 * @return SANE_STATUS_GOOD if success, error code if failure
 */
static SANE_Status
gl646_init (Genesys_Device * dev)
{
  SANE_Status status;
  struct timeval tv;
  uint8_t cold = 0, val = 0;
  uint32_t addr = 0xdead;
  int size, i;
  size_t len;

  DBG_INIT ();
  DBG (DBG_proc, "gl646_init: start\n");

  /* to detect real power up condition, we write to REG41
   * with pwrbit set, then read it back. When scanner is cold (just replugged)
   * PWRBIT will be set in the returned value
   */
  RIE (sanei_genesys_get_status (dev, &cold));
  DBG (DBG_info, "gl646_init: status=0x%02x\n", cold);
  cold = !(cold & REG41_PWRBIT);
  if (cold)
    {
      DBG (DBG_info, "gl646_init: device is cold\n");
    }
  else
    {
      DBG (DBG_info, "gl646_init: device is hot\n");
    }

  /* if scanning session hasn't been initialized, set it up */
  if (!dev->already_initialized)
    {
      dev->dark_average_data = NULL;
      dev->white_average_data = NULL;

      dev->settings.color_filter = 1;	/* green filter by default */
      gettimeofday (&tv, NULL);
      dev->init_date = tv.tv_sec;

      switch (dev->model->motor_type)
	{
	  /* set to 11111 to spot bugs, sanei_genesys_exposure_time should
	     have obsoleted this field  */
	case MOTOR_5345:
	  dev->settings.exposure_time = 11111;
	  break;

	case MOTOR_ST24:
	  dev->settings.exposure_time = 11000;
	  break;
	default:
	  dev->settings.exposure_time = 11000;
	  break;
	}

      /* Set default values for registers */
      gl646_init_regs (dev);

      /* build default gamma tables */
      if (dev->reg[reg_0x05].value & REG05_GMMTYPE)
	size = 16384;
      else
	size = 4096;

      for(i=0;i<3;i++)
        {
          if (dev->sensor.gamma_table[i] == NULL)
            {
              dev->sensor.gamma_table[i] = (uint16_t *) malloc (2 * size);
              if (dev->sensor.gamma_table[i] == NULL)
                {
                  DBG (DBG_error, "gl646_init: could not allocate memory for gamma table %d\n",i);
                  return SANE_STATUS_NO_MEM;
                }
              sanei_genesys_create_gamma_table (dev->sensor.gamma_table[i],
                                                size,
                                                size - 1,
                                                size - 1,
                                                dev->sensor.gamma[i]);
            }
        }

      /* Init shading data */
      RIE (sanei_genesys_init_shading_data (dev, dev->sensor.sensor_pixels));

      /* initial calibration reg values */
      memcpy (dev->calib_reg, dev->reg,
	      (GENESYS_GL646_MAX_REGS + 1) * sizeof (Genesys_Register_Set));
    }

  /* execute physical unit init only if cold */
  if (cold)
    {
      DBG (DBG_info, "gl646_init: device is cold\n");
      val = 0x04;
      RIE (sanei_usb_control_msg
	   (dev->dn, REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_INIT,
	    INDEX, 1, &val));

      /* ASIC reset */
      RIE (sanei_genesys_write_register (dev, 0x0e, 0x00));
      usleep (100000UL);	/* sleep 100 ms */

      /* Write initial registers */
      RIE (gl646_bulk_write_register (dev, dev->reg, GENESYS_GL646_MAX_REGS));

      /* Test ASIC and RAM */
      if (!(dev->model->flags & GENESYS_FLAG_LAZY_INIT))
	{
	  RIE (gl646_asic_test (dev));
	}

      /* send gamma tables if needed */
      status = gl646_send_gamma_table (dev);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "gl646_init: failed to send generic gamma tables: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      /* Set powersaving (default = 15 minutes) */
      RIE (gl646_set_powersaving (dev, 15));
    }				/* end if cold */

  /* Set analog frontend */
  RIE (gl646_set_fe (dev, AFE_INIT, 0));

  /* GPO enabling for XP200 */
  if (dev->model->ccd_type == CIS_XP200)
    {
      sanei_genesys_write_register (dev, 0x68, dev->gpo.enable[0]);
      sanei_genesys_write_register (dev, 0x69, dev->gpo.enable[1]);

      /* enable GPIO */
      val = 6;
      status = gl646_gpio_output_enable (dev->dn, val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_init: GPO enable failed ... %s\n",
	       sane_strstatus (status));
	}
      val = 0;

      /* writes 0 to GPIO */
      status = gl646_gpio_write (dev->dn, val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_init: GPO write failed ... %s\n",
	       sane_strstatus (status));
	}

      /* clear GPIO enable */
      status = gl646_gpio_output_enable (dev->dn, val);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_init: GPO disable failed ... %s\n",
	       sane_strstatus (status));
	}
      sanei_genesys_write_register (dev, 0x66, 0x10);
      sanei_genesys_write_register (dev, 0x66, 0x00);
      sanei_genesys_write_register (dev, 0x66, 0x10);
    }

  /* MD6471/G2410 and XP200 read/write data from an undocumented memory area which
   * is after the second slope table */
  if (dev->model->gpo_type != GPO_HP3670
      && dev->model->gpo_type != GPO_HP2400)
    {
      switch (dev->sensor.optical_res)
	{
	case 600:
	  addr = 0x08200;
	  break;
	case 1200:
	  addr = 0x10200;
	  break;
	case 2400:
	  addr = 0x1fa00;
	  break;
	}
      status = sanei_genesys_set_buffer_address (dev, addr);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_init: failed to set up control address\n");
	  return SANE_STATUS_INVAL;
	}
      sanei_usb_set_timeout (2 * 1000);
      len = 6;
      status = gl646_bulk_read_data (dev, 0x45, dev->control, len);
      /* for some reason, read fails here for MD6471, HP2300 and XP200
       * one time out of 2 scanimage launches
       */
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_warn, "gl646_init: failed to read control\n");
	  status = gl646_bulk_read_data (dev, 0x45, dev->control, len);
	}
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_warn, "gl646_init: failed to read control\n");
	  return SANE_STATUS_INVAL;
	}
      else
	{
	  DBG (DBG_info,
	       "gl646_init: control read=0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
	       dev->control[0], dev->control[1], dev->control[2],
	       dev->control[3], dev->control[4], dev->control[5]);
	}
      sanei_usb_set_timeout (30 * 1000);
    }
  else
    /* HP2400 and HP3670 case */
    {
      dev->control[0] = 0x00;
      dev->control[1] = 0x00;
      dev->control[2] = 0x01;
      dev->control[3] = 0x00;
      dev->control[4] = 0x00;
      dev->control[5] = 0x00;
    }

  /* ensure head is correctly parked, and check lock */
  if (dev->model->is_sheetfed == SANE_FALSE)
    {
      if (dev->model->flags & GENESYS_FLAG_REPARK)
	{
	  status = gl646_repark_head (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      if (status == SANE_STATUS_INVAL)
		{
		  DBG (DBG_error0,
		       "Your scanner is locked. Please move the lock switch "
		       "to the unlocked position\n");
#ifdef SANE_STATUS_HW_LOCKED
		  return SANE_STATUS_HW_LOCKED;
#else
		  return SANE_STATUS_JAMMED;
#endif
		}
	      else
		DBG (DBG_error,
		     "gl646_init: gl646_repark_head failed: %s\n",
		     sane_strstatus (status));
	      return status;
	    }
	}
      else
	{
	  RIE (gl646_slow_back_home (dev, SANE_TRUE));
	}
    }

  /* here session and device are initialized */
  dev->already_initialized = SANE_TRUE;

  DBG (DBG_proc, "gl646_init: end\n");
  return SANE_STATUS_GOOD;
}

GENESYS_STATIC
SANE_Status
gl646_move_to_ta (Genesys_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;

  DBGSTART;
  if (simple_move (dev, SANE_UNFIX (dev->model->y_offset_calib_ta)) !=
      SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_move_to_ta: failed to move to calibration area\n");
      return status;
    }
  DBGCOMPLETED;
  return status;
}


/**
 * Does a simple scan: ie no line reordering and avanced data buffering and
 * shading correction. Memory for data is allocated in this function
 * and must be freed by caller.
 * @param dev device of the scanner
 * @param settings parameters of the scan
 * @param move SANE_TRUE if moving during scan
 * @param forward SANE_TRUE if moving forward during scan
 * @param shading SANE_TRUE to enable shading correction
 * @param data pointer for the data
 */
GENESYS_STATIC SANE_Status
simple_scan (Genesys_Device * dev, Genesys_Settings settings, SANE_Bool move,
	     SANE_Bool forward, SANE_Bool shading, unsigned char **data)
{
  SANE_Status status = SANE_STATUS_INVAL;
  unsigned int size, lines, x, y, bpp;
  SANE_Bool empty, split;
  unsigned char *buffer;
  int count;
  uint8_t val;

  DBG (DBG_proc, "simple_scan: starting\n");
  DBG (DBG_io, "simple_scan: move=%d, forward=%d, shading=%d\n", move,
       forward, shading);

  /* round up to multiple of 3 in case of CIS scanner */
  if (dev->model->is_cis == SANE_TRUE)
    {
      settings.lines = ((settings.lines + 2) / 3) * 3;
    }

  /* setup for move then scan */
  if (move == SANE_TRUE && settings.tl_y > 0)
    {
      split = SANE_FALSE;
    }
  else
    {
      split = SANE_TRUE;
    }
  status = setup_for_scan (dev, dev->reg, settings, split, SANE_FALSE, SANE_FALSE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "simple_scan: setup_for_scan failed (%s)\n",
	   sane_strstatus (status));
      return status;
    }

  /* allocate memory fo scan : LINCNT may have been adjusted for CCD reordering */
  if (dev->model->is_cis == SANE_TRUE)
    {
      lines = gl646_get_triple_reg (dev->reg, REG_LINCNT) / 3;
    }
  else
    {
      lines = gl646_get_triple_reg (dev->reg, REG_LINCNT) + 1;
    }
  size = lines * settings.pixels;
  if (settings.depth == 16)
    bpp = 2;
  else
    bpp = 1;
  size *= bpp;
  if (settings.scan_mode == SCAN_MODE_COLOR)	/* single pass color */
    size *= 3;
  *data = malloc (size);
  if (!*data)
    {
      DBG (DBG_error,
	   "simple_scan: failed to allocate %d bytes of memory\n", size);
      return SANE_STATUS_NO_MEM;
    }
  DBG (DBG_io, "simple_scan: allocated %d bytes of memory for %d lines\n",
       size, lines);

  /* put back real line number in settings */
  settings.lines = lines;

  /* initialize frontend */
  status = gl646_set_fe (dev, AFE_SET, settings.xres);
  if (status != SANE_STATUS_GOOD)
    {
      free (*data);
      DBG (DBG_error,
	   "simple_scan: failed to set frontend: %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* no shading correction and not watch dog for simple scan */
  dev->reg[reg_0x01].value &= ~(REG01_DVDSET | REG01_DOGENB);
  if (shading == SANE_TRUE)
    {
      dev->reg[reg_0x01].value |= REG01_DVDSET;
    }

  /* enable gamma table for the scan */
  dev->reg[reg_0x05].value |= REG05_GMMENB;

  /* one table movement for simple scan */
  dev->reg[reg_0x02].value &= ~REG02_FASTFED;

  if (move == SANE_FALSE)
    {
      /* clear motor power flag if no move */
      dev->reg[reg_0x02].value &= ~REG02_MTRPWR;

      /* no automatic go home if no movement */
      dev->reg[reg_0x02].value &= ~REG02_AGOHOME;
    }
  if (forward == SANE_FALSE)
    {
      dev->reg[reg_0x02].value |= REG02_MTRREV;
    }
  else
    {
      dev->reg[reg_0x02].value &= ~REG02_MTRREV;
    }

  /* no automatic go home when using XPA */
  if (settings.scan_method == SCAN_METHOD_TRANSPARENCY)
    {
      dev->reg[reg_0x02].value &= ~REG02_AGOHOME;
    }

  /* write scan registers */
  status = gl646_bulk_write_register (dev, dev->reg,
				      sizeof (dev->reg) /
				      sizeof (dev->reg[0]));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "simple_scan: failed to bulk write registers: %s\n",
	   sane_strstatus (status));
      free (*data);
      return status;
    }

  /* starts scan */
  status = gl646_begin_scan (dev, dev->reg, move);
  if (status != SANE_STATUS_GOOD)
    {
      free (*data);
      DBG (DBG_error, "simple_scan: failed to begin scan: \n");
      return status;
    }

  /* wait for buffers to be filled */
  count = 0;
  do
    {
      usleep (10000UL);
      RIE (sanei_genesys_get_status (dev, &val));
      if (DBG_LEVEL > DBG_info)
	{
	  print_status (val);
	}
      RIE (sanei_genesys_test_buffer_empty (dev, &empty));
      count++;
    }
  while (empty && count < 1000);
  if (count == 1000)
    {
      free (*data);
      DBG (DBG_error, "simple_scan: failed toread data\n");
      return SANE_STATUS_IO_ERROR;
    }

  /* now we're on target, we can read data */
  status = sanei_genesys_read_data_from_scanner (dev, *data, size);
  if (status != SANE_STATUS_GOOD)
    {
      free (*data);
      DBG (DBG_error,
	   "simple_scan: failed to read data: %s\n", sane_strstatus (status));
      return status;
    }

  /* in case of CIS scanner, we must reorder data */
  if (dev->model->is_cis == SANE_TRUE
      && settings.scan_mode == SCAN_MODE_COLOR)
    {
      /* alloc one line sized working buffer */
      buffer = (unsigned char *) malloc (settings.pixels * 3 * bpp);
      if (buffer == NULL)
	{
	  DBG (DBG_error,
	       "simple_scan: failed to allocate %d bytes of memory\n",
	       settings.pixels * 3);
	  return SANE_STATUS_NO_MEM;
	}

      /* reorder one line of data and put it back to buffer */
      if (bpp == 1)
	{
	  for (y = 0; y < lines; y++)
	    {
	      /* reorder line */
	      for (x = 0; x < settings.pixels; x++)
		{
		  buffer[x * 3] = (*data)[y * settings.pixels * 3 + x];
		  buffer[x * 3 + 1] =
		    (*data)[y * settings.pixels * 3 + settings.pixels + x];
		  buffer[x * 3 + 2] =
		    (*data)[y * settings.pixels * 3 + 2 * settings.pixels +
			    x];
		}
	      /* copy line back */
	      memcpy ((*data) + settings.pixels * 3 * y, buffer,
		      settings.pixels * 3);
	    }
	}
      else
	{
	  for (y = 0; y < lines; y++)
	    {
	      /* reorder line */
	      for (x = 0; x < settings.pixels; x++)
		{
		  buffer[x * 6] = (*data)[y * settings.pixels * 6 + x * 2];
		  buffer[x * 6 + 1] =
		    (*data)[y * settings.pixels * 6 + x * 2 + 1];
		  buffer[x * 6 + 2] =
		    (*data)[y * settings.pixels * 6 + 2 * settings.pixels +
			    x * 2];
		  buffer[x * 6 + 3] =
		    (*data)[y * settings.pixels * 6 + 2 * settings.pixels +
			    x * 2 + 1];
		  buffer[x * 6 + 4] =
		    (*data)[y * settings.pixels * 6 + 4 * settings.pixels +
			    x * 2];
		  buffer[x * 6 + 5] =
		    (*data)[y * settings.pixels * 6 + 4 * settings.pixels +
			    x * 2 + 1];
		}
	      /* copy line back */
	      memcpy ((*data) + settings.pixels * 6 * y, buffer,
		      settings.pixels * 6);
	    }
	}
      free (buffer);
    }

  /* end scan , waiting the motor to stop if needed (if moving), but without ejecting doc */
  status = end_scan (dev, dev->reg, SANE_TRUE, SANE_FALSE);
  if (status != SANE_STATUS_GOOD)
    {
      free (*data);
      DBG (DBG_error,
	   "simple_scan: failed to end scan: %s\n", sane_strstatus (status));
      return status;
    }

  DBG (DBG_proc, "simple_scan: end\n");
  return status;
}

/**
 * Does a simple move of the given distance by doing a scan at lowest resolution
 * shading correction. Memory for data is allocated in this function
 * and must be freed by caller.
 * @param dev device of the scanner
 * @param distance distance to move in MM
 */
#ifndef UNIT_TESTING
static
#endif
  SANE_Status
simple_move (Genesys_Device * dev, SANE_Int distance)
{
  SANE_Status status;
  unsigned char *data = NULL;
  Genesys_Settings settings;

  DBG (DBG_proc, "simple_move: %d mm\n", distance);

  /* TODO give a no AGOHOME flag */
  settings.scan_method = SCAN_METHOD_TRANSPARENCY;
  settings.scan_mode = SCAN_MODE_COLOR;
  settings.xres = get_lowest_resolution (dev->model->ccd_type, SANE_TRUE);
  settings.yres = settings.xres;
  settings.tl_y = 0;
  settings.tl_x = 0;
  settings.pixels =
    (dev->sensor.sensor_pixels * settings.xres) / dev->sensor.optical_res;
  settings.lines = (distance * settings.xres) / MM_PER_INCH;
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  status = simple_scan (dev, settings, SANE_TRUE, SANE_TRUE, SANE_FALSE, &data);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "simple_move: simple_scan failed\n");
    }

  free (data);
  DBGCOMPLETED
  return status;
}

/**
 * update the status of the required sensor in the scanner session
 * the last_val fileds are used to make events 'sticky'
 */
static SANE_Status
gl646_update_hardware_sensors (Genesys_Scanner * session)
{
  Genesys_Device *dev = session->dev;
  uint8_t value;
  SANE_Status status;

  /* do what is needed to get a new set of events, but try to not loose
     any of them.
   */
  status = gl646_gpio_read (dev->dn, &value);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "gl646_update_hardware_sensors: failed to read GPIO %s\n",
	   sane_strstatus (status));
      return status;
    }
  DBG (DBG_io, "gl646_update_hardware_sensors: GPIO=0x%02x\n", value);

  /* scan button */
  if ((dev->model->buttons & GENESYS_HAS_SCAN_SW)
      && session->val[OPT_SCAN_SW].b == session->last_val[OPT_SCAN_SW].b)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_XP200:
	  session->val[OPT_SCAN_SW].b = ((value & 0x02) != 0);
	  break;
	case GPO_5345:
	  session->val[OPT_SCAN_SW].b = (value == 0x16);
	  break;
	case GPO_HP2300:
	  session->val[OPT_SCAN_SW].b = (value == 0x6c);
	  break;
	case GPO_HP3670:
	case GPO_HP2400:
	  session->val[OPT_SCAN_SW].b = ((value & 0x20) == 0);
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  /* email button */
  if ((dev->model->buttons & GENESYS_HAS_EMAIL_SW)
      && session->val[OPT_EMAIL_SW].b == session->last_val[OPT_EMAIL_SW].b)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_5345:
	  session->val[OPT_EMAIL_SW].b = (value == 0x12);
	  break;
	case GPO_HP3670:
	case GPO_HP2400:
	  session->val[OPT_EMAIL_SW].b = ((value & 0x08) == 0);
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  /* copy button */
  if ((dev->model->buttons & GENESYS_HAS_COPY_SW)
      && session->val[OPT_COPY_SW].b == session->last_val[OPT_COPY_SW].b)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_5345:
	  session->val[OPT_COPY_SW].b = (value == 0x11);
	  break;
	case GPO_HP2300:
	  session->val[OPT_COPY_SW].b = (value == 0x5c);
	  break;
	case GPO_HP3670:
	case GPO_HP2400:
	  session->val[OPT_COPY_SW].b = ((value & 0x10) == 0);
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  /* power button */
  if ((dev->model->buttons & GENESYS_HAS_POWER_SW)
      && session->val[OPT_POWER_SW].b == session->last_val[OPT_POWER_SW].b)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_5345:
	  session->val[OPT_POWER_SW].b = (value == 0x14);
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  /* ocr button */
  if ((dev->model->buttons & GENESYS_HAS_OCR_SW)
      && session->val[OPT_OCR_SW].b == session->last_val[OPT_OCR_SW].b)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_5345:
	  session->val[OPT_OCR_SW].b = (value == 0x13);
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  /* document detection */
  if ((dev->model->buttons & GENESYS_HAS_PAGE_LOADED_SW)
      && session->val[OPT_PAGE_LOADED_SW].b ==
      session->last_val[OPT_PAGE_LOADED_SW].b)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_XP200:
	  session->val[OPT_PAGE_LOADED_SW].b = ((value & 0x04) != 0);
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  /* XPA detection */
  if (dev->model->flags & GENESYS_FLAG_XPA)
    {
      switch (dev->model->gpo_type)
	{
	case GPO_HP3670:
	case GPO_HP2400:
	  /* test if XPA is plugged-in */
	  if ((value & 0x40) == 0)
	    {
	      DBG (DBG_io, "gl646_update_hardware_sensors: enabling XPA\n");
	      session->opt[OPT_SOURCE].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      DBG (DBG_io, "gl646_update_hardware_sensors: disabling XPA\n");
	      session->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
	    }
	  break;
	default:
	  return SANE_STATUS_UNSUPPORTED;
	}
    }

  return status;
}


static SANE_Status
write_control (Genesys_Device * dev, int resolution)
{
  SANE_Status status;
  uint8_t control[4];
  uint32_t addr = 0xdead;

  /* 2300 does not write to 'control' */
  if (dev->model->motor_type == MOTOR_HP2300)
    return SANE_STATUS_GOOD;

  /* MD6471/G2410/HP2300 and XP200 read/write data from an undocumented memory area which
   * is after the second slope table */
  switch (dev->sensor.optical_res)
    {
    case 600:
      addr = 0x08200;
      break;
    case 1200:
      addr = 0x10200;
      break;
    case 2400:
      addr = 0x1fa00;
      break;
    default:
      DBG (DBG_error, "write_control: failed to compute control address\n");
      return SANE_STATUS_INVAL;
    }

  /* XP200 sets dpi, what other scanner put is unknown yet */
  switch (dev->model->motor_type)
    {
    case MOTOR_XP200:
      /* we put scan's dpi, not motor one */
      control[0] = LOBYTE (resolution);
      control[1] = HIBYTE (resolution);
      control[2] = dev->control[4];
      control[3] = dev->control[5];
      break;
    case MOTOR_HP3670:
    case MOTOR_HP2400:
    case MOTOR_5345:
    default:
      control[0] = dev->control[2];
      control[1] = dev->control[3];
      control[2] = dev->control[4];
      control[3] = dev->control[5];
      break;
    }

  DBG (DBG_info,
       "write_control: control write=0x%02x 0x%02x 0x%02x 0x%02x\n",
       control[0], control[1], control[2], control[3]);
  status = sanei_genesys_set_buffer_address (dev, addr);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "write_control: failed to set up control address\n");
      return SANE_STATUS_INVAL;
    }
  status = gl646_bulk_write_data (dev, 0x3c, control, 4);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "write_control: failed to set up control\n");
      return SANE_STATUS_INVAL;
    }
  return status;
}

/**
 * check if a stored calibration is compatible with requested scan.
 * @return SANE_STATUS_GOOD if compatible, SANE_STATUS_UNSUPPORTED if not.
 * Whenever an error is met, it is returned.
 * @param dev scanner device
 * @param cache cache entry to test
 * @param for_overwrite reserved for future use ...
 */
static SANE_Status
gl646_is_compatible_calibration (Genesys_Device * dev,
				 Genesys_Calibration_Cache * cache,
				 int for_overwrite)
{
#ifdef HAVE_SYS_TIME_H
  struct timeval time;
#endif
  int compatible = 1;

  DBG (DBG_proc,
       "gl646_is_compatible_calibration: start (for_overwrite=%d)\n",
       for_overwrite);

  if (cache == NULL)
    return SANE_STATUS_UNSUPPORTED;

  /* build minimal current_setup for calibration cache use only, it will be better
   * computed when during setup for scan
   */
  if (dev->settings.scan_mode == SCAN_MODE_COLOR)
    {
      dev->current_setup.channels = 3;
    }
  else
    {
      dev->current_setup.channels = 1;
    }
  dev->current_setup.xres = dev->settings.xres;
  dev->current_setup.scan_method = dev->settings.scan_method;

  DBG (DBG_io,
       "gl646_is_compatible_calibration: requested=(%d,%f), tested=(%d,%f)\n",
       dev->current_setup.channels, dev->current_setup.xres,
       cache->used_setup.channels, cache->used_setup.xres);

  /* a calibration cache is compatible if color mode and x dpi match the user
   * requested scan. In the case of CIS scanners, dpi isn't a criteria */
  if (dev->model->is_cis == SANE_FALSE)
    {
      compatible =
	((dev->current_setup.channels == cache->used_setup.channels)
	 && (((int) dev->current_setup.xres) ==
	     ((int) cache->used_setup.xres)));
    }
  else
    {
      compatible =
	(dev->current_setup.channels == cache->used_setup.channels);
    }
  if (dev->current_setup.scan_method != cache->used_setup.scan_method)
    {
      DBG (DBG_io,
	   "gl646_is_compatible_calibration: current method=%d, used=%d\n",
	   dev->current_setup.scan_method, cache->used_setup.scan_method);
      compatible = 0;
    }
  if (!compatible)
    {
      DBG (DBG_proc,
	   "gl646_is_compatible_calibration: completed, non compatible cache\n");
      return SANE_STATUS_UNSUPPORTED;
    }

  /* a cache entry expires after 30 minutes for non sheetfed scanners */
  /* this is not taken into account when overwriting cache entries    */
#ifdef HAVE_SYS_TIME_H
  if(for_overwrite == SANE_FALSE)
    {
      gettimeofday (&time, NULL);
      if ((time.tv_sec - cache->last_calibration > 30 * 60)
          && (dev->model->is_sheetfed == SANE_FALSE))
        {
          DBG (DBG_proc,
               "gl646_is_compatible_calibration: expired entry, non compatible cache\n");
          return SANE_STATUS_UNSUPPORTED;
        }
    }
#endif

  DBG (DBG_proc,
       "gl646_is_compatible_calibration: completed, cache compatible\n");
  return SANE_STATUS_GOOD;
}

/**
 * search for a full width black or white strip.
 * @param dev scanner device
 * @param forward SANE_TRUE if searching forward, SANE_FALSE if searching backward
 * @param black SANE_TRUE if searching for a black strip, SANE_FALSE for a white strip
 * @return SANE_STATUS_GOOD if a matching strip is found, SANE_STATUS_UNSUPPORTED if not
 */
static SANE_Status
gl646_search_strip (Genesys_Device * dev, SANE_Bool forward, SANE_Bool black)
{
  SANE_Status status = SANE_STATUS_GOOD;
  SANE_Bool half_ccd = SANE_FALSE;
  Genesys_Settings settings;
  int res = get_closest_resolution (dev->model->ccd_type, 75, SANE_FALSE);
  unsigned char *data = NULL;
  unsigned int pass, count, found, x, y;
  char title[80];

  DBG (DBG_proc, "gl646_search_strip: start\n");
  /* adapt to half_ccd case */
  if (dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE)
    {
      /* walk the master mode list to find if half_ccd */
      if (is_half_ccd (dev->model->ccd_type, res, SANE_TRUE) == SANE_TRUE)
	{
	  half_ccd = SANE_TRUE;
	}
    }

  /* we set up for a lowest available resolution color grey scan, full width */
  settings.scan_method = SCAN_METHOD_FLATBED;
  settings.scan_mode = SCAN_MODE_GRAY;
  settings.xres = res;
  settings.yres = res;
  settings.tl_x = 0;
  settings.tl_y = 0;
  settings.pixels = (SANE_UNFIX (dev->model->x_size) * res) / MM_PER_INCH;
  if (half_ccd == SANE_TRUE)
    {
      settings.pixels /= 2;
    }

  /* 15 mm at at time */
  settings.lines = (15 * settings.yres) / MM_PER_INCH;	/* may become a parameter from genesys_devices.c */
  settings.depth = 8;
  settings.color_filter = 0;

  settings.disable_interpolation = 0;
  settings.threshold = 0;
  settings.exposure_time = 0;
  settings.dynamic_lineart = SANE_FALSE;

  /* signals if a strip of the given color has been found */
  found = 0;

  /* detection pass done */
  pass = 0;

  /* loop until strip is found or maximum pass number done */
  while (pass < 20 && !found)
    {
      /* scan a full width strip */
      status =
	simple_scan (dev, settings, SANE_TRUE, forward, SANE_FALSE, &data);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "gl646_search_strip: simple_scan failed\n");
	  free (data);
	  return status;
	}
      if (DBG_LEVEL >= DBG_data)
	{
	  sprintf (title, "search_strip_%s%02d.pnm", forward ? "fwd" : "bwd",
		   (int)pass);
	  sanei_genesys_write_pnm_file (title, data, settings.depth, 1,
					settings.pixels, settings.lines);
	}

      /* search data to find black strip */
      /* when searching forward, we only need one line of the searched color since we
       * will scan forward. But when doing backward search, we need all the area of the
       * same color */
      if (forward)
	{
	  for (y = 0; y < settings.lines && !found; y++)
	    {
	      count = 0;
	      /* count of white/black pixels depending on the color searched */
	      for (x = 0; x < settings.pixels; x++)
		{
		  /* when searching for black, detect white pixels */
		  if (black && data[y * settings.pixels + x] > 90)
		    {
		      count++;
		    }
		  /* when searching for white, detect black pixels */
		  if (!black && data[y * settings.pixels + x] < 60)
		    {
		      count++;
		    }
		}

	      /* at end of line, if count >= 3%, line is not fully of the desired color
	       * so we must go to next line of the buffer */
	      /* count*100/pixels < 3 */
	      if ((count * 100) / settings.pixels < 3)
		{
		  found = 1;
		  DBG (DBG_data,
		       "gl646_search_strip: strip found forward during pass %d at line %d\n",
		       pass, y);
		}
	      else
		{
		  DBG (DBG_data, "gl646_search_strip: pixels=%d, count=%d\n",
		       settings.pixels, count);
		}
	    }
	}
      else			/* since calibration scans are done forward, we need the whole area
				   to be of the required color when searching backward */
	{
	  count = 0;
	  for (y = 0; y < settings.lines; y++)
	    {
	      /* count of white/black pixels depending on the color searched */
	      for (x = 0; x < settings.pixels; x++)
		{
		  /* when searching for black, detect white pixels */
		  if (black && data[y * settings.pixels + x] > 60)
		    {
		      count++;
		    }
		  /* when searching for white, detect black pixels */
		  if (!black && data[y * settings.pixels + x] < 60)
		    {
		      count++;
		    }
		}
	    }

	  /* at end of area, if count >= 3%, area is not fully of the desired color
	   * so we must go to next buffer */
	  if ((count * 100) / (settings.pixels * settings.lines) < 3)
	    {
	      found = 1;
	      DBG (DBG_data,
		   "gl646_search_strip: strip found backward during pass %d \n",
		   pass);
	    }
	  else
	    {
	      DBG (DBG_data, "gl646_search_strip: pixels=%d, count=%d\n",
		   settings.pixels, count);
	    }
	}
      pass++;
    }
  free (data);
  if (found)
    {
      status = SANE_STATUS_GOOD;
      DBG (DBG_info, "gl646_search_strip: strip found\n");
    }
  else
    {
      status = SANE_STATUS_UNSUPPORTED;
      DBG (DBG_info, "gl646_search_strip: strip not found\n");
    }
  return status;
}

/** the gl646 command set */
static Genesys_Command_Set gl646_cmd_set = {
  "gl646-generic",		/* the name of this set */

  gl646_init,
  gl646_init_regs_for_warmup,
  gl646_init_regs_for_coarse_calibration,
  gl646_init_regs_for_shading,
  gl646_init_regs_for_scan,

  gl646_get_filter_bit,
  gl646_get_lineart_bit,
  gl646_get_bitset_bit,
  gl646_get_gain4_bit,
  gl646_get_fast_feed_bit,
  gl646_test_buffer_empty_bit,
  gl646_test_motor_flag_bit,

  gl646_bulk_full_size,

  gl646_public_set_fe,
  gl646_set_powersaving,
  gl646_save_power,
  gl646_set_motor_power,
  gl646_set_lamp_power,

  gl646_begin_scan,
  gl646_end_scan,

  gl646_send_gamma_table,

  gl646_search_start_position,

  gl646_offset_calibration,
  gl646_coarse_gain_calibration,
  gl646_led_calibration,

  gl646_slow_back_home,
  NULL,

  gl646_bulk_write_register,
  gl646_bulk_write_data,
  gl646_bulk_read_data,

  gl646_update_hardware_sensors,

  /* sheetfed related functions */
  gl646_load_document,
  gl646_detect_document_end,
  gl646_eject_document,
  gl646_search_strip,

  gl646_is_compatible_calibration,
  gl646_move_to_ta,
  NULL,
  NULL,
  NULL,
  NULL
};

SANE_Status
sanei_gl646_init_cmd_set (Genesys_Device * dev)
{
  dev->model->cmd_set = &gl646_cmd_set;
  return SANE_STATUS_GOOD;
}

/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */
