/* sane - Scanner Access Now Easy.

   Copyright (C) 2002 Sergey Vlasov <vsu@altlinux.ru>
   AFE offset/gain setting by David Stevenson <david.stevenson@zoom.co.uk>
   Copyright (C) 2002 - 2007 Henning Geinitz <sane@geinitz.org>
   Copyright (C) 2009 Stéphane Voltz <stef.dev@free.fr> for sheetfed
                      calibration code.

   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, see <https://www.gnu.org/licenses/>.

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

#include "gt68xx_high.h"
#include "gt68xx_mid.c"

#include <unistd.h>
#include <math.h>

static SANE_Status
gt68xx_afe_ccd_auto (GT68xx_Scanner * scanner, GT68xx_Scan_Request * request);
static SANE_Status gt68xx_afe_cis_auto (GT68xx_Scanner * scanner);

SANE_Status
gt68xx_calibrator_new (SANE_Int width,
		       SANE_Int white_level, GT68xx_Calibrator ** cal_return)
{
  GT68xx_Calibrator *cal;
  SANE_Int i;

  DBG (4, "gt68xx_calibrator_new: enter: width=%d, white_level=%d\n",
       width, white_level);

  *cal_return = 0;

  if (width <= 0)
    {
      DBG (5, "gt68xx_calibrator_new: invalid width=%d\n", width);
      return SANE_STATUS_INVAL;
    }

  cal = (GT68xx_Calibrator *) malloc (sizeof (GT68xx_Calibrator));
  if (!cal)
    {
      DBG (5, "gt68xx_calibrator_new: no memory for GT68xx_Calibrator\n");
      return SANE_STATUS_NO_MEM;
    }

  cal->k_white = NULL;
  cal->k_black = NULL;
  cal->white_line = NULL;
  cal->black_line = NULL;
  cal->width = width;
  cal->white_level = white_level;
  cal->white_count = 0;
  cal->black_count = 0;
#ifdef TUNE_CALIBRATOR
  cal->min_clip_count = cal->max_clip_count = 0;
#endif /* TUNE_CALIBRATOR */

  cal->k_white = (unsigned int *) malloc (width * sizeof (unsigned int));
  cal->k_black = (unsigned int *) malloc (width * sizeof (unsigned int));
  cal->white_line = (double *) malloc (width * sizeof (double));
  cal->black_line = (double *) malloc (width * sizeof (double));

  if (!cal->k_white || !cal->k_black || !cal->white_line || !cal->black_line)
    {
      DBG (5, "gt68xx_calibrator_new: no memory for calibration data\n");
      gt68xx_calibrator_free (cal);
      return SANE_STATUS_NO_MEM;
    }

  for (i = 0; i < width; ++i)
    {
      cal->k_white[i] = 0;
      cal->k_black[i] = 0;
      cal->white_line[i] = 0.0;
      cal->black_line[i] = 0.0;
    }

  *cal_return = cal;
  DBG (5, "gt68xx_calibrator_new: leave: ok\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_free (GT68xx_Calibrator * cal)
{
  DBG (5, "gt68xx_calibrator_free: enter\n");

  if (!cal)
    {
      DBG (5, "gt68xx_calibrator_free: cal==NULL\n");
      return SANE_STATUS_INVAL;
    }

#ifdef TUNE_CALIBRATOR
  DBG (4, "gt68xx_calibrator_free: min_clip_count=%d, max_clip_count=%d\n",
       cal->min_clip_count, cal->max_clip_count);
#endif /* TUNE_CALIBRATOR */

  if (cal->k_white)
    {
      free (cal->k_white);
      cal->k_white = NULL;
    }

  if (cal->k_black)
    {
      free (cal->k_black);
      cal->k_black = NULL;
    }

  if (cal->white_line)
    {
      free (cal->white_line);
      cal->white_line = NULL;
    }

  if (cal->black_line)
    {
      free (cal->black_line);
      cal->black_line = NULL;
    }

  free (cal);

  DBG (5, "gt68xx_calibrator_free: leave: ok\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_add_white_line (GT68xx_Calibrator * cal, unsigned int *line)
{
  SANE_Int i;
  SANE_Int width = cal->width;

  SANE_Int sum = 0;

  cal->white_count++;

  for (i = 0; i < width; ++i)
    {
      cal->white_line[i] += line[i];
      sum += line[i];
#ifdef SAVE_WHITE_CALIBRATION
      printf ("%c", line[i] >> 8);
#endif
    }
  if (sum / width / 256 < 0x50)
    DBG (1,
	 "gt68xx_calibrator_add_white_line: WARNING: dark calibration line: "
	 "%2d medium white: 0x%02x\n", cal->white_count - 1,
	 sum / width / 256);
  else
    DBG (5,
	 "gt68xx_calibrator_add_white_line: line: %2d medium white: 0x%02x\n",
	 cal->white_count - 1, sum / width / 256);

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_eval_white (GT68xx_Calibrator * cal, double factor)
{
  SANE_Int i;
  SANE_Int width = cal->width;

  for (i = 0; i < width; ++i)
    {
      cal->white_line[i] = cal->white_line[i] / cal->white_count * factor;
    }

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_add_black_line (GT68xx_Calibrator * cal, unsigned int *line)
{
  SANE_Int i;
  SANE_Int width = cal->width;

  SANE_Int sum = 0;

  cal->black_count++;

  for (i = 0; i < width; ++i)
    {
      cal->black_line[i] += line[i];
      sum += line[i];
#ifdef SAVE_BLACK_CALIBRATION
      printf ("%c", line[i] >> 8);
#endif
    }

  DBG (5,
       "gt68xx_calibrator_add_black_line: line: %2d medium black: 0x%02x\n",
       cal->black_count - 1, sum / width / 256);
  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_eval_black (GT68xx_Calibrator * cal, double factor)
{
  SANE_Int i;
  SANE_Int width = cal->width;

  for (i = 0; i < width; ++i)
    {
      cal->black_line[i] = cal->black_line[i] / cal->black_count - factor;
    }

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_finish_setup (GT68xx_Calibrator * cal)
{
#ifdef TUNE_CALIBRATOR
  double ave_black = 0.0;
  double ave_diff = 0.0;
#endif /* TUNE_CALIBRATOR */
  int i;
  int width = cal->width;
  unsigned int max_value = 65535;

  for (i = 0; i < width; ++i)
    {
      unsigned int white = cal->white_line[i];
      unsigned int black = cal->black_line[i];
      unsigned int diff = (white > black) ? white - black : 1;
      if (diff > max_value)
	diff = max_value;
      cal->k_white[i] = diff;
      cal->k_black[i] = black;
#ifdef TUNE_CALIBRATOR
      ave_black += black;
      ave_diff += diff;
#endif /* TUNE_CALIBRATOR */
    }

#ifdef TUNE_CALIBRATOR
  ave_black /= width;
  ave_diff /= width;
  DBG (4, "gt68xx_calibrator_finish_setup: ave_black=%f, ave_diff=%f\n",
       ave_black, ave_diff);
#endif /* TUNE_CALIBRATOR */

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_calibrator_process_line (GT68xx_Calibrator * cal, unsigned int *line)
{
  int i;
  int width = cal->width;
  unsigned int white_level = cal->white_level;

  for (i = 0; i < width; ++i)
    {
      unsigned int src_value = line[i];
      unsigned int black = cal->k_black[i];
      unsigned int value;

      if (src_value > black)
	{
	  value = (src_value - black) * white_level / cal->k_white[i];
	  if (value > 0xffff)
	    {
	      value = 0xffff;
#ifdef TUNE_CALIBRATOR
	      cal->max_clip_count++;
#endif /* TUNE_CALIBRATOR */
	    }
	}
      else
	{
	  value = 0;
#ifdef TUNE_CALIBRATOR
	  if (src_value < black)
	    cal->min_clip_count++;
#endif /* TUNE_CALIBRATOR */
	}

      line[i] = value;
    }

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_scanner_new (GT68xx_Device * dev, GT68xx_Scanner ** scanner_return)
{
  GT68xx_Scanner *scanner;
  int i;

  *scanner_return = NULL;

  scanner = (GT68xx_Scanner *) malloc (sizeof (GT68xx_Scanner));
  if (!scanner)
    {
      DBG (5, "gt68xx_scanner_new: no memory for GT68xx_Scanner\n");
      return SANE_STATUS_NO_MEM;
    }

  scanner->dev = dev;
  scanner->reader = NULL;
  scanner->cal_gray = NULL;
  scanner->cal_r = NULL;
  scanner->cal_g = NULL;
  scanner->cal_b = NULL;

  for(i=0;i<MAX_RESOLUTIONS;i++)
    {
      scanner->calibrations[i].dpi = 0;
      scanner->calibrations[i].red = NULL;
      scanner->calibrations[i].green = NULL;
      scanner->calibrations[i].blue = NULL;
      scanner->calibrations[i].gray = NULL;
    }

  *scanner_return = scanner;
  return SANE_STATUS_GOOD;
}

static void
gt68xx_scanner_free_calibrators (GT68xx_Scanner * scanner)
{
  if (scanner->cal_gray)
    {
      gt68xx_calibrator_free (scanner->cal_gray);
      scanner->cal_gray = NULL;
    }

  if (scanner->cal_r)
    {
      gt68xx_calibrator_free (scanner->cal_r);
      scanner->cal_r = NULL;
    }

  if (scanner->cal_g)
    {
      gt68xx_calibrator_free (scanner->cal_g);
      scanner->cal_g = NULL;
    }

  if (scanner->cal_b)
    {
      gt68xx_calibrator_free (scanner->cal_b);
      scanner->cal_b = NULL;
    }
}

SANE_Status
gt68xx_scanner_free (GT68xx_Scanner * scanner)
{
  int i;

  if (!scanner)
    {
      DBG (5, "gt68xx_scanner_free: scanner==NULL\n");
      return SANE_STATUS_INVAL;
    }

  if (scanner->reader)
    {
      gt68xx_line_reader_free (scanner->reader);
      scanner->reader = NULL;
    }

  gt68xx_scanner_free_calibrators (scanner);

  /* free in memory calibration data */
  for (i = 0; i < MAX_RESOLUTIONS; i++)
    {
      scanner->calibrations[i].dpi = 0;
      if (scanner->calibrations[i].red != NULL)
	{
	  gt68xx_calibrator_free (scanner->calibrations[i].red);
	}
      if (scanner->calibrations[i].green != NULL)
	{
	  gt68xx_calibrator_free (scanner->calibrations[i].green);
	}
      if (scanner->calibrations[i].blue != NULL)
	{
	  gt68xx_calibrator_free (scanner->calibrations[i].blue);
	}
      if (scanner->calibrations[i].gray != NULL)
	{
	  gt68xx_calibrator_free (scanner->calibrations[i].gray);
	}
    }

  free (scanner);

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_wait_for_positioning (GT68xx_Scanner * scanner)
{
  SANE_Status status;
  SANE_Bool moving;
  SANE_Int status_count = 0;

  usleep (100000);		/* needed by the BP 2400 CU Plus? */
  while (SANE_TRUE)
    {
      status = gt68xx_device_is_moving (scanner->dev, &moving);
      if (status == SANE_STATUS_GOOD)
	{
	  if (!moving)
	    break;
	}
      else
	{
	  if (status_count > 9)
	    {
	      DBG (1, "gt68xx_scanner_wait_for_positioning: error count too high!\n");
	      return status;
	    }
	  status_count++;
	  DBG(3, "gt68xx_scanner_wait_for_positioning: ignored error\n");
	}
      usleep (100000);
    }

  return SANE_STATUS_GOOD;
}


static SANE_Status
gt68xx_scanner_internal_start_scan (GT68xx_Scanner * scanner)
{
  SANE_Status status;
  SANE_Bool ready;
  SANE_Int repeat_count;

  status = gt68xx_scanner_wait_for_positioning (scanner);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_internal_start_scan: gt68xx_scanner_wait_for_positioning error: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gt68xx_device_start_scan (scanner->dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_internal_start_scan: gt68xx_device_start_scan error: %s\n",
	   sane_strstatus (status));
      return status;
    }

  for (repeat_count = 0; repeat_count < 30 * 100; ++repeat_count)
    {
      status = gt68xx_device_read_scanned_data (scanner->dev, &ready);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5,
	       "gt68xx_scanner_internal_start_scan: gt68xx_device_read_scanned_data error: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      if (ready)
	break;
      usleep (10000);
    }
  if (!ready)
    {
      DBG (5,
	   "gt68xx_scanner_internal_start_scan: scanner still not ready - giving up\n");
      return SANE_STATUS_DEVICE_BUSY;
    }

  status = gt68xx_device_read_start (scanner->dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_internal_start_scan: gt68xx_device_read_start error: %s\n",
	   sane_strstatus (status));
      return status;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_start_scan_extended (GT68xx_Scanner * scanner,
				    GT68xx_Scan_Request * request,
				    GT68xx_Scan_Action action,
				    GT68xx_Scan_Parameters * params)
{
  SANE_Status status;
  GT68xx_AFE_Parameters afe = *scanner->dev->afe;

  status = gt68xx_scanner_wait_for_positioning (scanner);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_start_scan_extended: gt68xx_scanner_wait_for_positioning error: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gt68xx_device_setup_scan (scanner->dev, request, action, params);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_start_scan_extended: gt68xx_device_setup_scan failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gt68xx_line_reader_new (scanner->dev, params,
				   action == SA_SCAN ? SANE_TRUE : SANE_FALSE,
				   &scanner->reader);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_start_scan_extended: gt68xx_line_reader_new failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  if (scanner->dev->model->is_cis
      && !((scanner->dev->model->flags & GT68XX_FLAG_SHEET_FED) && scanner->calibrated == SANE_FALSE))
    {
      status =
	gt68xx_device_set_exposure_time (scanner->dev,
					 scanner->dev->exposure);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5,
	       "gt68xx_scanner_start_scan_extended: gt68xx_device_set_exposure_time failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }

  status = gt68xx_device_set_afe (scanner->dev, &afe);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_start_scan_extended: gt68xx_device_set_afe failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gt68xx_scanner_internal_start_scan (scanner);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_start_scan_extended: gt68xx_scanner_internal_start_scan failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_create_calibrator (GT68xx_Scan_Parameters * params,
				  GT68xx_Calibrator ** cal_return)
{
  return gt68xx_calibrator_new (params->pixel_xs, 65535, cal_return);
}

static SANE_Status
gt68xx_scanner_create_color_calibrators (GT68xx_Scanner * scanner,
					 GT68xx_Scan_Parameters * params)
{
  SANE_Status status;

  if (!scanner->cal_r)
    {
      status = gt68xx_scanner_create_calibrator (params, &scanner->cal_r);
      if (status != SANE_STATUS_GOOD)
	return status;
    }
  if (!scanner->cal_g)
    {
      status = gt68xx_scanner_create_calibrator (params, &scanner->cal_g);
      if (status != SANE_STATUS_GOOD)
	return status;
    }
  if (!scanner->cal_b)
    {
      status = gt68xx_scanner_create_calibrator (params, &scanner->cal_b);
      if (status != SANE_STATUS_GOOD)
	return status;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_create_gray_calibrators (GT68xx_Scanner * scanner,
					GT68xx_Scan_Parameters * params)
{
  SANE_Status status;

  if (!scanner->cal_gray)
    {
      status = gt68xx_scanner_create_calibrator (params, &scanner->cal_gray);
      if (status != SANE_STATUS_GOOD)
	return status;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_calibrate_color_white_line (GT68xx_Scanner * scanner,
					   unsigned int **buffer_pointers)
{

  gt68xx_calibrator_add_white_line (scanner->cal_r, buffer_pointers[0]);
  gt68xx_calibrator_add_white_line (scanner->cal_g, buffer_pointers[1]);
  gt68xx_calibrator_add_white_line (scanner->cal_b, buffer_pointers[2]);

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_calibrate_gray_white_line (GT68xx_Scanner * scanner,
					  unsigned int **buffer_pointers)
{
  gt68xx_calibrator_add_white_line (scanner->cal_gray, buffer_pointers[0]);
  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_calibrate_color_black_line (GT68xx_Scanner * scanner,
					   unsigned int **buffer_pointers)
{
  gt68xx_calibrator_add_black_line (scanner->cal_r, buffer_pointers[0]);
  gt68xx_calibrator_add_black_line (scanner->cal_g, buffer_pointers[1]);
  gt68xx_calibrator_add_black_line (scanner->cal_b, buffer_pointers[2]);

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_scanner_calibrate_gray_black_line (GT68xx_Scanner * scanner,
					  unsigned int **buffer_pointers)
{
  gt68xx_calibrator_add_black_line (scanner->cal_gray, buffer_pointers[0]);
  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_scanner_calibrate (GT68xx_Scanner * scanner,
			  GT68xx_Scan_Request * request)
{
  SANE_Status status;
  GT68xx_Scan_Parameters params;
  GT68xx_Scan_Request req;
  SANE_Int i;
  unsigned int *buffer_pointers[3];
  GT68xx_AFE_Parameters *afe = scanner->dev->afe;
  GT68xx_Exposure_Parameters *exposure = scanner->dev->exposure;

  memcpy (&req, request, sizeof (req));

  gt68xx_scanner_free_calibrators (scanner);

  if (scanner->auto_afe)
    {
      if (scanner->dev->model->is_cis)
	status = gt68xx_afe_cis_auto (scanner);
      else
	status = gt68xx_afe_ccd_auto (scanner, request);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5, "gt68xx_scanner_calibrate: gt68xx_afe_*_auto failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      req.mbs = SANE_FALSE;
    }
  else
    req.mbs = SANE_TRUE;

  DBG (3, "afe 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", afe->r_offset,
       afe->r_pga, afe->g_offset, afe->g_pga, afe->b_offset, afe->b_pga);
  DBG (3, "exposure 0x%02x 0x%02x 0x%02x\n", exposure->r_time,
       exposure->g_time, exposure->b_time);

  if (!scanner->calib)
    return SANE_STATUS_GOOD;

  req.mds = SANE_TRUE;
  req.mas = SANE_FALSE;

  if (scanner->dev->model->is_cis && !(scanner->dev->model->flags & GT68XX_FLAG_CIS_LAMP))
    req.color = SANE_TRUE;

  if (req.use_ta)
    {
      req.lamp = SANE_FALSE;
      status =
	gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_TRUE);
    }
  else
    {
      req.lamp = SANE_TRUE;
      status =
	gt68xx_device_lamp_control (scanner->dev, SANE_TRUE, SANE_FALSE);
    }

  status = gt68xx_scanner_start_scan_extended (scanner, &req, SA_CALIBRATE,
					       &params);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_calibrate: gt68xx_scanner_start_scan_extended failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  if (params.color)
    {
      status = gt68xx_scanner_create_color_calibrators (scanner, &params);
    }
  else
    {
      status = gt68xx_scanner_create_gray_calibrators (scanner, &params);
    }

#if defined(SAVE_WHITE_CALIBRATION) || defined(SAVE_BLACK_CALIBRATION)
  printf ("P5\n%d %d\n255\n", params.pixel_xs, params.pixel_ys * 3);
#endif
  for (i = 0; i < params.pixel_ys; ++i)
    {
      status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5,
	       "gt68xx_scanner_calibrate: gt68xx_line_reader_read failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      if (params.color)
	status = gt68xx_scanner_calibrate_color_white_line (scanner,
							    buffer_pointers);
      else
	status = gt68xx_scanner_calibrate_gray_white_line (scanner,
							   buffer_pointers);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5, "gt68xx_scanner_calibrate: calibration failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }
  gt68xx_scanner_stop_scan (scanner);

  if (params.color)
    {
      gt68xx_calibrator_eval_white (scanner->cal_r, 1);
      gt68xx_calibrator_eval_white (scanner->cal_g, 1);
      gt68xx_calibrator_eval_white (scanner->cal_b, 1);
    }
  else
    {
      gt68xx_calibrator_eval_white (scanner->cal_gray, 1);
    }

  req.mbs = SANE_FALSE;
  req.mds = SANE_FALSE;
  req.mas = SANE_FALSE;
  req.lamp = SANE_FALSE;

  status = gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_FALSE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_calibrate: gt68xx_device_lamp_control failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  if (!scanner->dev->model->is_cis
      || (scanner->dev->model->flags & GT68XX_FLAG_CIS_LAMP))
    usleep (500000);
  status = gt68xx_scanner_start_scan_extended (scanner, &req, SA_CALIBRATE,
					       &params);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_calibrate: gt68xx_scanner_start_scan_extended failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  for (i = 0; i < params.pixel_ys; ++i)
    {
      status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5,
	       "gt68xx_scanner_calibrate: gt68xx_line_reader_read failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      if (params.color)
	status = gt68xx_scanner_calibrate_color_black_line (scanner,
							    buffer_pointers);
      else
	status = gt68xx_scanner_calibrate_gray_black_line (scanner,
							   buffer_pointers);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (5, "gt68xx_scanner_calibrate: calibration failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }
  gt68xx_scanner_stop_scan (scanner);

  if (req.use_ta)
    status = gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_TRUE);
  else
    status = gt68xx_device_lamp_control (scanner->dev, SANE_TRUE, SANE_FALSE);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_calibrate: gt68xx_device_lamp_control failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  if (!scanner->dev->model->is_cis)
    usleep (500000);

  if (params.color)
    {
      gt68xx_calibrator_eval_black (scanner->cal_r, 0.0);
      gt68xx_calibrator_eval_black (scanner->cal_g, 0.0);
      gt68xx_calibrator_eval_black (scanner->cal_b, 0.0);

      gt68xx_calibrator_finish_setup (scanner->cal_r);
      gt68xx_calibrator_finish_setup (scanner->cal_g);
      gt68xx_calibrator_finish_setup (scanner->cal_b);
    }
  else
    {
      gt68xx_calibrator_eval_black (scanner->cal_gray, 0.0);
      gt68xx_calibrator_finish_setup (scanner->cal_gray);
    }

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_scanner_start_scan (GT68xx_Scanner * scanner,
			   GT68xx_Scan_Request * request,
			   GT68xx_Scan_Parameters * params)
{
  request->mbs = SANE_FALSE;	/* don't go home before real scan */
  request->mds = SANE_TRUE;
  request->mas = SANE_FALSE;
  if (request->use_ta)
    {
      gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_TRUE);
      request->lamp = SANE_FALSE;
    }
  else
    {
      gt68xx_device_lamp_control (scanner->dev, SANE_TRUE, SANE_FALSE);
      request->lamp = SANE_TRUE;
    }
  if (!scanner->dev->model->is_cis)
    sleep (2);

  return gt68xx_scanner_start_scan_extended (scanner, request, SA_SCAN,
					     params);
}

SANE_Status
gt68xx_scanner_read_line (GT68xx_Scanner * scanner,
			  unsigned int **buffer_pointers)
{
  SANE_Status status;

  status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_scanner_read_line: gt68xx_line_reader_read failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  if (scanner->calib)
    {
      if (scanner->reader->params.color)
	{
	  gt68xx_calibrator_process_line (scanner->cal_r, buffer_pointers[0]);
	  gt68xx_calibrator_process_line (scanner->cal_g, buffer_pointers[1]);
	  gt68xx_calibrator_process_line (scanner->cal_b, buffer_pointers[2]);
	}
      else
	{
	  if (scanner->dev->model->is_cis && !(scanner->dev->model->flags & GT68XX_FLAG_CIS_LAMP))
	    {
	      if (strcmp
		  (scanner->val[OPT_GRAY_MODE_COLOR].s,
		   GT68XX_COLOR_BLUE) == 0)
		gt68xx_calibrator_process_line (scanner->cal_b,
						buffer_pointers[0]);
	      else
		if (strcmp
		    (scanner->val[OPT_GRAY_MODE_COLOR].s,
		     GT68XX_COLOR_GREEN) == 0)
		gt68xx_calibrator_process_line (scanner->cal_g,
						buffer_pointers[0]);
	      else
		gt68xx_calibrator_process_line (scanner->cal_r,
						buffer_pointers[0]);
	    }
	  else
	    {
	      gt68xx_calibrator_process_line (scanner->cal_gray,
					      buffer_pointers[0]);
	    }
	}
    }

  return SANE_STATUS_GOOD;
}

SANE_Status
gt68xx_scanner_stop_scan (GT68xx_Scanner * scanner)
{
  if (scanner->reader)
    {
      gt68xx_line_reader_free (scanner->reader);
      scanner->reader = NULL;
    }
  return gt68xx_device_stop_scan (scanner->dev);
}

/************************************************************************/
/*                                                                      */
/* AFE offset/gain automatic configuration                              */
/*                                                                      */
/************************************************************************/

typedef struct GT68xx_Afe_Values GT68xx_Afe_Values;

struct GT68xx_Afe_Values
{
  SANE_Int black;		/* minimum black (0-255) */
  SANE_Int white;		/* maximum white (0-255) */
  SANE_Int total_white;		/* average white of the complete line (0-65536) */
  SANE_Int calwidth;
  SANE_Int callines;
  SANE_Int max_width;
  SANE_Int scan_dpi;
  SANE_Fixed start_black;
  SANE_Int offset_direction;
  SANE_Int coarse_black;
  SANE_Int coarse_white;
};


/************************************************************************/
/* CCD scanners                                                         */
/************************************************************************/

/** Calculate average black and maximum white
 *
 * This function is used for CCD scanners. The black mark to the left is used
 * for the calculation of average black. The remaining calibration strip
 * is used for searching the segment whose white average is the highest.
 *
 * @param values AFE values
 * @param buffer scanned line
 */
static void
gt68xx_afe_ccd_calc (GT68xx_Afe_Values * values, unsigned int *buffer)
{
  SANE_Int start_black;
  SANE_Int end_black;
  SANE_Int start_white;
  SANE_Int end_white;
  SANE_Int i;
  SANE_Int max_black = 0;
  SANE_Int min_black = 255;
  SANE_Int max_white = 0;
  SANE_Int total_white = 0;

  /* set size of black mark and white segments */
  start_black =
    SANE_UNFIX (values->start_black) * values->scan_dpi / MM_PER_INCH;
  end_black = start_black + 1.0 * values->scan_dpi / MM_PER_INCH;	/* 1 mm */

  /* 5mm after mark */
  start_white = end_black + 5.0 * values->scan_dpi / MM_PER_INCH;
  end_white = values->calwidth;

  DBG (5,
       "gt68xx_afe_ccd_calc: dpi=%d, start_black=%d, end_black=%d, start_white=%d, end_white=%d\n",
       values->scan_dpi, start_black, end_black, start_white, end_white);

  /* calc min and max black value */
  for (i = start_black; i < end_black; i++)
    {
      if ((SANE_Int) (buffer[i] >> 8) < min_black)
	min_black = (buffer[i] >> 8);
      if ((SANE_Int) (buffer[i] >> 8) > max_black)
	max_black = (buffer[i] >> 8);
#ifdef DEBUG_BLACK
      if ((buffer[i] >> 8) > 15)
	fprintf (stderr, "+");
      else if ((buffer[i] >> 8) < 5)
	fprintf (stderr, "-");
      else
	fprintf (stderr, "_");
#endif
    }
#ifdef DEBUG_BLACK
  fprintf (stderr, "\n");
#endif

  for (i = start_white; i < end_white; ++i)
    {
      if ((SANE_Int) (buffer[i] >> 8) > max_white)
	max_white = (buffer[i] >> 8);
      total_white += buffer[i];
    }
  values->total_white = total_white / (end_white - start_white);

  values->black = min_black;
  values->white = max_white;
  if (values->white < 50 || values->black > 150
      || values->white - values->black < 30 || max_black - min_black > 15)
    DBG (1,
	 "gt68xx_afe_ccd_calc: WARNING: max_white %3d   min_black %3d  max_black %3d\n",
	 values->white, values->black, max_black);
  else
    DBG (5,
	 "gt68xx_afe_ccd_calc: max_white %3d   min_black %3d  max_black %3d\n",
	 values->white, values->black, max_black);
}

static SANE_Bool
gt68xx_afe_ccd_adjust_offset_gain (SANE_String_Const color_name,
				   GT68xx_Afe_Values * values,
				   unsigned int *buffer, SANE_Byte * offset,
				   SANE_Byte * pga, SANE_Byte * old_offset,
				   SANE_Byte * old_pga)
{
  SANE_Int black_low = values->coarse_black, black_high = black_low + 10;
  SANE_Int white_high = values->coarse_white, white_low = white_high - 10;
  SANE_Bool done = SANE_TRUE;
  SANE_Byte local_pga = *pga;
  SANE_Byte local_offset = *offset;

  gt68xx_afe_ccd_calc (values, buffer);

#if 0
  /* test all offset values */
  local_offset++;
  done = SANE_FALSE;
  goto finish;
#endif

  if (values->white > white_high)
    {
      if (values->black > black_high)
	local_offset += values->offset_direction;
      else if (values->black < black_low)
	local_pga--;
      else
	{
	  local_offset += values->offset_direction;
	  local_pga--;
	}
      done = SANE_FALSE;
      goto finish;
    }
  else if (values->white < white_low)
    {
      if (values->black < black_low)
	local_offset -= values->offset_direction;
      else if (values->black > black_high)
	local_pga++;
      else
	{
	  local_offset -= values->offset_direction;
	  local_pga++;
	}
      done = SANE_FALSE;
      goto finish;
    }
  if (values->black > black_high)
    {
      if (values->white > white_high)
	local_offset += values->offset_direction;
      else if (values->white < white_low)
	local_pga++;
      else
	{
	  local_offset += values->offset_direction;
	  local_pga++;
	}
      done = SANE_FALSE;
      goto finish;
    }
  else if (values->black < black_low)
    {
      if (values->white < white_low)
	local_offset -= values->offset_direction;
      else if (values->white > white_high)
	local_pga--;
      else
	{
	  local_offset -= values->offset_direction;
	  local_pga--;
	}
      done = SANE_FALSE;
      goto finish;
    }
finish:
  if ((local_pga == *pga) && (local_offset == *offset))
    done = SANE_TRUE;
  if ((local_pga == *old_pga) && (local_offset == *old_offset))
    done = SANE_TRUE;

  *old_pga = *pga;
  *old_offset = *offset;

  DBG (4, "%5s white=%3d, black=%3d, offset=%2d, gain=%2d, old offs=%2d, "
       "old gain=%2d, total_white=%5d %s\n", color_name, values->white,
       values->black, local_offset, local_pga, *offset, *pga,
       values->total_white, done ? "DONE " : "");

  *pga = local_pga;
  *offset = local_offset;

  return done;
}

/* Wait for lamp to give stable brightness */
static SANE_Status
gt68xx_wait_lamp_stable (GT68xx_Scanner * scanner,
			 GT68xx_Scan_Parameters * params,
			 GT68xx_Scan_Request *request,
			 unsigned int *buffer_pointers[3],
			 GT68xx_Afe_Values *values,
			 SANE_Bool dont_move)
{
  SANE_Status status = SANE_STATUS_GOOD;
  SANE_Int last_white = 0;
  SANE_Bool first = SANE_TRUE;
  SANE_Bool message_printed = SANE_FALSE;
  struct timeval now, start_time;
  int secs_lamp_on, secs_start;
  int increase = -5;

  gettimeofday (&start_time, 0);
  do
    {
      usleep (200000);

      if (!first && dont_move)
	{
	  request->mbs = SANE_FALSE;
	  request->mds = SANE_FALSE;
	}
      first = SANE_FALSE;

      /* read line */
      status = gt68xx_scanner_start_scan_extended (scanner, request,
						   SA_CALIBRATE_ONE_LINE,
						   params);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (3,
	       "gt68xx_wait_lamp_stable: gt68xx_scanner_start_scan_extended "
	       "failed: %s\n", sane_strstatus (status));
	  return status;
	}
      status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (3, "gt68xx_wait_lamp_stable: gt68xx_line_reader_read failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}
      gt68xx_scanner_stop_scan (scanner);

      gt68xx_afe_ccd_calc (values, buffer_pointers[0]);

      DBG (4,
	   "gt68xx_wait_lamp_stable: this white = %d, last white = %d\n",
	   values->total_white, last_white);

      gettimeofday (&now, 0);
      secs_lamp_on = now.tv_sec - scanner->lamp_on_time.tv_sec;
      secs_start = now.tv_sec - start_time.tv_sec;

      if (!message_printed && secs_start > 5)
	{
	  DBG (0, "Please wait for lamp warm-up\n");
	  message_printed = SANE_TRUE;
	}

      if (scanner->val[OPT_AUTO_WARMUP].w == SANE_TRUE)
	{
	  if (scanner->dev->model->flags & GT68XX_FLAG_CIS_LAMP)
	    {
	      if (values->total_white <= (last_white - 20))
		  increase--;
	      if (values->total_white >= last_white)
		  increase++;
	      if (increase > 0 && (values->total_white <= (last_white + 20))
		  && values->total_white != 0)
		break;
	    }
	  else
	    {
	      if ((values->total_white <= (last_white + 20))
		  && values->total_white != 0)
		break;		/* lamp is warmed up */
	    }
	}
      last_white = values->total_white;
    }
  while (secs_lamp_on <= WARMUP_TIME);

  DBG (3, "gt68xx_wait_lamp_stable: Lamp is stable after %d secs (%d secs total)\n",
       secs_start, secs_lamp_on);
  return status;
}

/** Select best AFE gain and offset parameters.
 *
 * This function must be called before the main scan to choose the best values
 * for the AFE gains and offsets.  It performs several one-line scans of the
 * calibration strip.
 *
 * @param scanner Scanner object.
 * @param orig_request Scan parameters.
 *
 * @returns
 * - #SANE_STATUS_GOOD - gain and offset setting completed successfully
 * - other error value - failure of some internal function
 */
static SANE_Status
gt68xx_afe_ccd_auto (GT68xx_Scanner * scanner,
		     GT68xx_Scan_Request * orig_request)
{
  SANE_Status status;
  GT68xx_Scan_Parameters params;
  GT68xx_Scan_Request request;
  int i;
  GT68xx_Afe_Values values;
  unsigned int *buffer_pointers[3];
  GT68xx_AFE_Parameters *afe = scanner->dev->afe, old_afe;
  SANE_Bool gray_done = SANE_FALSE;
  SANE_Bool red_done = SANE_FALSE, green_done = SANE_FALSE, blue_done =
    SANE_FALSE;

  values.offset_direction = 1;
  if (scanner->dev->model->flags & GT68XX_FLAG_OFFSET_INV)
    values.offset_direction = -1;

  memset (&old_afe, 255, sizeof (old_afe));

  request.x0 = SANE_FIX (0.0);
  request.xs = scanner->dev->model->x_size;
  request.xdpi = 300;
  request.ydpi = 300;
  request.depth = 8;
  request.color = orig_request->color;
  /*  request.color = SANE_TRUE; */
  request.mas = SANE_FALSE;
  request.mbs = SANE_FALSE;
  request.mds = SANE_TRUE;
  request.calculate = SANE_FALSE;
  request.use_ta = orig_request->use_ta;

  if (orig_request->use_ta)
    {
      gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_TRUE);
      request.lamp = SANE_FALSE;
    }
  else
    {
      gt68xx_device_lamp_control (scanner->dev, SANE_TRUE, SANE_FALSE);
      request.lamp = SANE_TRUE;
    }

  /* read line */
  status = gt68xx_scanner_start_scan_extended (scanner, &request,
					       SA_CALIBRATE_ONE_LINE,
					       &params);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (3,
	   "gt68xx_afe_ccd_auto: gt68xx_scanner_start_scan_extended failed: %s\n",
	   sane_strstatus (status));
      return status;
    }
  values.scan_dpi = params.xdpi;
  values.calwidth = params.pixel_xs;
  values.max_width =
    (params.pixel_xs * scanner->dev->model->optical_xdpi) / params.xdpi;
  if (orig_request->use_ta)
    values.start_black = SANE_FIX (20.0);
  else
    values.start_black = scanner->dev->model->x_offset_mark;
  values.coarse_black = 1;
  values.coarse_white = 254;

  request.mds = SANE_FALSE;
  DBG (5, "gt68xx_afe_ccd_auto: scan_dpi=%d, calwidth=%d, max_width=%d, "
       "start_black=%.1f mm\n", values.scan_dpi,
       values.calwidth, values.max_width, SANE_UNFIX (values.start_black));

  status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (3, "gt68xx_afe_ccd_auto: gt68xx_line_reader_read failed: %s\n",
	   sane_strstatus (status));
      return status;
    }
  gt68xx_scanner_stop_scan (scanner);

  status = gt68xx_wait_lamp_stable (scanner, &params, &request, buffer_pointers,
				    &values, SANE_FALSE);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "gt68xx_afe_ccd_auto: gt68xx_wait_lamp_stable failed %s\n",
	   sane_strstatus (status));
      return status;
    }

  i = 0;
  do
    {
      i++;
      /* read line */
      status = gt68xx_scanner_start_scan_extended (scanner, &request,
						   SA_CALIBRATE_ONE_LINE,
						   &params);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (3,
	       "gt68xx_afe_ccd_auto: gt68xx_scanner_start_scan_extended failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (3, "gt68xx_afe_ccd_auto: gt68xx_line_reader_read failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      if (params.color)
	{
	  /* red */
	  if (!red_done)
	    red_done =
	      gt68xx_afe_ccd_adjust_offset_gain ("red", &values,
						 buffer_pointers[0],
						 &afe->r_offset, &afe->r_pga,
						 &old_afe.r_offset,
						 &old_afe.r_pga);
	  /* green */
	  if (!green_done)
	    green_done =
	      gt68xx_afe_ccd_adjust_offset_gain ("green", &values,
						 buffer_pointers[1],
						 &afe->g_offset, &afe->g_pga,
						 &old_afe.g_offset,
						 &old_afe.g_pga);

	  /* blue */
	  if (!blue_done)
	    blue_done =
	      gt68xx_afe_ccd_adjust_offset_gain ("blue", &values,
						 buffer_pointers[2],
						 &afe->b_offset, &afe->b_pga,
						 &old_afe.b_offset,
						 &old_afe.b_pga);
	}
      else
	{
	  if (strcmp (scanner->val[OPT_GRAY_MODE_COLOR].s, GT68XX_COLOR_BLUE)
	      == 0)
	    {
	      gray_done =
		gt68xx_afe_ccd_adjust_offset_gain ("gray", &values,
						   buffer_pointers[0],
						   &afe->b_offset,
						   &afe->b_pga,
						   &old_afe.b_offset,
						   &old_afe.b_pga);
	    }
	  else
	    if (strcmp
		(scanner->val[OPT_GRAY_MODE_COLOR].s,
		 GT68XX_COLOR_GREEN) == 0)
	    {
	      gray_done =
		gt68xx_afe_ccd_adjust_offset_gain ("gray", &values,
						   buffer_pointers[0],
						   &afe->g_offset,
						   &afe->g_pga,
						   &old_afe.g_offset,
						   &old_afe.g_pga);
	      afe->r_offset = afe->b_offset = 0x20;
	      afe->r_pga = afe->b_pga = 0x18;
	    }
	  else
	    {
	      gray_done =
		gt68xx_afe_ccd_adjust_offset_gain ("gray", &values,
						   buffer_pointers[0],
						   &afe->r_offset,
						   &afe->r_pga,
						   &old_afe.r_offset,
						   &old_afe.r_pga);
	    }
	}
      gt68xx_scanner_stop_scan (scanner);
    }
  while (((params.color && (!red_done || !green_done || !blue_done))
	  || (!params.color && !gray_done)) && i < 100);

  return status;
}

/************************************************************************/
/* CIS scanners                                                         */
/************************************************************************/


static void
gt68xx_afe_cis_calc_black (GT68xx_Afe_Values * values,
			   unsigned int *black_buffer)
{
  SANE_Int start_black;
  SANE_Int end_black;
  SANE_Int i, j;
  SANE_Int min_black = 255;
  SANE_Int average = 0;

  start_black = 0;
  end_black = values->calwidth;

  /* find min average black value */
  for (i = start_black; i < end_black; ++i)
    {
      SANE_Int avg_black = 0;
      for (j = 0; j < values->callines; j++)
	avg_black += (*(black_buffer + i + j * values->calwidth) >> 8);
      avg_black /= values->callines;
      average += avg_black;
      if (avg_black < min_black)
	min_black = avg_black;
    }
  values->black = min_black;
  average /= (end_black - start_black);
  DBG (5,
       "gt68xx_afe_cis_calc_black: min_black=0x%02x, average_black=0x%02x\n",
       values->black, average);
}

static void
gt68xx_afe_cis_calc_white (GT68xx_Afe_Values * values,
			   unsigned int *white_buffer)
{
  SANE_Int start_white;
  SANE_Int end_white;
  SANE_Int i, j;
  SANE_Int max_white = 0;

  start_white = 0;
  end_white = values->calwidth;
  values->total_white = 0;

  /* find max average white value */
  for (i = start_white; i < end_white; ++i)
    {
      SANE_Int avg_white = 0;
      for (j = 0; j < values->callines; j++)
	{
	  avg_white += (*(white_buffer + i + j * values->calwidth) >> 8);
	  values->total_white += (*(white_buffer + i + j * values->calwidth));
	}
      avg_white /= values->callines;
      if (avg_white > max_white)
	max_white = avg_white;
    }
  values->white = max_white;
  values->total_white /= (values->callines * (end_white - start_white));
  DBG (5,
       "gt68xx_afe_cis_calc_white: max_white=0x%02x, average_white=0x%02x\n",
       values->white, values->total_white >> 8);
}

static SANE_Bool
gt68xx_afe_cis_adjust_gain_offset (SANE_String_Const color_name,
				   GT68xx_Afe_Values * values,
				   unsigned int *black_buffer,
				   unsigned int *white_buffer,
				   GT68xx_AFE_Parameters * afe,
				   GT68xx_AFE_Parameters * old_afe)
{
  SANE_Byte *offset, *old_offset, *gain, *old_gain;
  SANE_Int o, g;
  SANE_Int black_low = values->coarse_black, black_high = black_low + 10;
  SANE_Int white_high = values->coarse_white, white_low = white_high - 10;
  SANE_Bool done = SANE_TRUE;

  gt68xx_afe_cis_calc_black (values, black_buffer);
  gt68xx_afe_cis_calc_white (values, white_buffer);

  if (strcmp (color_name, "red") == 0)
    {
      offset = &(afe->r_offset);
      old_offset = &old_afe->r_offset;
      gain = &afe->r_pga;
      old_gain = &old_afe->r_pga;
    }
  else if (strcmp (color_name, "green") == 0)
    {
      offset = &afe->g_offset;
      old_offset = &old_afe->g_offset;
      gain = &afe->g_pga;
      old_gain = &old_afe->g_pga;
    }
  else
    {
      offset = &afe->b_offset;
      old_offset = &old_afe->b_offset;
      gain = &afe->b_pga;
      old_gain = &old_afe->b_pga;
    }

  o = *offset;
  g = *gain;

  if (values->white > white_high)
    {
      if (values->black > black_high)
	o -= values->offset_direction;
      else if (values->black < black_low)
	g--;
      else
	{
	  o -= values->offset_direction;
	  g--;
	}
      done = SANE_FALSE;
      goto finish;
    }
  else if (values->white < white_low)
    {
      if (values->black < black_low)
	o += values->offset_direction;
      else if (values->black > black_high)
	g++;
      else
	{
	  o += values->offset_direction;
	  g++;
	}
      done = SANE_FALSE;
      goto finish;
    }
  if (values->black > black_high)
    {
      if (values->white > white_high)
	o -= values->offset_direction;
      else if (values->white < white_low)
	g++;
      else
	{
	  o -= values->offset_direction;
	  g++;
	}
      done = SANE_FALSE;
      goto finish;
    }
  else if (values->black < black_low)
    {
      if (values->white < white_low)
	o += values->offset_direction;
      else if (values->white > white_high)
	g--;
      else
	{
	  o += values->offset_direction;
	  g--;
	}
      done = SANE_FALSE;
      goto finish;
    }
finish:
  if (g < 0)
    g = 0;
  if (g > 48)
    g = 48;
  if (o < 0)
    o = 0;
  if (o > 64)
    o = 64;

  if ((g == *gain) && (o == *offset))
    done = SANE_TRUE;
  if ((g == *old_gain) && (o == *old_offset))
    done = SANE_TRUE;

  *old_gain = *gain;
  *old_offset = *offset;

  DBG (4, "%5s white=%3d, black=%3d, offset=0x%02X, gain=0x%02X, old offs=0x%02X, "
       "old gain=0x%02X, total_white=%5d %s\n", color_name, values->white,
       values->black, o, g, *offset, *gain, values->total_white,
       done ? "DONE " : "");

  *gain = g;
  *offset = o;

  return done;
}


static SANE_Bool
gt68xx_afe_cis_adjust_exposure (SANE_String_Const color_name,
				GT68xx_Afe_Values * values,
				unsigned int *white_buffer, SANE_Int border,
				SANE_Int * exposure_time)
{
  SANE_Int exposure_change = 0;

  gt68xx_afe_cis_calc_white (values, white_buffer);

  if (values->white < border)
    {
      exposure_change = ((border - values->white) * 1);
      (*exposure_time) += exposure_change;
      DBG (4,
	   "%5s: white = %3d, total_white=%5d (exposure too low) --> exposure += %d (=0x%03x)\n",
	   color_name, values->white, values->total_white, exposure_change, *exposure_time);
      return SANE_FALSE;
    }
  else if (values->white > border + 5)
    {
      exposure_change = -((values->white - (border + 5)) * 1);
      (*exposure_time) += exposure_change;
      DBG (4,
	   "%5s: white = %3d, total_white=%5d (exposure too high) --> exposure -= %d (=0x%03x)\n",
	   color_name, values->white, values->total_white, exposure_change, *exposure_time);
      return SANE_FALSE;
    }
  else
    {
      DBG (4, "%5s: white = %3d, total_white=%5d (exposure ok=0x%03x)\n",
	   color_name, values->white, values->total_white, *exposure_time);
    }
  return SANE_TRUE;
}

static SANE_Status
gt68xx_afe_cis_read_lines (GT68xx_Afe_Values * values,
			   GT68xx_Scanner * scanner, SANE_Bool lamp,
			   SANE_Bool first, unsigned int *r_buffer,
			   unsigned int *g_buffer, unsigned int *b_buffer)
{
  SANE_Status status;
  int line;
  unsigned int *buffer_pointers[3];
  GT68xx_Scan_Request request;
  GT68xx_Scan_Parameters params;

  request.x0 = SANE_FIX (0.0);
  request.xs = scanner->dev->model->x_size;
  request.xdpi = 300;
  request.ydpi = 300;
  request.depth = 8;
  request.color = SANE_TRUE;
  request.mas = SANE_FALSE;
  request.calculate = SANE_FALSE;
  request.use_ta = SANE_FALSE;

  if (first)			/* go to start position */
    {
      request.mbs = SANE_TRUE;
      request.mds = SANE_TRUE;
    }
  else
    {
      request.mbs = SANE_FALSE;
      request.mds = SANE_FALSE;
    }
  request.lamp = lamp;

  if (!r_buffer)		/* First, set the size parameters */
    {
      request.calculate = SANE_TRUE;
      RIE (gt68xx_device_setup_scan
	   (scanner->dev, &request, SA_CALIBRATE_ONE_LINE, &params));
      values->scan_dpi = params.xdpi;
      values->calwidth = params.pixel_xs;
      values->callines = params.pixel_ys;
      values->start_black = scanner->dev->model->x_offset_mark;
      return SANE_STATUS_GOOD;
    }

  if (first && (scanner->dev->model->flags & GT68XX_FLAG_CIS_LAMP))
    {
      if (request.use_ta)
	{
	  gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_TRUE);
	  request.lamp = SANE_FALSE;
	}
      else
	{
	  gt68xx_device_lamp_control (scanner->dev, SANE_TRUE, SANE_FALSE);
	  request.lamp = SANE_TRUE;
	}
      status = gt68xx_wait_lamp_stable (scanner, &params, &request,
					buffer_pointers, values, SANE_TRUE);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "gt68xx_afe_cis_read_lines: gt68xx_wait_lamp_stable failed %s\n",
	       sane_strstatus (status));
	  return status;
	}
      request.mbs = SANE_FALSE;
      request.mds = SANE_FALSE;
    }

  status =
    gt68xx_scanner_start_scan_extended (scanner, &request,
					SA_CALIBRATE_ONE_LINE, &params);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_afe_cis_read_lines: gt68xx_scanner_start_scan_extended failed: %s\n",
	   sane_strstatus (status));
      return status;
    }
  values->scan_dpi = params.xdpi;
  values->calwidth = params.pixel_xs;
  values->callines = params.pixel_ys;
  values->coarse_black = 2;
  values->coarse_white = 253;

  if (r_buffer && g_buffer && b_buffer)
    for (line = 0; line < values->callines; line++)
      {
	status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
	if (status != SANE_STATUS_GOOD)
	  {
	    DBG (5,
		 "gt68xx_afe_cis_read_lines: gt68xx_line_reader_read failed: %s\n",
		 sane_strstatus (status));
	    return status;
	  }
	memcpy (r_buffer + values->calwidth * line, buffer_pointers[0],
		values->calwidth * sizeof (unsigned int));
	memcpy (g_buffer + values->calwidth * line, buffer_pointers[1],
		values->calwidth * sizeof (unsigned int));
	memcpy (b_buffer + values->calwidth * line, buffer_pointers[2],
		values->calwidth * sizeof (unsigned int));
      }

  status = gt68xx_scanner_stop_scan (scanner);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (5,
	   "gt68xx_afe_cis_read_lines: gt68xx_scanner_stop_scan failed: %s\n",
	   sane_strstatus (status));
      return status;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_afe_cis_auto (GT68xx_Scanner * scanner)
{
  SANE_Status status;
  int total_count, exposure_count;
  GT68xx_Afe_Values values;
  GT68xx_AFE_Parameters *afe = scanner->dev->afe, old_afe;
  GT68xx_Exposure_Parameters *exposure = scanner->dev->exposure;
  SANE_Int red_done, green_done, blue_done;
  SANE_Bool first = SANE_TRUE;
  unsigned int *r_gbuffer = 0, *g_gbuffer = 0, *b_gbuffer = 0;
  unsigned int *r_obuffer = 0, *g_obuffer = 0, *b_obuffer = 0;

  DBG (5, "gt68xx_afe_cis_auto: start\n");

  if (scanner->dev->model->flags & GT68XX_FLAG_NO_CALIBRATE)
  {
    return SANE_STATUS_GOOD;
  }

  memset (&old_afe, 255, sizeof (old_afe));

  /* Start with the preset exposure settings */
  memcpy (scanner->dev->exposure, &scanner->dev->model->exposure,
	  sizeof (*scanner->dev->exposure));

  RIE (gt68xx_afe_cis_read_lines (&values, scanner, SANE_FALSE, SANE_FALSE,
				  r_gbuffer, g_gbuffer, b_gbuffer));

  r_gbuffer =
    malloc (values.calwidth * values.callines * sizeof (unsigned int));
  g_gbuffer =
    malloc (values.calwidth * values.callines * sizeof (unsigned int));
  b_gbuffer =
    malloc (values.calwidth * values.callines * sizeof (unsigned int));
  r_obuffer =
    malloc (values.calwidth * values.callines * sizeof (unsigned int));
  g_obuffer =
    malloc (values.calwidth * values.callines * sizeof (unsigned int));
  b_obuffer =
    malloc (values.calwidth * values.callines * sizeof (unsigned int));
  if (!r_gbuffer || !g_gbuffer || !b_gbuffer || !r_obuffer || !g_obuffer
      || !b_obuffer)
    return SANE_STATUS_NO_MEM;

  total_count = 0;
  red_done = green_done = blue_done = SANE_FALSE;
  old_afe.r_offset = old_afe.g_offset = old_afe.b_offset = 255;
  old_afe.r_pga = old_afe.g_pga = old_afe.b_pga = 255;
  do
    {
      values.offset_direction = 1;
      if (scanner->dev->model->flags & GT68XX_FLAG_OFFSET_INV)
	values.offset_direction = -1;

      RIE (gt68xx_afe_cis_read_lines (&values, scanner, SANE_FALSE, first,
				      r_obuffer, g_obuffer, b_obuffer));
      RIE (gt68xx_afe_cis_read_lines (&values, scanner, SANE_TRUE, SANE_FALSE,
				      r_gbuffer, g_gbuffer, b_gbuffer));

      if (!red_done)
	red_done =
	  gt68xx_afe_cis_adjust_gain_offset ("red", &values, r_obuffer,
					     r_gbuffer, afe, &old_afe);
      if (!green_done)
	green_done =
	  gt68xx_afe_cis_adjust_gain_offset ("green", &values, g_obuffer,
					     g_gbuffer, afe, &old_afe);
      if (!blue_done)
	blue_done =
	  gt68xx_afe_cis_adjust_gain_offset ("blue", &values, b_obuffer,
					     b_gbuffer, afe, &old_afe);
      total_count++;
      first = SANE_FALSE;

    }
  while (total_count < 100 && (!red_done || !green_done || !blue_done));

  if (!red_done || !green_done || !blue_done)
    DBG (0, "gt68xx_afe_cis_auto: setting AFE reached limit\n");

  /* Exposure time */
  exposure_count = 0;
  red_done = green_done = blue_done = SANE_FALSE;
  do
    {
      /* read white line */
      RIE (gt68xx_afe_cis_read_lines (&values, scanner, SANE_TRUE, SANE_FALSE,
				      r_gbuffer, g_gbuffer, b_gbuffer));
      if (!red_done)
	red_done =
	  gt68xx_afe_cis_adjust_exposure ("red", &values, r_gbuffer, 245,
					  &exposure->r_time);
      if (!green_done)
	green_done =
	  gt68xx_afe_cis_adjust_exposure ("green", &values, g_gbuffer, 245,
					  &exposure->g_time);
      if (!blue_done)
	blue_done =
	  gt68xx_afe_cis_adjust_exposure ("blue", &values, b_gbuffer, 245,
					  &exposure->b_time);
      exposure_count++;
      total_count++;
    }
  while ((!red_done || !green_done || !blue_done) && exposure_count < 50);

  if (!red_done || !green_done || !blue_done)
    DBG (0, "gt68xx_afe_cis_auto: setting exposure reached limit\n");

  /* store afe calibration when needed */
  if(scanner->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE)
    {
      memcpy(&(scanner->afe_params), afe, sizeof(GT68xx_AFE_Parameters));
      scanner->exposure_params.r_time=exposure->r_time;
      scanner->exposure_params.g_time=exposure->g_time;
      scanner->exposure_params.b_time=exposure->b_time;
    }

  free (r_gbuffer);
  free (g_gbuffer);
  free (b_gbuffer);
  free (r_obuffer);
  free (g_obuffer);
  free (b_obuffer);
  DBG (4, "gt68xx_afe_cis_auto: total_count: %d\n", total_count);

  return SANE_STATUS_GOOD;
}

/** @brief create and copy calibrator
 * Creates a calibrator of the given width and copy data from reference
 * to initialize it
 * @param calibator pointer to the calibrator to create
 * @param reference calibrator with reference data to copy
 * @param width the width in pixels of the calibrator
 * @param offset offset in pixels when copying data from reference
 * @return SANE_STATUS_GOOD and a filled calibrator if enough memory
 */
static SANE_Status
gt68xx_calibrator_create_copy (GT68xx_Calibrator ** calibrator,
			       GT68xx_Calibrator * reference, int width,
			       int offset)
{
  SANE_Status status;
  int i;

  if (reference == NULL)
    {
      DBG (1, "gt68xx_calibrator_create_copy: NULL reference, skipping...\n");
      *calibrator = NULL;
      return SANE_STATUS_GOOD;
    }
  /* check for reference overflow */
  if(width+offset>reference->width)
    {
      DBG (1, "gt68xx_calibrator_create_copy: required with and offset exceed reference width\n");
      return SANE_STATUS_INVAL;
    }

  status = gt68xx_calibrator_new (width, 65535, calibrator);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1,
	   "gt68xx_calibrator_create_copy: failed to create calibrator: %s\n",
	   sane_strstatus (status));
      return status;
    }

  for(i=0;i<width;i++)
    {
      (*calibrator)->k_white[i]=reference->k_white[i+offset];
      (*calibrator)->k_black[i]=reference->k_black[i+offset];
      (*calibrator)->white_line[i]=reference->white_line[i+offset];
      (*calibrator)->black_line[i]=reference->black_line[i+offset];
    }

  return status;
}

static SANE_Status
gt68xx_sheetfed_move_to_scan_area (GT68xx_Scanner * scanner,
				  GT68xx_Scan_Request * request)
{
  SANE_Status status;

  if (!(scanner->dev->model->flags & GT68XX_FLAG_SHEET_FED)
      || scanner->dev->model->command_set->move_paper == NULL)
    return SANE_STATUS_GOOD;

  /* send move paper command */
  RIE (scanner->dev->model->command_set->move_paper (scanner->dev, request));

  /* wait until paper is set to the desired position */
  return gt68xx_scanner_wait_for_positioning (scanner);
}

/**< number of consecutive white line to detect a white area */
#define WHITE_LINES 2

/** @brief calibrate sheet fed scanner
 * This function calibrates sheet fed scanner by scanning a calibration
 * target (which may be a blank page). It first move to a white area then
 * does afe and exposure calibration. Then it scans white lines to get data
 * for shading correction.
 * @param scanner structure describing the frontend session and the device
 * @return SANE_STATUS_GOOD is everything goes right, SANE_STATUS_INVAL
 * otherwise.
 */
static SANE_Status
gt68xx_sheetfed_scanner_calibrate (GT68xx_Scanner * scanner)
{
  SANE_Status status;
  GT68xx_Scan_Request request;
  GT68xx_Scan_Parameters params;
  int count, i, x, y, white;
  unsigned int *buffer_pointers[3];
#ifdef DEBUG_CALIBRATION
  FILE *fcal;
  char title[50];
#endif

  DBG (3, "gt68xx_sheetfed_scanner_calibrate: start.\n");

  /* clear calibration if needed */
  gt68xx_scanner_free_calibrators (scanner);
  for (i = 0; i < MAX_RESOLUTIONS; i++)
    {
      if(scanner->calibrations[i].red!=NULL)
        {
          gt68xx_calibrator_free (scanner->calibrations[i].red);
        }
      if(scanner->calibrations[i].green!=NULL)
        {
          gt68xx_calibrator_free (scanner->calibrations[i].green);
        }
      if(scanner->calibrations[i].blue!=NULL)
        {
          gt68xx_calibrator_free (scanner->calibrations[i].blue);
        }
      if(scanner->calibrations[i].gray!=NULL)
        {
          gt68xx_calibrator_free (scanner->calibrations[i].gray);
        }
    }
  scanner->calibrated = SANE_FALSE;

  /* find minimum horizontal resolution */
  request.xdpi = 9600;
  for (i = 0; scanner->dev->model->xdpi_values[i] != 0; i++)
    {
      if (scanner->dev->model->xdpi_values[i] < request.xdpi)
	{
	  request.xdpi = scanner->dev->model->xdpi_values[i];
	  request.ydpi = scanner->dev->model->xdpi_values[i];
	}
    }

  /* move to white area SA_CALIBRATE uses its own y0/ys fixed values */
  request.x0 = 0;
  request.y0 = scanner->dev->model->y_offset_calib;
  request.xs = scanner->dev->model->x_size;
  request.depth = 8;

  request.color = SANE_FALSE;
  request.mbs = SANE_TRUE;
  request.mds = SANE_TRUE;
  request.mas = SANE_FALSE;
  request.lamp = SANE_TRUE;
  request.calculate = SANE_FALSE;
  request.use_ta = SANE_FALSE;
  request.backtrack = SANE_FALSE;
  request.backtrack_lines = 0;

  /* skip start of calibration sheet */
  status = gt68xx_sheetfed_move_to_scan_area (scanner, &request);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1,
	   "gt68xx_sheetfed_scanner_calibrate: failed to skip start of calibration sheet %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = gt68xx_device_lamp_control (scanner->dev, SANE_FALSE, SANE_TRUE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1,
	   "gt68xx_sheetfed_scanner_calibrate: gt68xx_device_lamp_control returned %s\n",
	   sane_strstatus (status));
      return status;
    }

  /* loop until we find a white area to calibrate on */
  i = 0;
  request.y0 = 0;
  do
    {
      /* start scan */
      status =
	gt68xx_scanner_start_scan_extended (scanner, &request, SA_CALIBRATE,
					    &params);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_sheetfed_scanner_calibrate: gt68xx_scanner_start_scan_extended returned %s\n",
	       sane_strstatus (status));
	  return status;
	}

      /* loop until we find WHITE_LINES consecutive white lines or we reach and of area */
      white = 0;
      y = 0;
      do
	{
	  status = gt68xx_line_reader_read (scanner->reader, buffer_pointers);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (1,
		   "gt68xx_sheetfed_scanner_calibrate: gt68xx_line_reader_read returned %s\n",
		   sane_strstatus (status));
	      gt68xx_scanner_stop_scan (scanner);
	      return status;
	    }

	  /* check for white line */
	  count = 0;
	  for (x = 0; x < params.pixel_xs; x++)
	    {
	      if (((buffer_pointers[0][x] >> 8) & 0xff) > 50)
		{
		  count++;
		}
	    }

	  /* line is white if 93% is above black level */
	  if ((100 * count) / params.pixel_xs < 93)
	    {
	      white = 0;
	    }
	  else
	    {
	      white++;
	    }
	  y++;
	}
      while ((white < WHITE_LINES) && (y < params.pixel_ys));

      /* end scan */
      gt68xx_scanner_stop_scan (scanner);

      i++;
    }
  while (i < 20 && white < WHITE_LINES);

  /* check if we found a white area */
  if (white != WHITE_LINES)
    {
      DBG (1,
	   "gt68xx_sheetfed_scanner_calibrate: didn't find a white area\n");
      return SANE_STATUS_INVAL;
    }

  /* now do calibration */
  scanner->auto_afe = SANE_TRUE;
  scanner->calib = SANE_TRUE;

  /* loop at each possible xdpi to create calibrators */
  i = 0;
  while (scanner->dev->model->xdpi_values[i] > 0)
    {
      request.xdpi = scanner->dev->model->xdpi_values[i];
      request.ydpi = scanner->dev->model->xdpi_values[i];
      request.x0 = 0;
      request.y0 = 0;
      request.xs = scanner->dev->model->x_size;
      request.color = SANE_FALSE;
      request.mbs = SANE_FALSE;
      request.mds = SANE_TRUE;
      request.mas = SANE_FALSE;
      request.lamp = SANE_TRUE;
      request.calculate = SANE_FALSE;
      request.use_ta = SANE_FALSE;
      request.backtrack = SANE_FALSE;
      request.backtrack_lines = 0;

      /* calibrate in color */
      request.color = SANE_TRUE;
      status = gt68xx_scanner_calibrate (scanner, &request);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_sheetfed_scanner_calibrate: gt68xx_scanner_calibrate returned %s\n",
	       sane_strstatus (status));
	  return status;
	}

      /* since auto afe is done at a fixed resolution, we don't need to
       * do each each time, once is enough */
      scanner->auto_afe = SANE_FALSE;

      /* allocate and save per dpi calibrators */
      scanner->calibrations[i].dpi = request.xdpi;

      /* recompute params */
      request.calculate = SANE_TRUE;
      gt68xx_device_setup_scan (scanner->dev, &request, SA_SCAN, &params);

      scanner->calibrations[i].pixel_x0 = params.pixel_x0;
      status =
	gt68xx_calibrator_create_copy (&(scanner->calibrations[i].red),
				       scanner->cal_r, scanner->cal_r->width,
				       0);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_sheetfed_scanner_calibrate: failed to create red calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      status =
	gt68xx_calibrator_create_copy (&(scanner->calibrations[i].green),
				       scanner->cal_g, scanner->cal_g->width,
				       0);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_sheetfed_scanner_calibrate: failed to create green calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      status =
	gt68xx_calibrator_create_copy (&(scanner->calibrations[i].blue),
				       scanner->cal_b, scanner->cal_b->width,
				       0);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_sheetfed_scanner_calibrate: failed to create blue calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      /* calibrate in gray */
      request.color = SANE_FALSE;
      status = gt68xx_scanner_calibrate (scanner, &request);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_sheetfed_scanner_calibrate: gt68xx_scanner_calibrate returned %s\n",
	       sane_strstatus (status));
	  return status;
	}

      if (scanner->cal_gray)
	{
	  status =
	    gt68xx_calibrator_create_copy (&(scanner->calibrations[i].gray),
					   scanner->cal_gray,
					   scanner->cal_gray->width, 0);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (1,
		   "gt68xx_sheetfed_scanner_calibrate: failed to create gray calibrator: %s\n",
		   sane_strstatus (status));
	      return status;
	    }
	}

#ifdef DEBUG_CALIBRATION
      sprintf (title, "cal-%03d-red.pnm", scanner->calibrations[i].dpi);
      fcal = fopen (title, "wb");
      if (fcal != NULL)
	{
	  fprintf (fcal, "P5\n%d 1\n255\n", params.pixel_xs);
	  for (x = 0; x < params.pixel_xs; x++)
	    fputc ((scanner->calibrations[i].red->k_white[x] >> 8) & 0xff,
		   fcal);
	  fclose (fcal);
	}
      sprintf (title, "cal-%03d-green.pnm", scanner->calibrations[i].dpi);
      fcal = fopen (title, "wb");
      if (fcal != NULL)
	{
	  fprintf (fcal, "P5\n%d 1\n255\n", params.pixel_xs);
	  for (x = 0; x < params.pixel_xs; x++)
	    fputc ((scanner->calibrations[i].green->k_white[x] >> 8) & 0xff,
		   fcal);
	  fclose (fcal);
	}
      sprintf (title, "cal-%03d-blue.pnm", scanner->calibrations[i].dpi);
      fcal = fopen (title, "wb");
      if (fcal != NULL)
	{
	  fprintf (fcal, "P5\n%d 1\n255\n", params.pixel_xs);
	  for (x = 0; x < params.pixel_xs; x++)
	    fputc ((scanner->calibrations[i].blue->k_white[x] >> 8) & 0xff,
		   fcal);
	  fclose (fcal);
	}
#endif

      /* next resolution */
      i++;
    }

  scanner->calibrated = SANE_TRUE;

  /* eject calibration target from feeder */
  gt68xx_device_paperfeed (scanner->dev);

  /* save calibration to file */
  gt68xx_write_calibration (scanner);

  DBG (3, "gt68xx_sheetfed_scanner_calibrate: end.\n");
  return SANE_STATUS_GOOD;
}

/** @brief assign calibration for scan
 * This function creates the calibrators and set up afe for the requested
 * scan. It uses calibration data that has been created by
 * gt68xx_sheetfed_scanner_calibrate.
 * @param scanner structure describing the frontend session and the device
 * @return SANE_STATUS_GOOD is everything goes right, SANE_STATUS_INVAL
 * otherwise.
 */
static SANE_Status
gt68xx_assign_calibration (GT68xx_Scanner * scanner,
			   GT68xx_Scan_Parameters params)
{
  int i, dpi, offset;
  SANE_Status status = SANE_STATUS_GOOD;

  DBG (3, "gt68xx_assign_calibration: start.\n");

  dpi = params.xdpi;
  DBG (4, "gt68xx_assign_calibration: searching calibration for %d dpi\n",
       dpi);

  /* search matching dpi */
  i = 0;
  while (scanner->calibrations[i].dpi > 0
	 && scanner->calibrations[i].dpi != dpi)
    {
      i++;
    }

  /* check if found a match */
  if (scanner->calibrations[i].dpi == 0)
    {
      DBG (4,
	   "gt68xx_assign_calibration: failed to find calibration for %d dpi\n",
	   dpi);
      return SANE_STATUS_INVAL;
    }
  DBG (4, "gt68xx_assign_calibration: using entry %d for %d dpi\n", i, dpi);

  DBG (5,
       "gt68xx_assign_calibration: using scan_parameters: pixel_x0=%d, pixel_xs=%d \n",
       params.pixel_x0, params.pixel_xs);

  /* AFE/exposure data copy */
  memcpy (scanner->dev->afe, &(scanner->afe_params),
	  sizeof (GT68xx_AFE_Parameters));
  scanner->dev->exposure->r_time = scanner->exposure_params.r_time;
  scanner->dev->exposure->g_time = scanner->exposure_params.g_time;
  scanner->dev->exposure->b_time = scanner->exposure_params.b_time;

  /* free calibrators if needed */
  gt68xx_scanner_free_calibrators (scanner);

  /* TODO compute offset based on the x0 value from scan_request */
  offset = params.pixel_x0 - scanner->calibrations[i].pixel_x0;

  /* calibrator allocation and copy */
  if (scanner->calibrations[i].red!=NULL)
    {
      status =
	gt68xx_calibrator_create_copy (&(scanner->cal_r),
				       scanner->calibrations[i].red,
				       params.pixel_xs,
                                       offset);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_assign_calibration: failed to create calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }

  if (scanner->calibrations[i].green!=NULL)
    {
      status =
	gt68xx_calibrator_create_copy (&(scanner->cal_g),
				       scanner->calibrations[i].green,
				       params.pixel_xs,
				       offset);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_assign_calibration: failed to create calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }

  if (scanner->calibrations[i].blue!=NULL)
    {
      status =
	gt68xx_calibrator_create_copy (&(scanner->cal_b),
				       scanner->calibrations[i].blue,
				       params.pixel_xs,
				       offset);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_assign_calibration: failed to create calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }

  if (scanner->calibrations[i].gray!=NULL)
    {
      status =
	gt68xx_calibrator_create_copy (&(scanner->cal_gray),
				       scanner->calibrations[i].gray,
				       params.pixel_xs,
				       offset);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1,
	       "gt68xx_assign_calibration: failed to create calibrator: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }

  DBG (3, "gt68xx_assign_calibration: end.\n");
  return status;
}

static char *gt68xx_calibration_file(GT68xx_Scanner * scanner)
{
  char *ptr=NULL;
  char tmp_str[PATH_MAX];

  ptr=getenv("HOME");
  if(ptr!=NULL)
    {
      sprintf (tmp_str, "%s/.sane/gt68xx-%s.cal", ptr, scanner->dev->model->name);
    }
  else
    {
      ptr=getenv("TMPDIR");
      if(ptr!=NULL)
        {
          sprintf (tmp_str, "%s/gt68xx-%s.cal", ptr, scanner->dev->model->name);
        }
      else
        {
          sprintf (tmp_str, "/tmp/gt68xx-%s.cal", scanner->dev->model->name);
        }
    }
  DBG(5,"gt68xx_calibration_file: using >%s< for calibration file name\n",tmp_str);
  return strdup(tmp_str);
}

static SANE_Status
gt68xx_clear_calibration (GT68xx_Scanner * scanner)
{
  char *fname;
  int i;

  if (scanner->calibrated == SANE_FALSE)
    return SANE_STATUS_GOOD;

  /* clear file */
  fname = gt68xx_calibration_file (scanner);
  unlink (fname);
  free (fname);

  /* free calibrators */
  for (i = 0; i < MAX_RESOLUTIONS && scanner->calibrations[i].dpi > 0; i++)
    {
      scanner->calibrations[i].dpi = 0;
      if (scanner->calibrations[i].red)
	gt68xx_calibrator_free (scanner->calibrations[i].red);
      if (scanner->calibrations[i].green)
	gt68xx_calibrator_free (scanner->calibrations[i].green);
      if (scanner->calibrations[i].blue)
	gt68xx_calibrator_free (scanner->calibrations[i].blue);
      if (scanner->calibrations[i].gray)
	gt68xx_calibrator_free (scanner->calibrations[i].gray);
    }

  /* reset flags */
  scanner->calibrated = SANE_FALSE;
  scanner->val[OPT_QUALITY_CAL].w = SANE_FALSE;
  scanner->val[OPT_NEED_CALIBRATION_SW].w = SANE_TRUE;
  DBG (5, "gt68xx_clear_calibration: done\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_write_calibration (GT68xx_Scanner * scanner)
{
  char *fname;
  FILE *fcal;
  int i;
  SANE_Int nullwidth = 0;

  if (scanner->calibrated == SANE_FALSE)
    {
      return SANE_STATUS_GOOD;
    }

  /* open file */
  fname = gt68xx_calibration_file (scanner);
  fcal = fopen (fname, "wb");
  free (fname);
  if (fcal == NULL)
    {
      DBG (1,
	   "gt68xx_write_calibration: failed to open calibration file for writing %s\n",
	   strerror (errno));
      return SANE_STATUS_IO_ERROR;
    }

  /* TODO we save check endianness and word alignment in case of a home
   * directory used trough different archs */
  fwrite (&(scanner->afe_params), sizeof (GT68xx_AFE_Parameters), 1, fcal);
  fwrite (&(scanner->exposure_params), sizeof (GT68xx_Exposure_Parameters), 1,
	  fcal);
  for (i = 0; i < MAX_RESOLUTIONS && scanner->calibrations[i].dpi > 0; i++)
    {
      DBG (1, "gt68xx_write_calibration: saving %d dpi calibration\n",
	   scanner->calibrations[i].dpi);
      fwrite (&(scanner->calibrations[i].dpi), sizeof (SANE_Int), 1, fcal);
      fwrite (&(scanner->calibrations[i].pixel_x0), sizeof (SANE_Int), 1,
	      fcal);

      fwrite (&(scanner->calibrations[i].red->width), sizeof (SANE_Int), 1,
	      fcal);
      fwrite (&(scanner->calibrations[i].red->white_level), sizeof (SANE_Int),
	      1, fcal);
      fwrite (scanner->calibrations[i].red->k_white, sizeof (unsigned int),
	      scanner->calibrations[i].red->width, fcal);
      fwrite (scanner->calibrations[i].red->k_black, sizeof (unsigned int),
	      scanner->calibrations[i].red->width, fcal);
      fwrite (scanner->calibrations[i].red->white_line, sizeof (double),
	      scanner->calibrations[i].red->width, fcal);
      fwrite (scanner->calibrations[i].red->black_line, sizeof (double),
	      scanner->calibrations[i].red->width, fcal);

      fwrite (&(scanner->calibrations[i].green->width), sizeof (SANE_Int), 1,
	      fcal);
      fwrite (&(scanner->calibrations[i].green->white_level),
	      sizeof (SANE_Int), 1, fcal);
      fwrite (scanner->calibrations[i].green->k_white, sizeof (unsigned int),
	      scanner->calibrations[i].green->width, fcal);
      fwrite (scanner->calibrations[i].green->k_black, sizeof (unsigned int),
	      scanner->calibrations[i].green->width, fcal);
      fwrite (scanner->calibrations[i].green->white_line, sizeof (double),
	      scanner->calibrations[i].green->width, fcal);
      fwrite (scanner->calibrations[i].green->black_line, sizeof (double),
	      scanner->calibrations[i].green->width, fcal);

      fwrite (&(scanner->calibrations[i].blue->width), sizeof (SANE_Int), 1,
	      fcal);
      fwrite (&(scanner->calibrations[i].blue->white_level),
	      sizeof (SANE_Int), 1, fcal);
      fwrite (scanner->calibrations[i].blue->k_white, sizeof (unsigned int),
	      scanner->calibrations[i].blue->width, fcal);
      fwrite (scanner->calibrations[i].blue->k_black, sizeof (unsigned int),
	      scanner->calibrations[i].blue->width, fcal);
      fwrite (scanner->calibrations[i].blue->white_line, sizeof (double),
	      scanner->calibrations[i].blue->width, fcal);
      fwrite (scanner->calibrations[i].blue->black_line, sizeof (double),
	      scanner->calibrations[i].blue->width, fcal);

      if (scanner->calibrations[i].gray != NULL)
	{
	  fwrite (&(scanner->calibrations[i].gray->width), sizeof (SANE_Int),
		  1, fcal);
	  fwrite (&(scanner->calibrations[i].gray->white_level),
		  sizeof (SANE_Int), 1, fcal);
	  fwrite (scanner->calibrations[i].gray->k_white,
		  sizeof (unsigned int), scanner->calibrations[i].gray->width,
		  fcal);
	  fwrite (scanner->calibrations[i].gray->k_black,
		  sizeof (unsigned int), scanner->calibrations[i].gray->width,
		  fcal);
	  fwrite (scanner->calibrations[i].gray->white_line, sizeof (double),
		  scanner->calibrations[i].gray->width, fcal);
	  fwrite (scanner->calibrations[i].gray->black_line, sizeof (double),
		  scanner->calibrations[i].gray->width, fcal);
	}
      else
	{
	  fwrite (&nullwidth, sizeof (SANE_Int), 1, fcal);
	}
    }
  DBG (5, "gt68xx_write_calibration: wrote %d calibrations\n", i);

  fclose (fcal);
  return SANE_STATUS_GOOD;
}

static SANE_Status
gt68xx_read_calibration (GT68xx_Scanner * scanner)
{
  char *fname;
  FILE *fcal;
  int i;
  SANE_Int width, level;

  scanner->calibrated = SANE_FALSE;
  fname = gt68xx_calibration_file (scanner);
  fcal = fopen (fname, "rb");
  free (fname);
  if (fcal == NULL)
    {
      DBG (1,
	   "gt68xx_read_calibration: failed to open calibration file for reading %s\n",
	   strerror (errno));
      return SANE_STATUS_IO_ERROR;
    }

  /* TODO we should check endianness and word alignment in case of a home
   * directory used trough different archs */

  /* TODO check for errors */
  fread (&(scanner->afe_params), sizeof (GT68xx_AFE_Parameters), 1, fcal);
  fread (&(scanner->exposure_params), sizeof (GT68xx_Exposure_Parameters), 1,
	 fcal);

  /* loop on calibrators */
  i = 0;
  fread (&(scanner->calibrations[i].dpi), sizeof (SANE_Int), 1, fcal);
  while (!feof (fcal) && scanner->calibrations[i].dpi > 0)
    {
      fread (&(scanner->calibrations[i].pixel_x0), sizeof (SANE_Int), 1,
	     fcal);

      fread (&width, sizeof (SANE_Int), 1, fcal);
      fread (&level, sizeof (SANE_Int), 1, fcal);
      gt68xx_calibrator_new (width, level, &(scanner->calibrations[i].red));
      fread (scanner->calibrations[i].red->k_white, sizeof (unsigned int),
	     width, fcal);
      fread (scanner->calibrations[i].red->k_black, sizeof (unsigned int),
	     width, fcal);
      fread (scanner->calibrations[i].red->white_line, sizeof (double), width,
	     fcal);
      fread (scanner->calibrations[i].red->black_line, sizeof (double), width,
	     fcal);

      fread (&width, sizeof (SANE_Int), 1, fcal);
      fread (&level, sizeof (SANE_Int), 1, fcal);
      gt68xx_calibrator_new (width, level, &(scanner->calibrations[i].green));
      fread (scanner->calibrations[i].green->k_white, sizeof (unsigned int),
	     width, fcal);
      fread (scanner->calibrations[i].green->k_black, sizeof (unsigned int),
	     width, fcal);
      fread (scanner->calibrations[i].green->white_line, sizeof (double),
	     width, fcal);
      fread (scanner->calibrations[i].green->black_line, sizeof (double),
	     width, fcal);

      fread (&width, sizeof (SANE_Int), 1, fcal);
      fread (&level, sizeof (SANE_Int), 1, fcal);
      gt68xx_calibrator_new (width, level, &(scanner->calibrations[i].blue));
      fread (scanner->calibrations[i].blue->k_white, sizeof (unsigned int),
	     width, fcal);
      fread (scanner->calibrations[i].blue->k_black, sizeof (unsigned int),
	     width, fcal);
      fread (scanner->calibrations[i].blue->white_line, sizeof (double),
	     width, fcal);
      fread (scanner->calibrations[i].blue->black_line, sizeof (double),
	     width, fcal);

      fread (&width, sizeof (SANE_Int), 1, fcal);
      if (width > 0)
	{
	  fread (&level, sizeof (SANE_Int), 1, fcal);
	  gt68xx_calibrator_new (width, level,
				 &(scanner->calibrations[i].gray));
	  fread (scanner->calibrations[i].gray->k_white,
		 sizeof (unsigned int), width, fcal);
	  fread (scanner->calibrations[i].gray->k_black,
		 sizeof (unsigned int), width, fcal);
	  fread (scanner->calibrations[i].gray->white_line, sizeof (double),
		 width, fcal);
	  fread (scanner->calibrations[i].gray->black_line, sizeof (double),
		 width, fcal);
	}
      /* prepare for nex resolution */
      i++;
      fread (&(scanner->calibrations[i].dpi), sizeof (SANE_Int), 1, fcal);
    }

  DBG (5, "gt68xx_read_calibration: read %d calibrations\n", i);
  fclose (fcal);

  scanner->val[OPT_QUALITY_CAL].w = SANE_TRUE;
  scanner->val[OPT_NEED_CALIBRATION_SW].w = SANE_FALSE;
  scanner->calibrated = SANE_TRUE;
  return SANE_STATUS_GOOD;
}


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