diff -Naur sane-1.0.2/backend/Makefile.in sane-1.0.2-hp4200/backend/Makefile.in
--- sane-1.0.2/backend/Makefile.in	Sun Mar  5 13:40:35 2000
+++ sane-1.0.2-hp4200/backend/Makefile.in	Sun Aug 20 01:08:23 2000
@@ -52,8 +52,9 @@
 @SET_MAKE@
 
 PRELOADABLE_BACKENDS = abaton agfafocus apple artec avision canon coolscan \
-	dc25 @DC210@ dmc epson hp m3096g microtek microtek2 mustek @NET@ \
-	@PINT@ pnm @QCAM@ ricoh s9036 sharp snapscan sp15c tamarack umax
+	dc25 @DC210@ dmc epson hp hp4200 microtek microtek2 mustek \
+	@NET@ @PINT@ pnm @QCAM@ ricoh s9036 sharp snapscan tamarack \
+	umax
 ALL_BACKENDS = $(PRELOADABLE_BACKENDS) dll
 
 LIBS = $(addprefix libsane-,$(addsuffix .la,$(ALL_BACKENDS)))
@@ -152,6 +153,7 @@
 # additional dependencies
 
 EXTRA_hp = hp-accessor hp-device hp-handle hp-hpmem hp-option hp-scl
+EXTRA_hp4200 = pv8630 lm983x
 EXTRA_dc210 = djpeg
 
 # When preloading dll, we need to add in all preloaded objects:
@@ -197,6 +199,9 @@
 libsane-hp.la: ../sanei/sanei_scsi.lo
 libsane-hp.la: $(addsuffix .lo,$(EXTRA_hp))
 libsane-hp.la: ../sanei/sanei_pio.lo
+libsane-hp4200.la: ../sanei/sanei_config2.lo
+libsane-hp4200.la: ../sanei/sanei_constrain_value.lo
+libsane-hp4200.la: $(addsuffix .lo,$(EXTRA_hp4200))
 libsane-m3096g.la: ../sanei/sanei_config2.lo
 libsane-m3096g.la: ../sanei/sanei_constrain_value.lo
 libsane-m3096g.la: ../sanei/sanei_scsi.lo
diff -Naur sane-1.0.2/backend/dll.conf sane-1.0.2-hp4200/backend/dll.conf
--- sane-1.0.2/backend/dll.conf	Sun Mar  5 13:41:15 2000
+++ sane-1.0.2-hp4200/backend/dll.conf	Fri Aug 11 16:06:52 2000
@@ -12,6 +12,7 @@
 dmc
 epson
 hp
+hp4200
 m3096g
 microtek
 microtek2
diff -Naur sane-1.0.2/backend/hp4200.c sane-1.0.2-hp4200/backend/hp4200.c
--- sane-1.0.2/backend/hp4200.c	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/hp4200.c	Mon Aug 21 19:29:34 2000
@@ -0,0 +1,2930 @@
+/*
+  Copyright (C) 2000 by Adrian Perez Jorge
+
+  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.  
+ */
+
+/* Developers:
+
+       Adrian Perez Jorge (APJ) - 
+            Creator of the original HP4200C backend code.  Actively
+	    developing code for the HP4200C.
+	    adrianpj@easynews.com
+				  
+       Andrew John Lewis  (AJL) - 
+            Actively developing code for the HP4200C.
+	    lewi0235@tc.umn.edu
+
+       Arnar Mar Hrafnkelsson (AMH) -
+            addi@umich.edu
+*/
+
+/* Changes:
+
+   03-06-2000  Added call to deinterlace() in try_a_scan to create the 
+               final output file within the try_a_scan function. 
+	       (AJL)
+
+   03-07-2000  Added GNU GPL disclaimer to beginning of file.
+               Modified try_a_scan() and main() to allow user defined
+	       resolution changes.  Problems when line width is set >
+	       1275.  You want this higher when doing > 150 dpi
+	       scans.  Not sure what the problem is.
+	       (AJL)
+
+   04-01-2000  hp4200c_goto_home: added code to return the scanner
+               head to home position.
+	       hp4200c_wait_homed: created.
+	       Some code added to get options from command line.
+	       Geometry and resolution parameters doesn't work yet!
+
+   06-06-2000  Fix test of the returned values from open().
+               Thanks to Arnar Mar Hrafnkelsson <addi@umich.edu> for
+	       the bugfix.
+	       TODO: check return values from other open() calls, but
+	       because those calls will not remain in the final code,
+	       it doesn't matter too much.
+
+   06-19-2000  Added new interface to scanner's registers via setreg(),
+               setbits() and clearbits().
+	       Replaced s->regs accesses by the right call to one of
+	       the above functions as recommended by Arnar.
+
+   07-02-2000  Just found what was wrong when reading data from the
+               scanner.  I have to read all data indicated in register
+	       0x01 before reading register 0x01 again.
+
+   07-03-2000  Renamed `main.c' to `hp4200.c'.  Added SANE interface.
+               Line separation correction added.  This is done at
+	       scanning time using a consumer-producer model.  Some
+	       fixes are needed but just works fine for 8bpp.
+
+   07-04-2000  First public release of the backend.
+
+   07-19-2000  do_fine_calibration: fixed data_pixels_start.
+               sane_start: fixed aborted_by_user.
+	       Added gamma tables.
+	       Added more dpi's.
+               
+
+TODO:
+
+   - support more scanning resolutions.
+   - support different color depths.
+   - improve scanning speed. Compute scanning parameters based on the
+     image size and the scanner-to-host bandwidth.
+   - improve image quality.
+  
+*/   
+
+
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <assert.h>
+#include "lm983x.h"
+#include "pv8630.h"
+#include <sane/sane.h>
+#include <sane/sanei.h>
+#include <sane/sanei_debug.h>
+#include <sane/sanei_config.h>
+#include "hp4200.h"
+
+#define BACKEND_NAME hp4200
+
+#include <sane/sanei_backend.h>
+#include <sane/saneopts.h>
+
+#define HP4200_CONFIG_FILE "HP4200.conf"
+
+#define DEFAULT_DEVICE "/dev/usbscanner"
+
+#define PATH_MAX 1024
+#define MM_PER_INCH 25.4
+
+/* debug levels */
+#define DL_INFO        1
+#define DL_MINOR_INFO  2
+#define DL_MAJOR_ERROR 1
+#define DL_MINOR_ERROR 2
+#define DL_DATA_TRACE  5
+#define DL_CALL_TRACE  10
+#define DL_VERBOSE     30
+
+struct coarse_t
+{
+  int min_red;
+  int min_green;
+  int min_blue;
+  int max_red;
+  int max_green;
+  int max_blue;
+  int red_gain;
+  int red_offset;
+  int green_gain;
+  int green_offset;
+  int blue_gain;
+  int blue_offset;
+};
+
+const double hdpi_mapping[8] = {1, 1.5, 2, 3, 4, 6, 8, 12};
+
+static HP4200_Device *first_device = NULL; /* device list head */
+static int n_devices = 0;	/* the device count */
+static const SANE_Device **get_devices_list = NULL;
+
+static unsigned char
+getreg (HP4200_Scanner *s, unsigned char reg)
+{
+  unsigned char reg_value;
+
+  if ((reg > 0x08) && (reg < 0x5b))
+    return (unsigned char) LOBYTE(s->regs[reg]);
+  else
+    {
+      lm983x_read_register (s->fd, reg, &reg_value);
+      return reg_value;
+    }
+}
+
+static void
+setreg (HP4200_Scanner *s, unsigned char reg, unsigned char reg_value)
+{
+  s->regs[reg] = reg_value;	/* dirty bit should be clear with this */
+  if ((reg < 0x08) || (reg > 0x5b))
+    {
+      lm983x_write_register (s->fd, reg, reg_value);
+    }
+}
+
+static void
+setbits (HP4200_Scanner *s, unsigned char reg, unsigned char bitmap)
+{
+  s->regs[reg] = (s->regs[reg] & 0xff) | bitmap;
+  if ((reg < 0x08) || (reg > 0x5b))
+    {
+      lm983x_write_register (s->fd, reg, LOBYTE(s->regs[reg]));
+    }
+}
+
+static void
+clearbits (HP4200_Scanner *s, unsigned char reg, unsigned char mask)
+{
+  s->regs[reg] = (s->regs[reg] & ~mask) & 0xff;
+  if ((reg < 0x08) || (reg > 0x5b))
+    {
+      lm983x_write_register (s->fd, reg, LOBYTE(s->regs[reg]));
+    }
+}
+
+static int
+compute_min_mclk (unsigned int SRAM_bandwidth,
+		  unsigned long crystal_frequency)
+{
+  int min_mclk;
+
+  if (SRAM_bandwidth == 8)
+    {
+      min_mclk = (int) ceil ((double) MCLKDIV_SCALING *
+			     crystal_frequency / 25000000.0);
+    }
+  else
+    {
+      min_mclk = (int) ceil ((double) MCLKDIV_SCALING *
+			     crystal_frequency / 50000000.0);
+    }
+
+  return min_mclk;
+}
+
+static int
+cache_write (HP4200_Scanner *s)
+{
+  int i;
+#ifdef DEBUG_REG_CACHE
+  int counter = 0;
+#endif
+
+  DBG (DL_CALL_TRACE, "Writing registers");
+
+  for (i = 0; i < 0x80; i++)
+    if (!(s->regs[i] & 0x100))	/* modified register */
+      {
+#ifdef DEBUG_REG_CACHE
+	fprintf (stderr, "%.2x", i);
+	if (counter == 8)
+	  fprintf (stderr, "\n");
+	else
+	  fprintf (stderr, ", ");
+	counter = (counter + 1) % 9;
+#endif
+	lm983x_write_register (s->fd, i, s->regs[i]);
+	s->regs[i] |= 0x100;	/* register is updated */
+      }
+  return 0;
+}
+
+/*
+ * HP4200-dependent register initialization.
+ */
+
+static int
+hp4200_init_registers(HP4200_Scanner *s)
+{
+  /* set up hardware parameters */
+
+  s->hw_parms.crystal_frequency = 48000000;
+  s->hw_parms.SRAM_size = 128;	/* Kb */
+  s->hw_parms.scan_area_width = 5100; /* pixels */
+  s->hw_parms.scan_area_length = 11; /* inches */
+  s->hw_parms.min_pixel_data_buffer_limit = 1024; /* bytes */
+  s->hw_parms.sensor_line_separation = 4; /* lines */
+  s->hw_parms.sensor_max_integration_time = 12;	/* milliseconds */
+  s->hw_parms.home_sensor = 2;
+  s->hw_parms.sensor_resolution = 1; /* 600 dpi */
+  s->hw_parms.motor_full_steps_per_inch = 300;
+  s->hw_parms.motor_max_speed = 1.4; /* inches/second */
+  s->hw_parms.num_tr_pulses = 1;
+  s->hw_parms.guard_band_duration = 1;
+  s->hw_parms.pulse_duration = 3;
+  s->hw_parms.fsteps_25_speed = 3;
+  s->hw_parms.fsteps_50_speed = 3;
+  s->hw_parms.target_value.red = 1000;
+  s->hw_parms.target_value.green = 1000;
+  s->hw_parms.target_value.blue = 1000;
+
+  {
+    int i;
+
+    /*
+     * we are using a cache-like data structure so registers whose
+     * values were written to the lm983x and aren't volatile, have
+     * bit 0x100 activated.  This bit must be cleared if you want the
+     * value to be written to the chip once cache_write() is called.
+     */
+
+    /* clears the registers cache */
+    memset (s->regs, 0, sizeof (s->regs));
+
+    /*
+     * registers 0x00 - 0x07 are non-cacheable/volatile, so don't
+     * read the values using the cache.  Instead use direct functions
+     * to read/write registers.
+     */
+    
+    for (i = 0; i < 0x08; i++)
+      s->regs[i] = 0x100;
+  }
+
+  setreg (s, 0x70, 0x70);	/* noise filter */
+  
+  setreg (s, 0x0b,
+	  INPUT_SIGNAL_POLARITY_NEGATIVE |
+	  CDS_ON |
+	  SENSOR_STANDARD |
+	  SENSOR_RESOLUTION_600 |
+	  LINE_SKIPPING_COLOR_PHASE_DELAY(0));
+  
+  setreg (s, 0x0c, 
+	  PHI1_POLARITY_POSITIVE |
+	  PHI2_POLARITY_POSITIVE |
+	  RS_POLARITY_POSITIVE |
+	  CP1_POLARITY_POSITIVE |
+	  CP2_POLARITY_POSITIVE |
+	  TR1_POLARITY_NEGATIVE |
+	  TR2_POLARITY_NEGATIVE);
+  
+  setreg (s, 0x0d, 
+	  PHI1_ACTIVE |
+	  PHI2_ACTIVE |
+	  RS_ACTIVE |
+	  CP1_ACTIVE |
+	  CP2_OFF |
+	  TR1_ACTIVE |
+	  TR2_OFF |
+	  NUMBER_OF_TR_PULSES(s->hw_parms.num_tr_pulses));
+  
+  setreg (s, 0x0e, 
+	  TR_PULSE_DURATION(s->hw_parms.pulse_duration) |
+	  TR_PHI1_GUARDBAND_DURATION(s->hw_parms.guard_band_duration));
+  
+  /* for pixel rate timing */
+  setreg (s, 0x0f, 6);
+  setreg (s, 0x10, 23);
+  setreg (s, 0x11, 1);
+  setreg (s, 0x12, 3);
+  setreg (s, 0x13, 3);		/* 0 */
+  setreg (s, 0x14, 5);		/* 0 */
+  setreg (s, 0x15, 0);
+  setreg (s, 0x16, 0);
+  setreg (s, 0x17, 11);
+  setreg (s, 0x18, 2);		/* 1 */
+  
+  setreg (s, 0x19, 
+	  CIS_TR1_TIMING_OFF |
+	  FAKE_OPTICAL_BLACK_PIXELS_OFF);
+
+  setreg (s, 0x1a, 0);
+  setreg (s, 0x1b, 0);
+
+  setreg (s, 0x1c, 0x0d);
+  setreg (s, 0x1d, 0x21);
+
+  setreg (s, 0x27, TR_RED_DROP(0) | TR_GREEN_DROP(0) | TR_BLUE_DROP(0));
+
+  setreg (s, 0x28, 0x00);
+
+  setreg (s, 0x29, ILLUMINATION_MODE(1));
+  setreg (s, 0x2a, HIBYTE(0));	/* 0 */
+  setreg (s, 0x2b, LOBYTE(0));	/* 0 */
+
+  setreg (s, 0x2c, HIBYTE(16383));
+  setreg (s, 0x2d, LOBYTE(16383));
+
+  setreg (s, 0x2e, HIBYTE(2));	/* 2 */
+  setreg (s, 0x2f, LOBYTE(2));	/* 1 */
+
+  setreg (s, 0x30, HIBYTE(0));
+  setreg (s, 0x31, LOBYTE(0));
+
+  setreg (s, 0x32, HIBYTE(0));
+  setreg (s, 0x33, LOBYTE(0));
+
+  setreg (s, 0x34, HIBYTE(32));
+  setreg (s, 0x35, LOBYTE(32));
+
+  setreg (s, 0x36, HIBYTE(48));
+  setreg (s, 0x37, LOBYTE(48));
+
+  setreg (s, 0x42, EPP_MODE | PPORT_DRIVE_CURRENT(3));
+
+  setreg (s, 0x43,
+	  RAM_SIZE_128 |
+	  SRAM_DRIVER_CURRENT(3) |
+	  SRAM_BANDWIDTH_8 |
+	  SCANNING_FULL_DUPLEX);
+
+  setreg (s, 0x45,
+	  MICRO_STEPPING |
+	  CURRENT_SENSING_PHASES(2) |
+	  PHASE_A_POLARITY_POSITIVE |
+	  PHASE_B_POLARITY_POSITIVE |
+	  STEPPER_MOTOR_OUTPUT);
+
+  setreg (s, 0x4a, HIBYTE(100));
+  setreg (s, 0x4b, LOBYTE(100));
+
+  setreg (s, 0x4c, HIBYTE(0));
+  setreg (s, 0x4d, LOBYTE(0));
+
+  /* resume scan threshold */
+  setreg (s, 0x4f, 64);
+  /* steps to reverse */
+  setreg (s, 0x50, 40);
+  setreg (s, 0x51,
+	  ACCELERATION_PROFILE_STOPPED(3) |
+	  ACCELERATION_PROFILE_25P(s->hw_parms.fsteps_25_speed) |
+	  ACCELERATION_PROFILE_50P(s->hw_parms.fsteps_50_speed));
+  setreg (s, 0x54, 
+	  NON_REVERSING_EXTRA_LINES(0) |
+	  FIRST_LINE_TO_PROCESS(0));
+  setreg (s, 0x55,
+	  KICKSTART_STEPS(0) |
+	  HOLD_CURRENT_TIMEOUT(2));
+
+  /* stepper PWM frequency */
+  setreg (s, 0x56, 8);
+  /* stepper pwm duty cycle */
+  setreg (s, 0x57, 23);
+  
+  setreg (s, 0x58,
+	  PAPER_SENSOR_1_POLARITY_HIGH |
+	  PAPER_SENSOR_1_TRIGGER_EDGE |
+	  PAPER_SENSOR_1_NO_STOP_SCAN |
+	  PAPER_SENSOR_2_POLARITY_HIGH |
+	  PAPER_SENSOR_2_TRIGGER_EDGE |
+	  PAPER_SENSOR_2_STOP_SCAN);
+  setreg (s, 0x59,
+	  MISCIO_1_TYPE_OUTPUT |
+	  MISCIO_1_POLARITY_HIGH |
+	  MISCIO_1_TRIGGER_EDGE |
+	  MISCIO_1_OUTPUT_STATE_HIGH |
+	  MISCIO_2_TYPE_OUTPUT |
+	  MISCIO_2_POLARITY_HIGH |
+	  MISCIO_2_TRIGGER_EDGE |
+	  MISCIO_2_OUTPUT_STATE_HIGH);
+
+  return 0;
+}
+
+static int
+dump_register_cache (HP4200_Scanner *s)
+{
+  int i;
+  
+  for (i = 0; i < 0x80; i++)
+    {
+      fprintf (stderr, "%.2x:0x%.2x", i, s->regs[i]);
+      if ((i + 1) % 8)
+	fprintf (stderr, ", ");
+      else
+	fprintf (stderr, "\n");
+    }
+  fputs ("", stderr);
+  return 0;
+}
+
+/*
+ * returns the scanner head to home position
+ */
+
+static int
+hp4200_goto_home (HP4200_Scanner *s)
+{
+  unsigned char cmd_reg;
+  unsigned char status_reg;
+  unsigned char old_paper_sensor_reg;
+
+  cmd_reg = getreg (s, 0x07);
+  if (cmd_reg != 2)
+    {
+      unsigned char paper_sensor_reg;
+      unsigned char sensor_bit[2] = {0x02, 0x10};
+      /* sensor head is not returning */
+
+      /* let's see if it's already at home */
+      /* first put paper (head) sensor level sensitive */
+      paper_sensor_reg = getreg (s, 0x58);
+      old_paper_sensor_reg = paper_sensor_reg;
+      paper_sensor_reg &= ~sensor_bit[s->hw_parms.home_sensor - 1];
+      setreg (s, 0x58, paper_sensor_reg);
+      cache_write(s);
+
+      /* if the scan head is not at home then move motor backwards */
+      status_reg = getreg (s, 0x02);
+      setreg (s, 0x58, old_paper_sensor_reg);
+      cache_write(s);
+      if (!(status_reg & s->hw_parms.home_sensor))
+	{
+	  setreg (s, 0x07, 0x08);
+	  usleep (10 * 1000);
+	  setreg (s, 0x07, 0x00);
+	  usleep (10 * 1000);
+	  setreg (s, 0x07, 0x02);
+	}
+    }
+  return 0;
+}
+
+#define HP4200_CHECK_INTERVAL 1000 /* usecs between status checks */
+static int
+hp4200_wait_homed (HP4200_Scanner *s)
+{
+  unsigned char cmd_reg;
+
+  cmd_reg = getreg (s, 0x07);
+  while (cmd_reg != 0)
+  {
+    usleep (HP4200_CHECK_INTERVAL);
+    cmd_reg = getreg (s, 0x07);
+  }
+  return 0;
+}
+
+static int
+compute_fastfeed_step_size (unsigned long crystal_freq, int mclk,
+			    float max_speed, int steps_per_inch,
+			    int color_mode)
+{
+  int aux;
+  int r;
+
+  if (color_mode == 0)
+    r = 24;
+  else
+    r = 8;
+
+  aux = floor (crystal_freq / ((double) mclk * max_speed * 4.0 *
+			       steps_per_inch * r)); 
+
+
+  if (aux < 2)
+    aux = 2;
+  return aux;
+}
+
+#define error(x) fprintf(stderr, (x))
+
+SANE_Status
+read_available_data (HP4200_Scanner *s, char *buffer, int *byte_count)
+{
+  unsigned char scankb1;
+  unsigned char scankb2;
+  int to_read;
+  int really_read;
+  int chunk;
+
+  assert (buffer != NULL);
+
+  *byte_count = 0;
+  do {
+    scankb1 = getreg (s, 0x01);
+    scankb2 = getreg (s, 0x01);
+    if (s->aborted_by_user)
+      return SANE_STATUS_CANCELLED;
+  } while ((scankb1 != scankb2) || (scankb1 < 12));
+
+  to_read = scankb1 * 1024;
+
+  while (to_read)
+    {
+      if (s->aborted_by_user)
+	return SANE_STATUS_CANCELLED;
+      chunk = (to_read > 0xffff) ? 0xffff : to_read;
+
+      pv8630_write_byte (s->fd, 0x00, PV8630_REPPADDRESS);
+      pv8630_init_eppbulk_read (s->fd, chunk);
+      really_read = read (s->fd, buffer, chunk);
+      if (really_read < 0)
+	{
+	  perror ("read");
+	  return -1;
+	}
+      if (really_read > to_read)
+	{
+	  error ("USB stack read more bytes than requested!");
+	  return -1;
+	}
+      *byte_count += really_read;
+      buffer += really_read;
+      to_read -= really_read;
+#ifdef DEBUG
+      fprintf (stderr, "he leido %d bytes\n", really_read);
+#endif
+    }
+  return SANE_STATUS_GOOD;
+}
+
+static int
+compute_datalink_bandwidth (HP4200_Scanner *s)
+{
+  int line_size;
+  int pause_limit;
+  unsigned int color_mode;
+
+  /*
+   * Line size for 8 bpp, the entire scan area width (plus the
+   * status byte) at optical resolution.
+   */
+
+  if (s->user_parms.color)
+    {
+      line_size = 3 * s->hw_parms.scan_area_width + 1;
+      color_mode = 0;
+      setreg (s, 0x26, color_mode); /* 3 channel pixel rate color */
+    }
+  else
+    {
+      line_size = s->hw_parms.scan_area_width + 1;
+      color_mode = 4;
+      setreg (s, 0x26, 0x08 | color_mode); /* 1 channel mode A (green) */
+    }
+  setreg (s, 0x09, (3 << 3));	/* h-divider = 1, 8 bpp */
+		
+  {
+    int first_white_pixel;
+    unsigned int line_end;
+
+    first_white_pixel = s->hw_parms.sensor_pixel_end - 10;
+    line_end = first_white_pixel + s->hw_parms.scan_area_width;
+    if (line_end > (s->hw_parms.sensor_num_pixels - 20))
+    line_end = s->hw_parms.sensor_num_pixels - 20;
+	
+    setreg (s, 0x1c, HIBYTE(s->hw_parms.sensor_pixel_start));
+    setreg (s, 0x1d, LOBYTE(s->hw_parms.sensor_pixel_end));
+    setreg (s, 0x1e, HIBYTE(first_white_pixel));
+    setreg (s, 0x1f, LOBYTE(first_white_pixel));
+    setreg (s, 0x20, HIBYTE(s->hw_parms.sensor_num_pixels));
+    setreg (s, 0x21, LOBYTE(s->hw_parms.sensor_num_pixels));
+    setreg (s, 0x22, getreg (s, 0x1e));
+    setreg (s, 0x23, getreg (s, 0x1f));
+    setreg (s, 0x24, HIBYTE(line_end));
+    setreg (s, 0x25, LOBYTE(line_end));
+  }
+
+  /*
+   * During transfer rate calculation don't forward scanner sensor.
+   * Stay in the calibration region.
+   */
+
+  setreg (s, 0x4f, 0);
+  clearbits (s, 0x45, 0x10);
+
+  /*
+   * Pause the scan when memory is full.
+   */
+
+  pause_limit = s->hw_parms.SRAM_size - (line_size / 1024) - 1;
+  setreg (s, 0x4e, pause_limit & 0xff);
+
+  s->mclk = compute_min_mclk (s->hw_parms.SRAM_bandwidth,
+			      s->hw_parms.crystal_frequency);
+
+
+  /*
+   * Set step size to fast speed.
+   */
+
+  {
+    int step_size;
+
+    step_size = 
+      compute_fastfeed_step_size (s->hw_parms.crystal_frequency,
+				  s->mclk, 
+				  s->hw_parms.scan_bar_max_speed,
+				  s->hw_parms.motor_full_steps_per_inch,
+				  color_mode);
+
+    setreg (s, 0x46, HIBYTE(step_size));
+    setreg (s, 0x47, LOBYTE(step_size));
+    setreg (s, 0x48, HIBYTE(step_size));
+    setreg (s, 0x49, LOBYTE(step_size));
+  }
+
+  cache_write (s);
+
+  //  dump_register_cache (s);
+
+  /*
+   * scann during 1 sec. aprox.
+   */
+
+  setreg (s, 0x07, 0x08);
+  setreg (s, 0x07, 0x03);
+
+  {
+    struct timeval tv_before;
+    struct timeval tv_after;
+    int elapsed_time_ms = 0;
+    long bytes_read_total;
+    char *buffer;
+
+    buffer = malloc(2*98304);	/* check this */
+    if (!buffer)
+      {
+	perror ("compute_datalink_bandwidth");
+	return 0;
+      }
+    bytes_read_total = 0;
+    gettimeofday (&tv_before, NULL);
+    do
+      {
+	int bytes_read;
+	SANE_Status status;
+
+	status = read_available_data (s, buffer, &bytes_read);
+	if (status != SANE_STATUS_GOOD)
+	  {
+	    perror("read");
+	    return 0;
+	  }
+	bytes_read_total += bytes_read;
+	gettimeofday (&tv_after, NULL);
+	elapsed_time_ms = (tv_after.tv_sec - tv_before.tv_sec) * 1000;
+	elapsed_time_ms += (tv_after.tv_usec - tv_before.tv_usec) / 1000;
+      } while (elapsed_time_ms < 1000);
+
+    setreg (s, 0x07, 0x00);
+    free (buffer);
+
+    s->msrd_parms.datalink_bandwidth = bytes_read_total /
+      (elapsed_time_ms / 1000);
+
+#ifdef DEBUG
+    fprintf (stderr, "PC Transfer rate = %d bytes/sec. (%ld/%d)\n",
+	     s->msrd_parms.datalink_bandwidth, bytes_read_total,
+	     elapsed_time_ms);
+#endif
+  }
+  return 0;
+}
+
+static void
+compute_first_gain_offset (int target, int max, int min, int *gain,
+			   int *offset, int *max_gain, int *min_offset) 
+{
+  *gain = (int) 15.0 * (target / (max - min) - 0.933);
+  *offset = (int) (-1.0 * min / (512.0 * 0.0195));
+  if (*gain >= 32)
+    {
+      *gain = (int) 15.0 * (target / 3.0 / (max - min) - 0.933);
+      *offset = (int) -3.0 * min / (512.0 * 0.0195);
+    }
+  if (*gain < 0)
+    *gain = 0;
+  else if (*gain > 63)
+    *gain = 63;
+  
+  if (*offset < -31)
+    *offset = -31;
+  else if (*offset > 31)
+    *offset = 31;
+
+  *max_gain = 63;
+  *min_offset = -31;
+}
+
+#define DATA_PORT_READ (1 << 5)
+#define DATA_PORT_WRITE 0
+
+static int
+write_gamma (HP4200_Scanner *s)
+{
+  int color;
+  int i;
+  unsigned char gamma[1024];
+  unsigned char read_gamma[1024];
+  int retval;
+
+  for (color = 0; color < 3; color++)
+    {
+      for (i = 0; i < 1024; i++)
+	gamma[i] = s->user_parms.gamma[color][i];
+      
+      setreg (s, 0x03, color << 1);
+      setreg (s, 0x04, DATA_PORT_WRITE);
+      setreg (s, 0x05, 0x00);
+      pv8630_write_byte (s->fd, 0x06, PV8630_REPPADDRESS);
+      pv8630_init_eppbulk_write (s->fd, sizeof (gamma));
+      write (s->fd, gamma, sizeof (gamma));
+      
+      /* check if gamma vector was correctly written */
+      
+      setreg (s, 0x03, color << 1);
+      setreg (s, 0x04, DATA_PORT_READ);
+      setreg (s, 0x05, 0x00);
+      pv8630_write_byte (s->fd, 0x06, PV8630_REPPADDRESS);
+      pv8630_init_eppbulk_read (s->fd, sizeof (read_gamma));
+      read (s->fd, read_gamma, sizeof (read_gamma));
+      retval = memcmp (read_gamma, gamma, sizeof (read_gamma));
+      if (retval != 0)
+	{
+	  fprintf (stderr, "error: color %d has bad gamma table\n", color);
+	}
+#ifdef DEBUG
+      else
+	fprintf (stderr, "color %d gamma table is good\n", color);
+#endif
+    }
+  
+  return 0;
+}
+
+static int
+write_default_offset_gain (HP4200_Scanner *s, char *gain_offset,
+			   int size, int color)
+{
+  char *check_data;
+  int retval;
+
+  setreg (s, 0x03, (color << 1) | 1);
+  setreg (s, 0x04, DATA_PORT_WRITE);
+  setreg (s, 0x05, 0x00);
+  pv8630_write_byte (s->fd, 0x06, PV8630_REPPADDRESS);
+  pv8630_init_eppbulk_write (s->fd, size);
+  write (s->fd, gain_offset, size);
+
+  check_data = malloc (size);
+  setreg (s, 0x03, (color << 1) | 1);
+  setreg (s, 0x04, DATA_PORT_READ);
+  setreg (s, 0x05, 0x00);
+  pv8630_write_byte (s->fd, 0x06, PV8630_REPPADDRESS);
+  pv8630_init_eppbulk_read (s->fd, size);
+  read (s->fd, check_data, size);
+  retval = memcmp (gain_offset, check_data, size);
+  free (check_data);
+  if (retval != 0)
+    {
+      fprintf (stderr, "error: color %d has bad gain/offset table\n", color);
+    }
+#ifdef DEBUG
+  else
+    fprintf (stderr, "color %d gain/offset table is good\n", color);
+#endif
+
+ return 0;
+}
+
+static int
+compute_gain_offset (int target, int max, int min, int *gain,
+		     int *offset, int *max_gain, int *min_offset)
+{
+  int gain_stable;
+  int is_unstable;
+
+  gain_stable = 1;		/* unless the opposite is said */
+  is_unstable = 0;
+
+  if (max > target)
+    {
+      if (*gain > 0)
+	{
+	  (*gain)--;
+	  *max_gain = *gain;
+	  gain_stable = 0;
+	  is_unstable |= 1;
+	}
+      else
+	{
+	  fprintf (stderr, "error: integration time too long.\n");
+	  return -1;
+	}
+    }
+  else
+    {
+      if (*gain < *max_gain)
+	{
+	  (*gain)++;
+	  gain_stable = 0;
+	  is_unstable |= 1;
+	}
+    }
+
+  if (min == 0)
+    {
+      if (*offset < 31)
+	{
+	  (*offset)++;
+	  if (gain_stable)
+	    *min_offset = *offset;
+	  is_unstable |= 1;
+	}
+      else
+	{
+	  fprintf (stderr, "error: max static has pixel value == 0\n");
+	  return -1;
+	}
+    }
+  else
+    {
+      if (*offset > *min_offset)
+	{
+	  (*offset)--;
+	  is_unstable |= 1;
+	}
+    }
+  return is_unstable;
+}
+
+static int
+compute_bytes_per_line (int width_in_pixels, unsigned char hdpi_code,
+			unsigned char pixel_packing, 
+			unsigned char data_mode,
+			unsigned char AFE_operation, int m)
+{
+  const int dpi_qot_mul[] = {1, 2, 1, 1, 1, 1, 1, 1}; 
+  const int dpi_qot_div[] = {1, 3, 2, 3, 4, 6, 8, 12};
+  int pixels_per_line;
+  int bytes_per_line;
+  int pixels_per_byte;
+  int status_bytes;
+  const int pixels_per_byte_mapping[] = {8, 4, 2, 1};
+
+  assert (hdpi_code <= 7);
+  pixels_per_line = (width_in_pixels * dpi_qot_mul[hdpi_code]) /
+    dpi_qot_div[hdpi_code];
+  if ((width_in_pixels * dpi_qot_mul[hdpi_code]) %
+      dpi_qot_div[hdpi_code])
+    pixels_per_line++;
+  
+
+  status_bytes = (m == 0) ? 1 : m;
+
+  if (data_mode == 1)
+    pixels_per_byte = 1;	/* should be 0.5 but later
+				   bytes_per_line will be multiplied
+				   by 2, and also the number of status
+				   bytes, that in this case should be
+				   2.
+				   umm.. maybe this should be done in
+				   the cleaner way.
+				*/
+  else
+    {
+      assert (pixel_packing <= 3);
+      pixels_per_byte = pixels_per_byte_mapping[pixel_packing];
+    }
+
+  switch (AFE_operation)
+    {
+    case PIXEL_RATE_3_CHANNELS:
+      bytes_per_line = ((pixels_per_line * 3) / pixels_per_byte) +
+	status_bytes;
+      break;
+    case MODEA_1_CHANNEL:
+      bytes_per_line = (pixels_per_line / pixels_per_byte) + status_bytes;
+      break;
+    default:
+      fputs ("error: not implemented! (yet?)", stderr);
+      exit (-1);
+    }
+
+  if (data_mode == 1)		/* see big note above */
+    bytes_per_line *= 2;
+
+  return bytes_per_line;
+}
+
+static int
+compute_pause_limit (hardware_parameters_t *hw_parms, int bytes_per_line)
+{
+  int coef_size;
+  const int coef_mapping[] = {16, 32};
+  int pause_limit;
+  
+  coef_size = coef_mapping[hw_parms->sensor_resolution & 0x01];
+  pause_limit = hw_parms->SRAM_size - coef_size -
+    (bytes_per_line / 1024) - 1; 
+
+#ifdef PROBLEMS_WITH_PAUSE_LIMIT
+  if (pause_limit > 2)
+    pause_limit--; /* or try -= 2 */
+#endif
+
+  return pause_limit;
+}
+
+static int
+compute_dpd (HP4200_Scanner *s, int step_size, int line_end)
+{
+  int tr, dpd;
+  
+  tr = 1 /* color mode */ *
+    (line_end + ((s->hw_parms.num_tr_pulses + 1) *
+		 (2 * s->hw_parms.guard_band_duration +
+		  s->hw_parms.pulse_duration + 1) + 
+		 3 - s->hw_parms.num_tr_pulses));
+  
+  dpd = (((s->hw_parms.fsteps_25_speed * 4) +
+	  (s->hw_parms.fsteps_50_speed * 2) +
+	 s->hw_parms.steps_to_reverse) * 4 * step_size) %
+    tr;
+  dpd = (tr == 0) ? 0 : tr - dpd;
+  
+  return dpd;
+}
+
+SANE_Status
+read_required_bytes (HP4200_Scanner *s, int required, char *buffer)
+{
+  int read_count = 0;
+  unsigned char scankb1;
+  unsigned char scankb2;
+  int to_read;
+  int really_read;
+  int chunk;
+
+  assert (buffer != NULL);
+
+  while (required)
+    {
+      do
+	{
+	  scankb1 = getreg (s, 0x01);
+	  scankb2 = getreg (s, 0x01);
+	  if (s->aborted_by_user)
+	    return SANE_STATUS_CANCELLED;
+	}
+      while ((scankb1 != scankb2) || (scankb1 < 12));
+
+      to_read = min (required, (scankb1 * 1024));
+      while (to_read)
+	{
+	  if (s->aborted_by_user)
+	    return SANE_STATUS_CANCELLED;
+	  chunk = (to_read > 0xffff) ? 0xffff : to_read;
+
+	  pv8630_write_byte (s->fd, 0x00, PV8630_REPPADDRESS);
+	  pv8630_init_eppbulk_read (s->fd, chunk);
+	  really_read = read (s->fd, buffer, chunk);
+	  if (really_read < 0)
+	    {
+	      perror ("read");
+	      return -1;
+	    }
+	  if (really_read > chunk)
+	    {
+	      error ("USB stack read more bytes than requested!");
+	      return -1;
+	    }
+	  buffer += really_read;
+	  required -= really_read;
+	  to_read -= really_read;
+	  read_count += really_read;
+	}
+    }
+
+  return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+scanner_buffer_init (scanner_buffer_t *sb, int size_in_kb)
+{
+
+  sb->size = size_in_kb * 1024 + 3;
+  sb->buffer = malloc (sb->size);
+  if (!sb->buffer)
+    return SANE_STATUS_NO_MEM;
+  sb->num_bytes = 0;
+  sb->data_ptr = sb->buffer;
+
+  return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+scanner_buffer_read (HP4200_Scanner *s)
+{
+  SANE_Status status;
+  int num_bytes_read_now;
+
+  assert (s->scanner_buffer.num_bytes <= 3);
+
+  memcpy (s->scanner_buffer.buffer, s->scanner_buffer.data_ptr, 3);
+  
+  status = read_available_data (s, s->scanner_buffer.buffer +
+				s->scanner_buffer.num_bytes,
+				&num_bytes_read_now);
+  s->scanner_buffer.data_ptr = s->scanner_buffer.buffer;
+  s->scanner_buffer.num_bytes += num_bytes_read_now;
+  return status;
+}
+
+#define OFFSET_CODE_SIGN(off) (((off) < 0) ? (-(off) & 0x1f) | 0x20 : (off))
+#define OFFSET_DECODE_SIGN(off) (((off) & 0x20) ? -(off & 0x1f) : (off))
+
+SANE_Status
+do_coarse_calibration (HP4200_Scanner *s, struct coarse_t *coarse)
+{
+  SANE_Status status;
+  unsigned char *cal_line;
+  unsigned char *cal_line_ptr;
+  int cal_line_size;
+  /* local scanning params */
+  int active_pixels_start;
+  int line_end;
+  int data_pixels_start;
+  int data_pixels_end;
+  int dpd;
+  int step_size;
+  int ff_step_size;
+  char steps_to_reverse;
+  char hdpi_div;
+  char line_rate_color;
+  int vdpi;			/* vertical dots per inch */
+  int hdpi_code;
+  int calibrated;
+  int first_time;
+  
+  int red_offset = 0;
+  int green_offset = 0;
+  int blue_offset = 0;
+      
+  int red_gain = 1;
+  int green_gain = 1;
+  int blue_gain = 1;
+
+  int min_red_offset = -31;
+  int min_green_offset = -31;
+  int min_blue_offset = -31;
+  
+  int max_red_gain = 63;
+  int max_green_gain = 63;
+  int max_blue_gain = 63;
+
+  int max_red;
+  int min_red;
+  int max_green;
+  int min_green;
+  int max_blue;
+  int min_blue;
+  static char me[] = "do_coarse_calibration";
+
+  DBG (DL_CALL_TRACE, "%s\n", me);
+
+  setreg (s, 0x07, 0x00);
+  usleep (10 * 1000);
+  
+  vdpi = 150;
+  hdpi_code = 0;
+  hdpi_div = hdpi_mapping[hdpi_code];
+  active_pixels_start = 0x40;
+  line_end = 0x2ee0;
+  s->mclk_div = 2;
+  data_pixels_start = 0x40;
+  data_pixels_end = (int) (data_pixels_start + s->hw_parms.scan_area_width);
+  data_pixels_end = min(data_pixels_end, line_end - 20);
+
+  cal_line_size = s->hw_parms.scan_area_width * 3 * 2 + 2;
+
+  setreg (s, 0x1e, HIBYTE(active_pixels_start));
+  setreg (s, 0x1f, LOBYTE(active_pixels_start));
+  setreg (s, 0x20, HIBYTE(line_end));
+  setreg (s, 0x21, LOBYTE(line_end));
+  setreg (s, 0x22, HIBYTE(data_pixels_start));
+  setreg (s, 0x23, LOBYTE(data_pixels_start));
+  setreg (s, 0x24, HIBYTE(data_pixels_end));
+  setreg (s, 0x25, LOBYTE(data_pixels_end));
+  
+  setreg (s, 0x26,
+	  PIXEL_RATE_3_CHANNELS |
+	  GRAY_CHANNEL_RED |
+	  TR_RED(0) | TR_GREEN(0) | TR_BLUE(0));
+
+
+  setreg (s, 0x08, (s->mclk_div - 1) * 2);
+  setreg (s, 0x09, hdpi_code | PIXEL_PACKING(3) | DATAMODE(1));
+  setreg (s, 0x0a, 0);		/* reserved and strange register */
+
+  setreg (s, 0x38, red_offset);
+  setreg (s, 0x39, green_offset);
+  setreg (s, 0x3a, blue_offset);
+  setreg (s, 0x3b, red_gain);
+  setreg (s, 0x3c, green_gain);
+  setreg (s, 0x3d, blue_gain);
+
+  setreg (s, 0x5e, 0x80);
+
+  setreg (s, 0x3e, 0x00);	/* 1.5:1, 6/10 bits, 2*fixed */
+  setreg (s, 0x3f, 0x00);
+  setreg (s, 0x40, 0x00);
+  setreg (s, 0x41, 0x00);
+
+  setreg (s, 0x4e, 0x5b - 0x3c); /* max Kb to pause */
+  setreg (s, 0x4f, 0x02);	/* min Kb to resume */
+
+  line_rate_color = 1;
+  step_size = (vdpi * line_end * line_rate_color) /
+    (4 * s->hw_parms.motor_full_steps_per_inch);
+
+  dpd = compute_dpd (s, step_size, line_end); /* 0x0ada; */
+#ifdef DEBUG
+  fprintf (stderr, "dpd = %d\n", dpd);
+#endif
+  setreg (s, 0x52, HIBYTE(dpd));
+  setreg (s, 0x53, LOBYTE(dpd));
+
+  setreg (s, 0x46, HIBYTE(step_size));
+  setreg (s, 0x47, LOBYTE(step_size));
+
+  ff_step_size = compute_fastfeed_step_size (s->hw_parms.crystal_frequency,
+					     s->mclk_div,
+					     s->hw_parms.motor_max_speed,
+					     s->hw_parms.motor_full_steps_per_inch,
+					     0); /* 0x0190; */
+  setreg (s, 0x48, HIBYTE(ff_step_size));
+  setreg (s, 0x49, LOBYTE(ff_step_size));
+  setreg (s, 0x4b, 0x15);
+  steps_to_reverse = 0x3f;
+  setreg (s, 0x50, steps_to_reverse);
+  setreg (s, 0x51, 0x15);	/* accel profile */
+
+  /* this is to stay the motor stopped */
+  clearbits (s, 0x45, (1 << 4));
+
+  cache_write (s);
+
+  calibrated = 0;
+  first_time = 1;
+  cal_line = malloc (cal_line_size + 1024);
+
+  do {
+    unsigned char cmd_reg;
+
+    /* resets the lm983x before start scanning */
+    setreg (s, 0x07, 0x08);
+    do
+      {
+	setreg (s, 0x07, 0x03);
+	cmd_reg = getreg (s, 0x07);
+      }
+    while (cmd_reg != 0x03);
+
+    cal_line_ptr = cal_line;
+    status = read_required_bytes (s, cal_line_size, cal_line_ptr);
+    if (status != SANE_STATUS_GOOD)
+      return status;
+
+    setreg (s, 0x07, 0x00);
+    {
+      unsigned int i;
+      min_red = max_red = (cal_line[0] * 256 + cal_line[1]) >> 2;
+      min_green = max_green = (cal_line[2] * 256 + cal_line[3]) >> 2;
+      min_blue = max_blue = (cal_line[4] * 256 + cal_line[5]) >> 2;
+      for (i = 6; i < (s->hw_parms.scan_area_width * 3 * 2); i += 6)
+	{
+	  int value;
+
+	  value = cal_line[i] * 256 + cal_line[i + 1];
+	  value >>= 2;
+	  if (value > max_red)
+	    max_red = value;
+	  value = cal_line[i + 2] * 256 + cal_line[i + 3];
+	  value >>= 2;
+	  if (value > max_green)
+	    max_green = value;
+	  value = cal_line[i + 4] * 256 + cal_line[i + 5];
+	  value >>= 2;
+	  if (value > max_blue)
+	    max_blue = value;
+	  value = cal_line[i] * 256 + cal_line[i + 1];
+	  value >>= 2;
+	  if (value < min_red)
+	    min_red = value;
+	  value = cal_line[i + 2] * 256 + cal_line[i + 3];
+	  value >>= 2;
+	  if (value < min_green)
+	    min_green = value;
+	  value = cal_line[i + 4] * 256 + cal_line[i + 5];
+	  value >>= 2;
+	  if (value < min_blue)
+	    min_blue = value;
+	}
+#ifdef DEBUG
+      fprintf (stderr, "max_red:%d max_green:%d max_blue:%d\n",
+	       max_red, max_green, max_blue);
+      fprintf (stderr, "min_red:%d min_green:%d min_blue:%d\n",
+	       min_red, min_green, min_blue);
+#endif
+      
+      if (first_time)
+	{
+	  first_time = 0;
+	  compute_first_gain_offset (s->hw_parms.target_value.red,
+				     max_red, min_red,
+				     &red_gain, &red_offset,
+				     &max_red_gain, &min_red_offset);
+	  compute_first_gain_offset (s->hw_parms.target_value.green,
+				     max_green, min_green,
+				     &green_gain, &green_offset,
+				     &max_green_gain, &min_green_offset);
+	  compute_first_gain_offset (s->hw_parms.target_value.blue,
+				     max_blue, min_blue,
+				     &blue_gain, &blue_offset,
+				     &max_blue_gain, &min_blue_offset);
+	}
+      else
+	{
+	  int retval;
+
+	  /* this code should check return value -1 for error */
+
+	  retval = compute_gain_offset (s->hw_parms.target_value.red,
+					max_red, min_red,
+					&red_gain, &red_offset,
+					&max_red_gain, &min_red_offset);
+	  if (retval < 0)
+	    break;
+	  retval |= compute_gain_offset (s->hw_parms.target_value.green,
+					 max_green, min_green,
+					 &green_gain, &green_offset,
+					 &max_green_gain, &min_green_offset);
+	  if (retval < 0)
+	    break;
+	  retval |= compute_gain_offset (s->hw_parms.target_value.blue,
+					 max_blue, min_blue,
+					 &blue_gain, &blue_offset,
+					 &max_blue_gain, &min_blue_offset);
+	  if (retval < 0)
+	    break;
+	  calibrated = !retval;
+	}
+
+      setreg (s, 0x3b, red_gain);
+      setreg (s, 0x3c, green_gain);
+      setreg (s, 0x3d, blue_gain);
+      
+      setreg (s, 0x38, OFFSET_CODE_SIGN(red_offset));
+      setreg (s, 0x39, OFFSET_CODE_SIGN(green_offset));
+      setreg (s, 0x3a, OFFSET_CODE_SIGN(blue_offset));
+
+#ifdef DEBUG
+      fprintf (stderr, "%d, %d, %d   %d, %d, %d\n", red_gain, green_gain,
+	       blue_gain, red_offset, green_offset, blue_offset);
+#endif
+      cache_write (s);
+    }
+  } while (!calibrated);
+  coarse->min_red = min_red;
+  coarse->min_green = min_green;
+  coarse->min_blue = min_blue;
+  coarse->max_red = max_red;
+  coarse->max_green = max_green;
+  coarse->max_blue = max_blue;
+  coarse->red_gain = red_gain;
+  coarse->green_gain = green_gain;
+  coarse->blue_gain = blue_gain;
+  coarse->red_offset = red_offset;
+  coarse->green_offset = green_offset;
+  coarse->blue_offset = blue_offset;
+
+  return SANE_STATUS_GOOD;
+}
+
+static int
+compute_corr_code (int average, int min_color, int range, int target)
+{
+  int value;
+  int corr_code;
+
+  value = average - min_color;
+  if (value > 0)
+    corr_code = (int) (range * ((double)target / (double)value - 1.0) + 0.5);
+  else
+    corr_code = 0;
+  if (corr_code < 0)
+    corr_code = 0;
+  else if (corr_code > 2048)
+    corr_code = 0;
+  else if (corr_code > 1023)
+    corr_code = 1023;
+  return corr_code;
+}
+
+static int
+compute_hdpi_code (int hres)
+{
+  int hdpi_code;
+
+  /* Calculate the horizontal DPI code based on the requested
+     horizontal resolution.  Defaults to 150dpi.  */
+  switch(hres)
+    {
+    case 600:
+      hdpi_code = 0;
+      break;
+    case 400:
+      hdpi_code = 1;
+      break;
+    case 300:
+      hdpi_code = 2;
+      break;
+    case 200:
+      hdpi_code = 3;
+      break;
+    case 150:
+      hdpi_code = 4;
+      break;
+    case 100:
+      hdpi_code = 5;
+      break;
+    case 75:
+      hdpi_code = 6;
+      break;
+    case 50:
+      hdpi_code = 7;
+      break;
+    default:
+      hdpi_code = 4;
+    }
+  return hdpi_code;
+}
+
+
+SANE_Status
+do_fine_calibration (HP4200_Scanner *s, struct coarse_t *coarse)
+{
+  SANE_Status status;
+  unsigned char *cal_line;
+  unsigned char *cal_line_ptr;
+  int *average;
+  char red_gain_offset[5460 * 2];
+  char green_gain_offset[5460 * 2];
+  char blue_gain_offset[5460 * 2];
+  int *corr_red;
+  int *corr_green;
+  int *corr_blue;
+  int registro[30][5460 * 3];
+  int cal_line_size;
+  /* local scanning params */
+  int active_pixels_start;
+  int line_end;
+  int line_length;
+  int data_pixels_start;
+  int data_pixels_end;
+  int dpd;
+  int step_size;
+  int ff_step_size;
+  char steps_to_reverse;
+  char hdpi_div;
+  char line_rate_color;
+  int vdpi;			/* vertical dots per inch */
+  int hdpi_code;
+  int calibrated;
+  int first_time;
+  int lines_to_process;
+  
+  static char me[] = "do_fine_calibration";
+
+  DBG (DL_CALL_TRACE, "%s\n", me);
+
+  setreg (s, 0x07,0x00);
+  usleep (10 * 1000);
+  
+  vdpi = 150;
+  hdpi_code = compute_hdpi_code (s->user_parms.horizontal_resolution);
+
+  /* figure out which horizontal divider to use based on the
+     calculated horizontal dpi code */
+  hdpi_div = hdpi_mapping[hdpi_code];
+  active_pixels_start = 0x40;
+  line_end = 0x2ee0;
+  line_length = s->user_parms.image_width * hdpi_div;
+  s->mclk_div = 2;
+  data_pixels_start = 0x72 + s->runtime_parms.first_pixel * hdpi_div;
+  data_pixels_end = (int) (data_pixels_start + s->user_parms.image_width * hdpi_div);
+  data_pixels_end = min(data_pixels_end, line_end - 20);
+
+  cal_line_size = line_length * 3 * 2 + 2;
+
+  setreg (s, 0x1e, HIBYTE(active_pixels_start));
+  setreg (s, 0x1f, LOBYTE(active_pixels_start));
+  setreg (s, 0x20, HIBYTE(line_end));
+  setreg (s, 0x21, LOBYTE(line_end));
+  setreg (s, 0x22, HIBYTE(data_pixels_start));
+  setreg (s, 0x23, LOBYTE(data_pixels_start));
+  setreg (s, 0x24, HIBYTE(data_pixels_end));
+  setreg (s, 0x25, LOBYTE(data_pixels_end));
+  
+  setreg (s, 0x26,
+	  PIXEL_RATE_3_CHANNELS |
+	  GRAY_CHANNEL_RED |
+	  TR_RED(0) | TR_GREEN(0) | TR_BLUE(0));
+
+
+  setreg (s, 0x08, (s->mclk_div - 1) * 2);
+  setreg (s, 0x09, 0 | PIXEL_PACKING(3) | DATAMODE(1));
+  setreg (s, 0x0a, 0);		/* reserved and strange register */
+
+  setreg (s, 0x38, 1);
+  setreg (s, 0x39, 1);
+  setreg (s, 0x3a, 1);
+  setreg (s, 0x3b, coarse->red_gain);
+  setreg (s, 0x3c, coarse->green_gain);
+  setreg (s, 0x3d, coarse->blue_gain);
+
+  setreg (s, 0x5e, 0x80);
+
+  setreg (s, 0x3e, 0x00);	/* 1.5:1, 6/10 bits, 2*fixed */
+  setreg (s, 0x3f, 0x00);
+  setreg (s, 0x40, 0x00);
+  setreg (s, 0x41, 0x00);
+
+  setreg (s, 0x4e, 0x5b - 0x3c); /* max Kb to pause */
+  setreg (s, 0x4f, 0x02);	/* min Kb to resume */
+
+  line_rate_color = 1;
+  step_size = (vdpi * line_end * line_rate_color) /
+    (4 * s->hw_parms.motor_full_steps_per_inch);
+
+  dpd = compute_dpd (s, step_size, line_end); /* 0x0ada; */
+#ifdef DEBUG
+  fprintf (stderr, "dpd = %d\n", dpd);
+#endif
+  setreg (s, 0x52, HIBYTE(dpd));
+  setreg (s, 0x53, LOBYTE(dpd));
+
+  setreg (s, 0x46, HIBYTE(step_size));
+  setreg (s, 0x47, LOBYTE(step_size));
+
+  ff_step_size = compute_fastfeed_step_size (s->hw_parms.crystal_frequency,
+					     s->mclk_div,
+					     s->hw_parms.motor_max_speed,
+					     s->hw_parms.motor_full_steps_per_inch,
+					     0); /* 0x0190; */
+  setreg (s, 0x48, HIBYTE(ff_step_size));
+  setreg (s, 0x49, LOBYTE(ff_step_size));
+  setreg (s, 0x4b, 0x15);
+  steps_to_reverse = 0x3f;
+  setreg (s, 0x50, steps_to_reverse);
+  setreg (s, 0x51, 0x15);	/* accel profile */
+
+  /* this is to activate the motor */
+  setbits (s, 0x45, (1 << 4));
+
+  lines_to_process = 8 * step_size * 4 / line_end;
+  if (lines_to_process < 1)
+    lines_to_process = 1;
+
+#ifdef DEBUG
+  fprintf (stderr, "lines to process = %d\n", lines_to_process);
+#endif
+
+  setreg (s, 0x58, 0);
+
+  cache_write (s);
+
+  calibrated = 0;
+  first_time = 1;
+  cal_line = malloc (cal_line_size + 1024);
+  average = malloc (sizeof (int) * line_length * 3);
+  memset (average, 0, sizeof (int) * line_length * 3);
+  { int i;
+  for (i = 0; i < 12; i++)
+    {
+      memset (registro[i], 0, 5460 * 3);
+    }
+  }
+
+  /* resets the lm983x before start scanning */
+  setreg (s, 0x07, 0x08);
+  setreg (s, 0x07, 0x03);
+    
+  usleep(100);
+
+  do {
+
+    cal_line_ptr = cal_line;
+
+    status = read_required_bytes (s, cal_line_size, cal_line_ptr);
+    if (status != SANE_STATUS_GOOD)
+      return status;
+    {
+      int i, j;
+
+      if (calibrated == 0)
+	for (j = 0, i = 0; i < (line_length * 3); i++, j += 2)
+	  {
+	    average[i] = (cal_line[j] * 256 + cal_line[j + 1]) >> 2;
+	    registro[calibrated][i] = average[i];
+	  }
+      else
+	for (j = 0, i = 0; i < (line_length * 3); i++, j += 2)
+	  {
+	    int value;
+	    value = (cal_line[j] * 256 + cal_line[j + 1]) >> 2;
+	    average[i] += value;
+	    average[i] /= 2;
+	    registro[calibrated][i] = value;
+	  }
+    }
+    calibrated++;
+  } while (calibrated < lines_to_process);
+  lm983x_write_register (s->fd, 0x07, 0x00);
+  usleep (10 * 1000);
+
+#if 0
+  {
+    int i;
+    int j = 0;
+    do {
+      for (i = 3; (i+6) < (line_length * 3); i += 3)
+	{
+	  average[i] = (2*average[i-3] + average[i] + 2*average[i+3]) / 5; 
+	  average[i+1] = (2*average[i-2] + average[i+1] + 2*average[i+4]) / 5;
+	  average[i+2] = (2*average[i-1] + average[i+2] + 2*average[i+5]) / 5;
+	}
+      j++;
+    } while (j < 3);
+  }
+#endif
+  {
+    int i;
+    int max_red;
+    int min_red;
+    int max_green;
+    int min_green;
+    int max_blue;
+    int min_blue;
+    min_red = max_red = average[0];
+    min_green = max_green = average[1];
+    min_blue = max_blue = average[2];
+    for (i = 3; i < (line_length * 3); i += 3)
+      {
+	int value;
+
+	value = average[i];
+	if (value > max_red)
+	  max_red = value;
+	value = average[i + 1];
+	if (value > max_green)
+	  max_green = value;
+	value = average[i + 2];
+	if (value > max_blue)
+	  max_blue = value;
+	value = average[i];
+	if (value < min_red)
+	  min_red = value;
+	value = average[i + 1];
+	if (value < min_green)
+	  min_green = value;
+	value = average[i + 2];
+	if (value < min_blue)
+	  min_blue = value;
+      }
+#ifdef DEBUG
+    fprintf (stderr, "max_red:%d max_green:%d max_blue:%d\n",
+	     max_red, max_green, max_blue);
+    fprintf (stderr, "min_red:%d min_green:%d min_blue:%d\n",
+	     min_red, min_green, min_blue);
+#endif
+      
+    /* do fine calibration */
+    {
+      int min_white_red;
+      int min_white_green;
+      int min_white_blue;
+      double ratio;
+      int range;
+      double aux;
+      int min_white_err;
+      int j;
+
+      min_white_red = min_white_green = min_white_blue = 0x3ff;
+      for (i = 0; i < (line_length * 3); i += 3)
+	{
+	  int value;
+
+	  value = average[i] - coarse->min_red;
+	  if ((value > 0) && (value < min_white_red))
+	    min_white_red = value;
+	  value = average[i + 1] - coarse->min_green;
+	  if ((value > 0) && (value < min_white_green))
+	    min_white_green = value;
+	  value = average[i + 2] - coarse->min_blue;
+	  if ((value > 0) && (value < min_white_blue))
+	    min_white_blue = value;
+	}
+
+      ratio = 0;
+      min_white_err = 0x3ff;
+
+      aux = (double) s->hw_parms.target_value.red / min_white_red;
+      if (aux > ratio)
+	ratio = aux;
+      if (min_white_err > min_white_red)
+	min_white_err = min_white_red;
+      aux = (double) s->hw_parms.target_value.green / min_white_green;
+      if (aux > ratio)
+	ratio = aux;
+      if (min_white_err > min_white_green)
+	min_white_err = min_white_green;
+      aux = (double) s->hw_parms.target_value.blue / min_white_blue;
+      if (aux > ratio)
+	ratio = aux;
+      if (min_white_err > min_white_blue)
+	min_white_err = min_white_blue;
+
+#ifdef DEBUG
+      fprintf (stderr, "min_white_err = %d, ratio = %f\n", min_white_err,
+	       ratio);
+#endif
+      if (ratio <= 1.5)
+	range = 2048;
+      else if (ratio <= 2.0)
+	range = 1024;
+      else
+	range = 512;
+
+      corr_red = malloc (sizeof(int) * line_length);
+      corr_green = malloc (sizeof(int) * line_length);
+      corr_blue = malloc (sizeof(int) * line_length);
+
+      for (i = 0, j = 0; i < (line_length * 3); i += 3, j++)
+	{
+	  corr_red[j] = compute_corr_code (average[i],
+					   coarse->min_red,
+					   range,
+					   s->hw_parms.target_value.red);
+	  corr_green[j] = compute_corr_code (average[i + 1],
+					     coarse->min_green,
+					     range,
+					     s->hw_parms.target_value.green);
+	  corr_blue[j] = compute_corr_code (average[i + 2],
+					    coarse->min_blue,
+					    range,
+					    s->hw_parms.target_value.blue);
+	}
+#ifdef DEBUG
+      {
+	FILE *kaka;
+	int i;
+	kaka = fopen ("corr.raw", "w");
+	for (i = 0; i < line_length; i++)
+	  {
+	    fprintf (kaka, "%d %d %d %d %d %d ",
+		     corr_red[i], corr_green[i], corr_blue[i],
+		     average[3 * i], average[3 * i + 1],
+		     average[3 * i + 2]);
+	    fprintf (kaka, "%d %d %d  %d %d %d  %d %d %d ",
+		     registro[0][3*i], registro[0][3*i+1], registro[0][3*i+2], 
+		     registro[1][3*i], registro[1][3*i+1], registro[1][3*i+2], 
+		     registro[2][3*i], registro[2][3*i+1], registro[2][3*i+2]
+		     );
+	    fprintf (kaka, "%d %d %d  %d %d %d  %d %d %d\n",
+		     registro[3][3*i], registro[3][3*i+1], registro[3][3*i+2], 
+		     registro[4][3*i], registro[4][3*i+1], registro[4][3*i+2], 
+		     registro[5][3*i], registro[5][3*i+1], registro[5][3*i+2]
+		     );
+	  }
+	fclose (kaka);
+      }
+#endif
+      {
+	int max_black;
+	int use_six_eight_bits;
+
+	max_black = max(coarse->min_red, coarse->min_green);
+	max_black = max(max_black, coarse->min_blue);
+	use_six_eight_bits = (max_black < 64);
+	  
+	if (use_six_eight_bits)
+	  {
+	    setreg (s, 0x3e, (1 << 4) | (1 << 3) | (1024 / range));
+	  }
+	else
+	  {
+	    setreg (s, 0x3e, (1 << 4) | (1 << 3) | (1 << 2) | (1024 /
+							      range));
+	  }
+	for (i = 0, j = (data_pixels_start - active_pixels_start) * 2;
+	     i < line_length; i++, j += 2)
+	  {
+	    if (use_six_eight_bits)
+	      {
+		red_gain_offset[j] = (coarse->min_red << 2) |
+		  ((corr_red[i] >> 8) & 0x03);
+		red_gain_offset[j + 1] = corr_red[i] & 0xff;
+		green_gain_offset[j] = (coarse->min_green << 2) |
+		  ((corr_green[i] >> 8) & 0x03);
+		green_gain_offset[j + 1] = corr_green[i] & 0xff;
+		blue_gain_offset[j] = (coarse->min_blue << 2) |
+		  ((corr_blue[i] >> 8) & 0x03);
+		blue_gain_offset[j + 1] = corr_blue[i] & 0xff;
+	      }
+	    else
+	      {
+		red_gain_offset[j] = coarse->min_red;
+		red_gain_offset[j + 1] = corr_red[j] >> 2;
+		green_gain_offset[j] = coarse->min_green;
+		green_gain_offset[j + 1] = corr_green[j] >> 2;
+		blue_gain_offset[j] = coarse->min_blue;
+		blue_gain_offset[j + 1] = corr_blue[j] >> 2;
+	      }
+	  }
+	write_default_offset_gain (s, red_gain_offset,
+				   5460 * 2, 0);
+	write_default_offset_gain (s, green_gain_offset,
+				   5460 * 2, 1);
+	write_default_offset_gain (s, blue_gain_offset,
+				   5460 * 2, 2);
+      }
+    }
+  }
+
+  return 0;
+}
+
+static void
+ciclic_buffer_init_offset_correction (ciclic_buffer_t *cb, int vres)
+{
+  cb->blue_idx = 0;
+  if ((vres == 600) || (vres == 1200 /* ans using n out of m */ ))
+    {
+      cb->green_idx = 4;
+      cb->red_idx = 8;
+      cb->first_good_line = 8;
+    }
+  else if (vres == 300)
+    {
+      cb->green_idx = 2;
+      cb->red_idx = 4;
+      cb->first_good_line = 4;
+    }
+  else if (vres == 150)
+    {
+      cb->green_idx = 1;
+      cb->red_idx = 2;
+      cb->first_good_line = 2;
+    }
+  else
+    {
+      cb->green_idx = 0;
+      cb->red_idx = 0;
+      cb->first_good_line = 0;
+    }
+
+  cb->buffer_position = cb->buffer_ptrs[cb->first_good_line];
+}
+
+static SANE_Status
+ciclic_buffer_init (ciclic_buffer_t *cb, SANE_Int bytes_per_line,
+		    int vres, int status_bytes)
+{
+  cb->good_bytes = 0;
+  cb->num_lines = 12;
+  cb->size = bytes_per_line * cb->num_lines;
+  cb->can_consume = cb->size +
+    cb->num_lines * status_bytes;
+
+  cb->buffer = malloc (cb->size);
+  if (!cb->buffer)
+    return SANE_STATUS_NO_MEM;
+
+  {
+    int i;
+    unsigned char *buffer;
+    unsigned char **ptrs;
+
+    ptrs = cb->buffer_ptrs = (unsigned char **)
+      malloc (sizeof (unsigned char *) * cb->num_lines);
+    if (!cb->buffer_ptrs)
+      return SANE_STATUS_NO_MEM;
+
+    buffer = cb->buffer;
+    for (i = 0; i < cb->num_lines; i++)
+      {
+	ptrs[i] = buffer;
+	buffer += bytes_per_line;
+      }
+  }
+
+  cb->current_line = 0;
+  cb->pixel_position = 0;
+  ciclic_buffer_init_offset_correction (cb, vres);
+
+  return SANE_STATUS_GOOD;
+}
+
+int
+prepare_for_a_scan (HP4200_Scanner *s)
+{
+  /* local scanning params */
+  int active_pixels_start;
+  int line_end;
+  int data_pixels_start;
+  int data_pixels_end;
+  int ff_step_size;
+  int dpd;
+  int step_size;
+  char steps_to_reverse;
+  char hdpi_div;
+  char line_rate_color;
+  int hdpi_code;
+  unsigned char pixel_packing;
+  unsigned char data_mode;
+  unsigned char AFE_operation;
+  int pause_limit;
+  int n = 0, m = 0;
+
+  setreg (s, 0x07,0x00);
+  usleep (10 * 1000);
+
+  hdpi_code = compute_hdpi_code (s->user_parms.horizontal_resolution);
+  /* figure out which horizontal divider to use based on the
+     calculated horizontal dpi code */
+  hdpi_div = hdpi_mapping[hdpi_code]; 
+
+  /* image_width is set to the correct number of pixels by calling 
+     fxn.  This might be the reason we can't do high res full width
+     scans though...not sure.  */
+  /*s->user_parms.image_width /= 4;*/
+  active_pixels_start = 0x40;
+  line_end = 0x2ee0; /* 2ee0 */
+  s->mclk_div = 2;
+  data_pixels_start = 0x72 + s->runtime_parms.first_pixel * hdpi_div;
+  data_pixels_end = (int) (data_pixels_start + s->user_parms.image_width * hdpi_div);
+  data_pixels_end = min(data_pixels_end, line_end - 20);
+  setreg (s, 0x1e, HIBYTE(active_pixels_start));
+  setreg (s, 0x1f, LOBYTE(active_pixels_start));
+  setreg (s, 0x20, HIBYTE(line_end));
+  setreg (s, 0x21, LOBYTE(line_end));
+  setreg (s, 0x22, HIBYTE(data_pixels_start));
+  setreg (s, 0x23, LOBYTE(data_pixels_start));
+  setreg (s, 0x24, HIBYTE(data_pixels_end));
+  setreg (s, 0x25, LOBYTE(data_pixels_end));
+  
+  AFE_operation = PIXEL_RATE_3_CHANNELS;
+  setreg (s, 0x26,
+	  AFE_operation |
+	  GRAY_CHANNEL_RED |
+	  TR_RED(0) | TR_GREEN(0) | TR_BLUE(0));
+
+  setreg (s, 0x08, (s->mclk_div - 1) * 2);
+  pixel_packing = 3;
+  data_mode = 0;
+  setreg (s, 0x09, hdpi_code | PIXEL_PACKING(pixel_packing) |
+	  DATAMODE(data_mode));
+  setreg (s, 0x0a, 0);		/* reserved and strange register */
+
+  setreg (s, 0x5c, 0x00);
+  setreg (s, 0x5d, 0x00);
+  setreg (s, 0x5e, 0x00);
+
+  if (s->user_parms.vertical_resolution == 1200)
+    {
+      /* 1 out of 2 */
+      n = 1;
+      m = 2;
+    }
+  setreg (s, 0x44, (256 - n) & 0xff);
+  setreg (s, 0x5a, m);
+  s->runtime_parms.status_bytes = (m == 0) ? 1 : m;
+  if (data_mode == 1)
+    s->runtime_parms.status_bytes *= 2;
+
+  s->runtime_parms.scanner_line_size =
+    compute_bytes_per_line (data_pixels_end - data_pixels_start,
+			    hdpi_code, pixel_packing, data_mode,
+			    AFE_operation, m);
+  pause_limit = compute_pause_limit (&(s->hw_parms),
+				     s->runtime_parms.scanner_line_size);
+
+#ifdef DEBUG
+  fprintf (stderr, "scanner_line_size = %d\npause_limit = %d\n",
+	   s->runtime_parms.scanner_line_size, pause_limit);
+#endif
+
+  setreg (s, 0x4e, pause_limit); /* max Kb to pause */
+  setreg (s, 0x4f, 0x02);	/* min Kb to resume */
+
+  line_rate_color = 1;
+  step_size = (s->user_parms.vertical_resolution * line_end * line_rate_color) /
+    (4 * s->hw_parms.motor_full_steps_per_inch);
+
+  if (s->val[OPT_BACKTRACK].b)
+    {
+      steps_to_reverse = 0x3f;
+      setreg (s, 0x50, steps_to_reverse);
+      setreg (s, 0x51, 0x15);	/* accel profile */
+    }
+  else
+    {
+      s->hw_parms.steps_to_reverse = 0;
+      setreg (s, 0x50, s->hw_parms.steps_to_reverse);
+      setreg (s, 0x51, 0);	/* accel profile */
+      s->hw_parms.fsteps_25_speed = 0;
+      s->hw_parms.fsteps_50_speed = 0;
+    }
+
+  dpd = compute_dpd (s, step_size, line_end); /* 0x0ada; */
+#ifdef DEBUG
+  fprintf (stderr, "dpd = %d\n", dpd);
+#endif
+  setreg (s, 0x52, HIBYTE(dpd));
+  setreg (s, 0x53, LOBYTE(dpd));
+
+  setreg (s, 0x46, HIBYTE(step_size));
+  setreg (s, 0x47, LOBYTE(step_size));
+
+  ff_step_size = compute_fastfeed_step_size (s->hw_parms.crystal_frequency,
+					     s->mclk_div,
+					     s->hw_parms.motor_max_speed,
+					     s->hw_parms.motor_full_steps_per_inch,
+					     0);
+  setreg (s, 0x48, HIBYTE(ff_step_size));
+  setreg (s, 0x49, LOBYTE(ff_step_size));
+  setreg (s, 0x4b, 0x15);
+  /* this is to stay the motor running */
+  setbits (s, 0x45, (1 << 4));
+
+  setreg (s, 0x4a, HIBYTE(47 + s->runtime_parms.steps_to_skip));
+  setreg (s, 0x4b, LOBYTE(47 + s->runtime_parms.steps_to_skip));
+
+  setreg (s, 0x58, 0);
+
+  ciclic_buffer_init (&(s->ciclic_buffer),
+		      s->runtime_parms.image_line_size,
+		      s->user_parms.vertical_resolution,
+		      s->runtime_parms.status_bytes);
+
+  s->runtime_parms.num_bytes_left_to_scan =
+    s->user_parms.lines_to_scan * s->runtime_parms.image_line_size;
+
+#ifdef DEBUG
+  fprintf (stderr, "bytes to scan = %ld\n",
+	   s->runtime_parms.num_bytes_left_to_scan);
+#endif
+
+  cache_write (s);
+
+#ifdef DEBUG
+  lm983x_dump_registers (s->fd);
+#endif
+
+  lm983x_reset (s->fd);
+
+  setreg (s, 0x07, 0x03);
+  usleep(100);
+
+  return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+end_scan (HP4200_Scanner *s)
+{
+  s->scanning = SANE_FALSE;
+  setreg (s, 0x07, 0x00);
+  lm983x_reset (s->fd);
+  setbits (s, 0x58, PAPER_SENSOR_2_STOP_SCAN);
+  cache_write (s);
+  setreg (s, 0x07, 0x02);
+
+#ifdef DEBUG
+  fprintf (stderr, "%d bytes read\n", byte_count);
+#endif
+  
+  return SANE_STATUS_GOOD;
+}
+
+int
+hp4200_init_scanner (HP4200_Scanner *s)
+{
+  int ff_step_size;
+  int mclk_div;
+
+  lm983x_ini_scanner (s->fd, NULL);
+  hp4200_init_registers (s);
+  scanner_buffer_init (&(s->scanner_buffer), s->hw_parms.SRAM_size);
+  setreg (s, 0x07, 0x08);
+  usleep (10 * 1000);
+  setreg (s, 0x07, 0x00);
+  usleep (10 * 1000);
+  mclk_div = 2;
+
+  setreg (s, 0x08, (mclk_div - 1) * 2);
+  ff_step_size =
+    compute_fastfeed_step_size (s->hw_parms.crystal_frequency,
+				mclk_div,
+				s->hw_parms.motor_max_speed,
+				s->hw_parms.motor_full_steps_per_inch,
+				0);
+  setreg (s, 0x48, HIBYTE(ff_step_size));
+  setreg (s, 0x49, LOBYTE(ff_step_size));
+  setbits (s, 0x45, (1 << 4));
+  cache_write (s);
+  return 0;
+}
+
+static void
+ciclic_buffer_copy (ciclic_buffer_t *cb, SANE_Byte *buf,
+		    SANE_Int num_bytes, int image_line_size,
+		    int status_bytes)
+{
+  int biggest_upper_block_size;
+  int upper_block_size;
+  int lower_block_size;
+  int bytes_to_be_a_entire_line;
+
+  /* copy the upper block */
+  biggest_upper_block_size = cb->size - (cb->buffer_position - cb->buffer);
+  upper_block_size = min(biggest_upper_block_size, num_bytes);
+  memcpy (buf, cb->buffer_position, upper_block_size);
+  cb->good_bytes -= upper_block_size;
+
+  bytes_to_be_a_entire_line = (cb->buffer_position - cb->buffer) %
+    image_line_size;
+  cb->can_consume += upper_block_size +
+    status_bytes * (((bytes_to_be_a_entire_line + upper_block_size) /
+		    image_line_size) - 1);
+
+  if (num_bytes < biggest_upper_block_size)
+    {
+      cb->buffer_position += num_bytes;
+      return;
+    }
+
+  /* copy the lower block */
+  lower_block_size = num_bytes - biggest_upper_block_size;
+  if (lower_block_size > 0)
+    {
+      memcpy (buf + biggest_upper_block_size, cb->buffer, lower_block_size);
+      cb->good_bytes -= lower_block_size;
+      cb->can_consume += lower_block_size + status_bytes *
+	(lower_block_size / image_line_size);
+      cb->buffer_position = cb->buffer + lower_block_size;
+    }
+  else
+    {
+      cb->buffer_position = cb->buffer;
+    }
+  assert (cb->good_bytes >= 0);
+  assert (lower_block_size >= 0);
+}
+
+static void
+ciclic_buffer_consume (ciclic_buffer_t *cb,
+		       scanner_buffer_t *scanner_buffer,
+		       int image_width, int status_bytes)
+{
+  int to_consume;
+  int to_consume_now;
+  int i;
+  int processed;
+
+  to_consume = min (cb->can_consume, scanner_buffer->num_bytes);
+
+  while (to_consume)
+    {
+      
+      if (cb->pixel_position == image_width)
+	{
+	  if (scanner_buffer->num_bytes >= status_bytes)
+	    {
+	      /* forget status bytes */ 
+	      scanner_buffer->data_ptr += status_bytes; 
+	      scanner_buffer->num_bytes -= status_bytes;
+	      cb->can_consume -= status_bytes;
+	      to_consume -= status_bytes;
+
+	      cb->pixel_position = 0; /* back to the start pixel */
+
+	      cb->red_idx = (cb->red_idx + 1) % cb->num_lines;
+	      cb->green_idx = (cb->green_idx + 1) % cb->num_lines;
+	      cb->blue_idx = (cb->blue_idx + 1) % cb->num_lines;
+	      cb->current_line++;
+	    }
+	  else
+	    break;
+	}
+
+      to_consume_now = min ((image_width - cb->pixel_position) * 3,
+			    to_consume);
+
+      if (to_consume_now < 3)
+	break;
+
+      for (i = cb->pixel_position * 3; to_consume_now >= 3;
+	   i += 3, to_consume_now -= 3)
+	{
+	  cb->buffer_ptrs[cb->red_idx][i] = scanner_buffer->data_ptr[0];
+	  cb->buffer_ptrs[cb->green_idx][i + 1] = scanner_buffer->data_ptr[1];
+	  cb->buffer_ptrs[cb->blue_idx][i + 2] = scanner_buffer->data_ptr[2];
+	  scanner_buffer->data_ptr += 3;
+	}
+      processed = i - (cb->pixel_position * 3);
+      cb->pixel_position = i / 3;
+      to_consume -= processed;
+      cb->can_consume -= processed;
+      scanner_buffer->num_bytes -= processed;
+      if (cb->current_line > cb->first_good_line)
+	cb->good_bytes += processed;
+    }
+}
+
+SANE_Status
+sane_read (SANE_Handle h, SANE_Byte *buf,
+	   SANE_Int maxlen, SANE_Int *len)
+{
+  SANE_Status status;
+  int to_copy_now;
+  int bytes_to_copy_to_frontend;
+  HP4200_Scanner *s = (HP4200_Scanner *)h;
+
+  static char me[] = "sane_read";
+  DBG (DL_CALL_TRACE, "%s\n", me);
+
+  if (!buf || !len)
+    return SANE_STATUS_INVAL;
+  
+  *len = 0;
+
+  if (s->runtime_parms.num_bytes_left_to_scan == 0)
+    {
+      end_scan (s);
+      return SANE_STATUS_EOF;
+    }
+
+  bytes_to_copy_to_frontend = min (s->runtime_parms.num_bytes_left_to_scan,
+				   maxlen);
+
+  /* first copy available data from the ciclic buffer */
+  to_copy_now = min (s->ciclic_buffer.good_bytes,
+		     bytes_to_copy_to_frontend);
+
+  if (to_copy_now > 0)
+    {
+      ciclic_buffer_copy (&(s->ciclic_buffer), buf, to_copy_now,
+			  s->runtime_parms.image_line_size,
+			  s->runtime_parms.status_bytes);
+      buf += to_copy_now;
+      bytes_to_copy_to_frontend -= to_copy_now;
+      *len += to_copy_now;
+    }
+
+  /* if not enough bytes, get data from the scanner */
+  while (bytes_to_copy_to_frontend)
+    {
+      if (s->scanner_buffer.num_bytes < 3) /* cicl buf consumes modulo 3
+					      bytes at least now for rgb
+					      color 8 bpp fixme: but this
+					      is ugly and not generic
+					   */
+	{
+	  status = scanner_buffer_read (s);
+      
+	  if (status == SANE_STATUS_CANCELLED)
+	    {
+	      end_scan (s);
+	      s->aborted_by_user = SANE_FALSE;
+	      return status;
+	    }
+	  if (status != SANE_STATUS_GOOD)
+	    return status;
+	}
+      
+      while ((s->scanner_buffer.num_bytes > 3) && bytes_to_copy_to_frontend)
+	{
+	  ciclic_buffer_consume (&(s->ciclic_buffer), &(s->scanner_buffer),
+				 s->user_parms.image_width,
+				 s->runtime_parms.status_bytes);
+	  to_copy_now = min (s->ciclic_buffer.good_bytes,
+			     bytes_to_copy_to_frontend);
+
+	  if (to_copy_now > 0)
+	    {
+	      ciclic_buffer_copy (&(s->ciclic_buffer), buf, to_copy_now,
+				  s->runtime_parms.image_line_size,
+				  s->runtime_parms.status_bytes);
+	      buf += to_copy_now;
+	      bytes_to_copy_to_frontend -= to_copy_now;
+	      *len += to_copy_now;
+	    }
+	}
+    }
+
+  s->runtime_parms.num_bytes_left_to_scan -= *len;
+
+  if (s->runtime_parms.num_bytes_left_to_scan < 0)
+    *len += s->runtime_parms.num_bytes_left_to_scan;
+
+  return SANE_STATUS_GOOD;
+}
+
+static HP4200_Device *
+find_device (SANE_String_Const name)
+{
+  static char me[] = "find_device";
+  HP4200_Device *dev;
+  
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  
+  for (dev = first_device; dev; dev = dev->next)
+    {
+      if (strcmp (dev->dev.name, name) == 0)
+	{
+	  return dev;
+	}
+    }
+  return NULL;
+}
+
+static SANE_Status
+add_device (SANE_String_Const name, HP4200_Device **argpd)
+{
+  int fd;
+  HP4200_Device *pd;
+  static const char me[] = "add_device";
+
+  DBG (DL_CALL_TRACE, "%s(%s)\n", me, name);
+
+  /* Avoid adding the same device more than once */
+  if ((pd = find_device (name)))
+    {
+      if (argpd)
+	*argpd = pd;
+      return SANE_STATUS_GOOD;
+    }
+  
+  /* open the device file, but read only or read/write to perform
+     ioctl's ? */
+  fd = open (name, O_RDONLY);
+  if (fd == -1)
+    {
+      DBG (DL_INFO, "%s: open(%s) failed: %s\n", me, name,
+	   strerror (errno));
+      return SANE_STATUS_INVAL;
+    }
+  
+  /* put here some code to probe that the device attached to the
+     device file is a supported scanner. Maybe some ioctl */
+  close (fd);
+  
+  pd = (HP4200_Device *) malloc (sizeof (HP4200_Device));
+  if (!pd)
+    {
+      DBG (DL_MAJOR_ERROR, "%s: out of memory allocating device.", me);
+      return SANE_STATUS_NO_MEM;
+    }
+  
+  pd->dev.name = strdup (name);
+  pd->dev.vendor = strdup ("Hewlett-Packard");
+  pd->dev.model = strdup ("HP-4200");
+  pd->dev.type = strdup ("flatbed scanner");
+
+  if (!pd->dev.name || !pd->dev.vendor || !pd->dev.model || !pd->dev.type)
+    {
+      DBG (DL_MAJOR_ERROR,
+	   "%s: out of memory allocating device descriptor strings.\n", me);
+      free (pd);
+      return SANE_STATUS_NO_MEM;
+    }
+  
+  pd->handle = NULL;
+  pd->next = first_device;
+  first_device = pd;
+  n_devices++;
+  if (argpd)
+    *argpd = pd;
+  
+  return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+attach (SANE_String_Const name)
+{
+  static char me[] = "attach";
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  return add_device (name, NULL);
+}
+
+SANE_Status
+sane_init (SANE_Int *version_code, SANE_Auth_Callback authorize)
+{
+  static const char me[] = "sane_hp4200_init";
+  char dev_name[PATH_MAX];
+  FILE *fp;
+  int len;
+  SANE_Status status;
+
+  DBG_INIT ();
+
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  /* put some version_code checks here */
+
+  if (NULL != version_code)
+    {
+      *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);
+    }
+  
+  fp = sanei_config_open (HP4200_CONFIG_FILE);
+  if (!fp)
+    {
+      /* default to DEFAULT_DEVICE instead of insisting on config file */
+      DBG (DL_INFO,
+	   "%s: configuration file not found, defaulting to %s.\n",
+	   me, DEFAULT_DEVICE);
+      status = add_device (DEFAULT_DEVICE, NULL);
+      if (status != SANE_STATUS_GOOD)
+	{
+	  DBG (DL_MINOR_ERROR, "%s: failed to add device \"%s\"\n",
+	       me, dev_name);
+	}
+    }
+  else
+    {
+      while (fgets (dev_name, sizeof (dev_name), fp))
+	{
+	  if (dev_name[0] == '#') /* ignore line comments */
+	    continue;
+	  len = strlen (dev_name);
+	  if (dev_name[len - 1] == '\n')
+	    dev_name[--len] = '\0';
+	  
+	  if (!len)
+	    continue;		/* ignore empty lines */
+	  
+	  if (strncmp (dev_name, "option", 6) == 0
+	      && isspace (dev_name[6]))
+	    {
+	      const char *str = dev_name + 7;
+	      
+	      while (isspace (*str))
+		++str;
+	      
+	      continue;
+	    }
+	  
+	  sanei_config_attach_matching_devices (dev_name, attach);
+	}
+      
+      fclose (fp);
+    }
+  
+  return SANE_STATUS_GOOD;
+}
+
+void
+sane_exit (void)
+{
+  HP4200_Device *device, *next;
+
+  DBG (DL_CALL_TRACE, "sane_hp4200_exit\n");
+
+  for (device = first_device; device; device = next)
+    {
+      next = device->next;
+      if (device->handle)
+	sane_close (device->handle);
+      free (device);
+    }
+}
+
+SANE_Status
+sane_get_devices (const SANE_Device ***device_list,
+		  SANE_Bool local_only)
+{
+  static const char me[] = "sane_hp4200_get_devices";
+
+  DBG (DL_CALL_TRACE, "%s (%p, %ld)\n", me, (void *) device_list,
+       (long) local_only);
+  
+  /* Waste the last list returned from this function */
+  if (NULL != get_devices_list)
+    free (get_devices_list);
+  
+  *device_list = (const SANE_Device **)
+    malloc ((n_devices + 1) * sizeof (SANE_Device *));
+  
+  if (*device_list)
+    {
+      int i;
+      HP4200_Device *pdev;
+      
+      for (i = 0, pdev = first_device; pdev; i++, pdev = pdev->next)
+	{
+	  (*device_list)[i] = &(pdev->dev);
+	}
+      (*device_list)[i] = NULL;
+    }
+  else
+    {
+      DBG (DL_MAJOR_ERROR, "%s: out of memory\n", me);
+      return SANE_STATUS_NO_MEM;
+    }
+  
+  get_devices_list = *device_list;
+  
+  return SANE_STATUS_GOOD;
+}
+
+static void
+init_options (HP4200_Scanner *s)
+{
+
+  /* this should work, but the preview mode doesn't work properly */
+#if 0
+  static const SANE_Int hdpi_list[] = {8, 50, 75, 100, 150, 200, 300,
+				       400, 600};
+  static const SANE_Int vdpi_list[] = {4, 75, 150, 300, 600};
+#endif
+
+  static const SANE_Int hdpi_list[] = {4, 75, 150, 300, 600};
+  static const SANE_Int vdpi_list[] = {4, 75, 150, 300, 600};
+  static SANE_Range x_range =  {1, 0, 0};
+  static SANE_Range y_range =  {1, 0, 0};
+  static const SANE_Range u8_range = {0, 255, 0};
+
+  x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
+  y_range.max = SANE_FIX (11.75 * MM_PER_INCH);
+
+  s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
+  s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
+  s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
+  s->opt[OPT_NUM_OPTS].unit = SANE_UNIT_NONE;
+  s->opt[OPT_NUM_OPTS].size = sizeof (SANE_Word);
+  s->opt[OPT_NUM_OPTS].constraint_type = SANE_CONSTRAINT_NONE;
+  s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
+  
+  s->opt[OPT_HRES].name = SANE_NAME_SCAN_X_RESOLUTION;
+  s->opt[OPT_HRES].title = SANE_TITLE_SCAN_X_RESOLUTION;
+  s->opt[OPT_HRES].desc = SANE_DESC_SCAN_X_RESOLUTION;
+  s->opt[OPT_HRES].cap = SANE_CAP_SOFT_SELECT;
+  s->opt[OPT_HRES].type = SANE_TYPE_INT;
+  s->opt[OPT_HRES].size = sizeof (SANE_Word);
+  s->opt[OPT_HRES].unit = SANE_UNIT_DPI;
+  s->opt[OPT_HRES].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+  s->opt[OPT_HRES].constraint.word_list = hdpi_list;
+  s->val[OPT_HRES].w = 150;
+
+  s->opt[OPT_VRES].name = SANE_NAME_SCAN_Y_RESOLUTION;
+  s->opt[OPT_VRES].title = SANE_TITLE_SCAN_Y_RESOLUTION;
+  s->opt[OPT_VRES].desc = SANE_DESC_SCAN_Y_RESOLUTION;
+  s->opt[OPT_VRES].cap = SANE_CAP_SOFT_SELECT;
+  s->opt[OPT_VRES].type = SANE_TYPE_INT;
+  s->opt[OPT_VRES].size = sizeof (SANE_Word);
+  s->opt[OPT_VRES].unit = SANE_UNIT_DPI;
+  s->opt[OPT_VRES].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+  s->opt[OPT_VRES].constraint.word_list = vdpi_list;
+  s->val[OPT_VRES].w = 150;
+
+  s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
+  s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
+  s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
+  s->opt[OPT_TL_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
+  s->opt[OPT_TL_X].size = sizeof (SANE_Fixed);
+  s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
+  s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_TL_X].constraint.range = &x_range;
+  s->val[OPT_TL_X].w = x_range.min;
+
+  s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
+  s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
+  s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
+  s->opt[OPT_TL_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED;
+  s->opt[OPT_TL_Y].size = sizeof (SANE_Fixed);
+  s->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
+  s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_TL_Y].constraint.range = &y_range;
+  s->val[OPT_TL_Y].w = y_range.min;
+
+  s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
+  s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
+  s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
+  s->opt[OPT_BR_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_BR_X].type = SANE_TYPE_FIXED;
+  s->opt[OPT_BR_X].size = sizeof (SANE_Fixed);
+  s->opt[OPT_BR_X].unit = SANE_UNIT_MM;
+  s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_BR_X].constraint.range = &x_range;
+  s->val[OPT_BR_X].w = x_range.max;
+
+  s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
+  s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
+  s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
+  s->opt[OPT_BR_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
+  s->opt[OPT_BR_Y].size = sizeof (SANE_Fixed);
+  s->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
+  s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_BR_Y].constraint.range = &y_range;
+  s->val[OPT_BR_Y].w = y_range.max;
+
+  s->opt[OPT_BACKTRACK].name = SANE_NAME_BACKTRACK;
+  s->opt[OPT_BACKTRACK].title = SANE_TITLE_BACKTRACK;
+  s->opt[OPT_BACKTRACK].desc = SANE_DESC_BACKTRACK;
+  s->opt[OPT_BACKTRACK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_BACKTRACK].type = SANE_TYPE_BOOL;
+  s->opt[OPT_BACKTRACK].size = sizeof (SANE_Bool);
+  s->opt[OPT_BACKTRACK].unit = SANE_UNIT_NONE;
+  s->opt[OPT_BACKTRACK].constraint_type = SANE_CONSTRAINT_NONE;
+  s->val[OPT_BACKTRACK].b = SANE_FALSE;
+
+  s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
+  s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
+  s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
+  s->opt[OPT_GAMMA_VECTOR_R].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT;
+  s->opt[OPT_GAMMA_VECTOR_R].size = 1024 * sizeof (SANE_Word);
+  s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
+  s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range;
+  s->val[OPT_GAMMA_VECTOR_R].wa = s->user_parms.gamma[0];
+
+  s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
+  s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
+  s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
+  s->opt[OPT_GAMMA_VECTOR_G].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT;
+  s->opt[OPT_GAMMA_VECTOR_G].size = 1024 * sizeof (SANE_Word);
+  s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
+  s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range;
+  s->val[OPT_GAMMA_VECTOR_G].wa = s->user_parms.gamma[1];
+
+  s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
+  s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
+  s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
+  s->opt[OPT_GAMMA_VECTOR_B].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+  s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT;
+  s->opt[OPT_GAMMA_VECTOR_B].size = 1024 * sizeof (SANE_Word);
+  s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
+  s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
+  s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range;
+  s->val[OPT_GAMMA_VECTOR_B].wa = s->user_parms.gamma[2];
+
+  {
+    int i;
+    for (i = 0; i < 1024; i++)
+      {
+	s->user_parms.gamma[0][i] = i / 4;
+	s->user_parms.gamma[1][i] = i / 4;
+	s->user_parms.gamma[2][i] = i / 4;
+      }
+  }
+}
+
+SANE_Status
+sane_open (SANE_String_Const name, SANE_Handle *h)
+{
+  static const char me[] = "sane_hp4200_open";
+  SANE_Status status;
+  HP4200_Device *dev;
+  HP4200_Scanner *s;
+  
+  DBG (DL_CALL_TRACE, "%s (%s, %p)\n", me, name, (void *) h);
+  
+  if (name && name[0])
+    {
+      dev = find_device (name);
+      if (!dev)
+	{
+	  status = add_device (name, &dev);
+	  if (status != SANE_STATUS_GOOD)
+	    return status;
+	}
+    }
+  else
+    {
+      dev = first_device;
+    }
+  
+  if (!dev)
+    return SANE_STATUS_INVAL;
+  
+  if (!h)
+    return SANE_STATUS_INVAL;
+  
+  s = *h = (HP4200_Scanner *) malloc (sizeof (HP4200_Scanner));
+  if (!s)
+    {
+      DBG (DL_MAJOR_ERROR,
+	   "%s: out of memory creating scanner structure.\n", me);
+      return SANE_STATUS_NO_MEM;
+    }
+  dev->handle = s;
+  s->aborted_by_user = SANE_FALSE;
+  s->ciclic_buffer.buffer = NULL;
+  s->scanner_buffer.buffer = NULL;
+  s->dev = dev;
+  s->user_parms.image_width = 0;
+  s->user_parms.lines_to_scan = 0;
+  s->user_parms.vertical_resolution = 0;
+  s->scanning = SANE_FALSE;
+
+  init_options (s);
+
+  s->fd = open (name, O_RDWR | O_EXCL);
+  if (s->fd == -1)
+    {
+      return SANE_STATUS_IO_ERROR; /* fixme: return busy when file is
+				      being accesed already */
+    }
+
+  return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle h)
+{
+  HP4200_Scanner *s = (HP4200_Scanner *) h;
+  DBG (DL_CALL_TRACE, "sane_hp4200_close (%p)\n", (void *) h);
+
+  if (s)
+    {
+      s->dev->handle = NULL;
+      close (s->fd);
+      if (s->scanner_buffer.buffer)
+	free (s->scanner_buffer.buffer);
+      if (s->ciclic_buffer.buffer)
+	free (s->ciclic_buffer.buffer);
+      free (s);
+    }
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle h, SANE_Int n)
+{
+  static char me[] = "sane_get_option_descriptor";
+  HP4200_Scanner *s = (HP4200_Scanner *) h;
+  
+  DBG (DL_CALL_TRACE, "%s\n", me);
+
+  if ((n < 0) || (n > NUM_OPTIONS))
+    return NULL;
+
+  return s->opt + n;
+}
+
+SANE_Status
+sane_control_option (SANE_Handle h, SANE_Int n, SANE_Action action,
+		     void *val, SANE_Int *info)
+{
+  static char me[] = "sane_control_option";
+  HP4200_Scanner *s = (HP4200_Scanner *) h;
+  SANE_Status status;
+  SANE_Int myinfo = 0;
+
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  
+  if (info)
+    *info = 0;
+
+  if ((n < 0) || (n > NUM_OPTIONS) ||
+      ((n > 1) && !SANE_OPTION_IS_ACTIVE (s->opt[n].cap)))
+    return SANE_STATUS_UNSUPPORTED;
+
+  if (action == SANE_ACTION_GET_VALUE)
+    {
+      if (!val)
+	return SANE_STATUS_INVAL;
+
+      switch (n)
+	{
+	case OPT_NUM_OPTS:
+	case OPT_HRES:
+	case OPT_VRES:
+	case OPT_TL_X:
+	case OPT_TL_Y:
+	case OPT_BR_X:
+	case OPT_BR_Y:
+	  *(SANE_Word *) val = s->val[n].w;
+	  break;
+	case OPT_BACKTRACK:
+	  *(SANE_Bool *) val = s->val[n].b;
+	  break;
+	case OPT_GAMMA_VECTOR_R:
+	case OPT_GAMMA_VECTOR_G:
+	case OPT_GAMMA_VECTOR_B:
+	  memcpy (val, s->val[n].wa, s->opt[n].size);
+	  break;
+	default:
+	  return SANE_STATUS_UNSUPPORTED;
+	}
+    }
+  else if (action == SANE_ACTION_SET_VALUE)
+    {
+      status = sanei_constrain_value (s->opt + n, val, &myinfo);
+      if (status != SANE_STATUS_GOOD)
+	return status;
+
+      switch (n)
+	{
+	case OPT_NUM_OPTS:
+	  s->val[n].w = *(SANE_Word *) val;
+	  break;
+	case OPT_HRES:
+	case OPT_VRES:
+	case OPT_TL_X:
+	case OPT_TL_Y:
+	case OPT_BR_X:
+	case OPT_BR_Y:
+	  myinfo |= SANE_INFO_RELOAD_PARAMS;
+	  s->val[n].w = *(SANE_Word *) val;
+	  break;
+	case OPT_BACKTRACK:
+	  s->val[n].b = *(SANE_Bool *) val;
+	  break;
+	case OPT_GAMMA_VECTOR_R:
+	case OPT_GAMMA_VECTOR_G:
+	case OPT_GAMMA_VECTOR_B:
+	  memcpy (s->val[n].wa, val, s->opt[n].size);
+	  break;
+	default:
+	  return SANE_STATUS_UNSUPPORTED;
+	}
+    }
+  else
+    {
+      return SANE_STATUS_UNSUPPORTED;
+    }
+
+  if (info)
+    *info = myinfo;
+
+  return SANE_STATUS_GOOD;
+}
+
+static void
+compute_parameters (HP4200_Scanner *s)
+{
+  s->user_parms.horizontal_resolution = s->val[OPT_HRES].w;
+  if (s->val[OPT_VRES].w == 600)
+    s->user_parms.vertical_resolution = 1200;
+  else
+    s->user_parms.vertical_resolution = s->val[OPT_VRES].w;
+    
+  s->runtime_parms.steps_to_skip = floor (300.0 / MM_PER_INCH *
+    SANE_UNFIX (s->val[OPT_TL_Y].w));
+  s->user_parms.lines_to_scan = 
+    floor ((SANE_UNFIX (s->val[OPT_BR_Y].w) - 
+	    SANE_UNFIX (s->val[OPT_TL_Y].w)) / 
+	   MM_PER_INCH * s->val[OPT_VRES].w);
+  s->user_parms.image_width =
+    floor ((SANE_UNFIX (s->val[OPT_BR_X].w) -
+	    SANE_UNFIX (s->val[OPT_TL_X].w)) / 
+	   MM_PER_INCH * s->user_parms.horizontal_resolution);
+  s->runtime_parms.first_pixel =
+    floor (SANE_UNFIX (s->val[OPT_TL_X].w) /
+	   MM_PER_INCH * s->user_parms.horizontal_resolution);
+
+  /* fixme: add support for more depth's and bpp's. */
+  s->runtime_parms.image_line_size = s->user_parms.image_width * 3;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle h, SANE_Parameters *p)
+{
+  static char me[] = "sane_get_parameters";
+  HP4200_Scanner *s = (HP4200_Scanner *) h;
+  
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  if (!p)
+    return SANE_STATUS_INVAL;
+
+  p->format = SANE_FRAME_RGB;
+  p->last_frame = SANE_TRUE;
+  p->depth = 8;
+  if (!s->scanning)
+    {    
+      compute_parameters (s);
+    }
+
+  p->lines = s->user_parms.lines_to_scan;
+  p->pixels_per_line = s->user_parms.image_width;
+  p->bytes_per_line = s->runtime_parms.image_line_size;
+
+  return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_start (SANE_Handle h)
+{
+  HP4200_Scanner *s = (HP4200_Scanner *) h;
+  struct coarse_t coarse;
+
+  static char me[] = "sane_start";
+  DBG (DL_CALL_TRACE, "%s\n", me);
+
+  s->scanning = SANE_TRUE;
+  s->aborted_by_user = SANE_FALSE;
+  s->user_parms.color = SANE_TRUE;
+
+  compute_parameters (s);
+
+  hp4200_init_scanner (s);
+  hp4200_goto_home (s);
+  hp4200_wait_homed (s);
+  /* restore default register values here... */
+  write_gamma (s);
+  hp4200_init_registers (s);
+  lm983x_ini_scanner (s->fd, NULL);
+  /* um... do not call cache_write() here, don't know why :( */
+  do_coarse_calibration (s, &coarse);
+  do_fine_calibration (s, &coarse);
+  prepare_for_a_scan (s);
+
+  return SANE_STATUS_GOOD;
+}
+
+void
+sane_cancel (SANE_Handle h)
+{
+  static char me[] = "sane_cancel";
+  HP4200_Scanner *s = (HP4200_Scanner *) h;
+  DBG (DL_CALL_TRACE, "%s\n", me);
+
+  s->aborted_by_user = SANE_TRUE;
+}
+
+SANE_Status
+sane_set_io_mode (SANE_Handle h, SANE_Bool m)
+{
+  static char me[] = "sane_set_io_mode";
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  if (m == SANE_TRUE)
+    return SANE_STATUS_UNSUPPORTED;
+  return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle h, SANE_Int *fd)
+{
+  static char me[] = "sane_get_select_fd";
+  DBG (DL_CALL_TRACE, "%s\n", me);
+  return SANE_STATUS_UNSUPPORTED;
+}
diff -Naur sane-1.0.2/backend/hp4200.conf sane-1.0.2-hp4200/backend/hp4200.conf
--- sane-1.0.2/backend/hp4200.conf	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/hp4200.conf	Fri Aug  4 19:52:19 2000
@@ -0,0 +1 @@
+/dev/usbscanner
diff -Naur sane-1.0.2/backend/hp4200.desc sane-1.0.2-hp4200/backend/hp4200.desc
--- sane-1.0.2/backend/hp4200.desc	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/hp4200.desc	Sun Aug 20 01:47:33 2000
@@ -0,0 +1,29 @@
+;
+; SANE Backend specification file
+;
+; It's basically emacs-lisp --- so ";" indicates comment to end of line.
+; All syntactic elements are keyword tokens, followed by a string or
+;  keyword argument, as specified.
+;
+; ":backend" *must* be specified.
+; All other information is optional (but what good is the file without it?).
+;
+
+:backend "hp4200"               ; name of backend
+:version "(0.2)"                    ; version of backend
+:status :alpha                    ; :alpha, :beta, :stable, :new
+; :manpage "sane-hp4200"           ; name of manpage (if it exists)
+:url "http://hp4200-backend.sourceforge.net" ; backend's web page
+
+:devicetype :scanner              ; start of a list of devices....
+                                  ; other types:  :stillcam, :vidcam,
+                                  ;               :meta, :api
+
+:mfg "HP"                         ; name a manufacturer
+:url "http://www.hp.com/"
+:model "HP4200"                   ; name models for above-specified mfg.
+:interface "USB"
+:comment "8bpp only and scanned image needs postprocessing"
+
+; :comment and :url specifiers are optional after :mfg, :model, :desc,
+;  and at the top-level.                                                       
diff -Naur sane-1.0.2/backend/hp4200.h sane-1.0.2-hp4200/backend/hp4200.h
--- sane-1.0.2/backend/hp4200.h	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/hp4200.h	Sun Aug 20 01:52:16 2000
@@ -0,0 +1,234 @@
+/*
+  Copyright (C) 2000 by Adrian Perez Jorge
+
+  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.  
+ */
+
+#ifndef _HP4200_H
+#define _HP4200_H
+
+#include <sys/types.h>
+
+#define MCLKDIV_SCALING 2
+#define NUM_REGISTERS 0x80
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+
+enum HP4200_Option
+{
+  OPT_NUM_OPTS = 0,
+
+  OPT_HRES,
+  OPT_VRES,
+  OPT_TL_X,			/* top-left x */
+  OPT_TL_Y,			/* top-left y */
+  OPT_BR_X,			/* bottom-right x */
+  OPT_BR_Y,			/* bottom-right y */
+  OPT_BACKTRACK,
+  OPT_GAMMA_VECTOR_R,
+  OPT_GAMMA_VECTOR_G,
+  OPT_GAMMA_VECTOR_B,
+  NUM_OPTIONS			/* must come last */
+};
+
+typedef union
+{
+  SANE_Word w;
+  SANE_Bool b;
+  SANE_Fixed f;
+  SANE_Word *wa;
+} Option_Value;
+
+enum ScannerModels
+{
+  HP4200
+};
+
+typedef struct HP4200_Device
+{
+  struct HP4200_Device *next;
+  SANE_Device dev;
+  SANE_Handle handle;
+} HP4200_Device;
+
+struct _scanner_buffer_t
+{
+  unsigned char *buffer;	/* buffer memory space */
+  int size;			/* size of the buffer */
+  int num_bytes;		/* number of bytes left (to read) */
+  unsigned char *data_ptr;	/* cursor in buffer */
+};
+
+typedef struct _scanner_buffer_t scanner_buffer_t;
+
+struct _ciclic_buffer_t
+{
+  int good_bytes;		/* number of valid bytes of the image */
+  int num_lines;		/* number of lines of the ciclic buffer */
+  int size;			/* size in bytes of the buffer space */
+  unsigned char *buffer;	/* pointer to the buffer space */
+  unsigned char **buffer_ptrs;	/* pointers to the beginning of each
+				   line in the buffer space */
+  int can_consume;		/* num of bytes the ciclic buf can consume */
+  int current_line;		/* current scanned line */
+  int first_good_line;		/* number of lines to fill the ``pipeline'' */
+  unsigned char *buffer_position; /* pointer to the first byte that
+				     can be copied */
+  int pixel_position;		/* pixel position in current line */
+
+  /* color indexes for the proper line in the ciclic buffer */
+  int red_idx;
+  int green_idx;
+  int blue_idx;
+};
+
+typedef struct _ciclic_buffer_t ciclic_buffer_t;
+
+struct _hardware_parameters_t
+{
+  unsigned int SRAM_size;
+  unsigned char SRAM_bandwidth;
+  unsigned long crystal_frequency;
+  unsigned int min_pixel_data_buffer_limit;
+  unsigned int motor_full_steps_per_inch;
+  float motor_max_speed;
+  unsigned int scan_bar_max_speed;
+  unsigned int start_of_scanning_area;
+  unsigned int calibration_strip_height;
+  unsigned int scan_area_width;
+  double scan_area_length;	/* in inches */
+  unsigned int sensor_num_pixels;
+  unsigned int sensor_pixel_start;
+  unsigned int sensor_pixel_end;
+  int sensor_cds_state;		/* 0 == off, 1 == on */
+  int sensor_signal_polarity;	/* 0 == ??, 1 == ?? */
+  int sensor_max_integration_time;
+  int sensor_line_separation;
+  int sensor_type;
+  unsigned int sensor_resolution;
+  int sensor_control_signals_polarity;
+  int sensor_control_signals_state;
+  int sensor_control_pixel_rate_timing;
+  int sensor_control_line_rate_timing;
+  unsigned int sensor_black_clamp_timing; /* ??? */
+  unsigned int sensor_CIS_timing;
+  int sensor_toshiba_timing;
+  int sensor_color_modes;	/* bitmask telling color modes supported */
+  int illumination_mode;
+  int motor_control_mode;
+  int motor_paper_sense_mode;
+  int motor_pause_reverse_mode;
+  int misc_io_mode;
+  int num_tr_pulses;
+  int guard_band_duration;
+  int pulse_duration;
+  int fsteps_25_speed;
+  int fsteps_50_speed;
+  int steps_to_reverse;
+  struct {
+	int red;
+	int green;
+	int blue;
+  } target_value;
+  unsigned short home_sensor;
+};
+
+typedef struct _hardware_parameters_t hardware_parameters_t;
+
+struct _user_parameters_t
+{
+  unsigned int image_width;
+  unsigned int lines_to_scan;
+  unsigned int horizontal_resolution;
+  unsigned int vertical_resolution;
+  int hres_reduction_method;	/* interpolation/??? */
+  int vres_reduction_method;
+  SANE_Bool color;		/* color/grayscale */
+  int bpp;
+  int scan_mode;		/* preview/full scan */
+  SANE_Bool no_reverse;
+  SANE_Word gamma[3][1024];	/* gamma table for rgb */
+};
+
+typedef struct _user_parameters_t user_parameters_t;
+
+struct _measured_parameters_t
+{
+  unsigned int datalink_bandwidth;
+  struct
+  {
+    int red;
+    int green;
+    int blue;
+  } coarse_calibration_data;
+  struct
+  {
+    int *pRedOffset;
+    int *pGreenOffset;
+    int *pBlueOffset;
+    int *pRedGain;
+    int *pGreenGain;
+    int *pBlueGain;
+  } fine_calibration_data;
+  int max_integration_time;
+  int color_mode;
+};
+
+typedef struct _measured_parameters_t measured_parameters_t;
+
+struct _runtime_parameters_t
+{
+  long num_bytes_left_to_scan;
+  int status_bytes;		/* number of status bytes per line */
+  int image_line_size;		/* line size in bytes without status bytes */
+  int scanner_line_size;	/* line size in bytes including the
+				   status bytes */
+  int first_pixel;		/* first pixel in the line to be scanned */
+  int steps_to_skip;
+};
+
+typedef struct _runtime_parameters_t runtime_parameters_t;
+
+struct _HP4200_Scanner
+{
+  struct _HP4200_Scanner *next;
+
+  SANE_Option_Descriptor opt[NUM_OPTIONS];
+  Option_Value val[NUM_OPTIONS];
+  
+  SANE_Bool scanning;
+  SANE_Bool aborted_by_user;
+  
+  SANE_Parameters params;
+
+  HP4200_Device *dev;
+  hardware_parameters_t hw_parms;
+  user_parameters_t user_parms;
+  measured_parameters_t msrd_parms;
+  unsigned int regs[NUM_REGISTERS];
+  int mclk;
+  float mclk_div;
+
+  int fd;
+
+  ciclic_buffer_t ciclic_buffer;
+  scanner_buffer_t scanner_buffer;
+  runtime_parameters_t runtime_parms;
+};
+
+typedef struct _HP4200_Scanner HP4200_Scanner;
+
+#endif /* !_HP4200_H */
diff -Naur sane-1.0.2/backend/lm983x.c sane-1.0.2-hp4200/backend/lm983x.c
--- sane-1.0.2/backend/lm983x.c	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/lm983x.c	Thu Aug 10 22:18:15 2000
@@ -0,0 +1,212 @@
+/* sane - Scanner Access Now Easy.
+
+   Copyright (C) 2000 Adrian Perez Jorge
+
+   This file is part of the SANE package.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+   As a special exception, the authors of SANE give permission for
+   additional uses of the libraries contained in this release of SANE.
+
+   The exception is that, if you link a SANE library with other files
+   to produce an executable, this does not by itself cause the
+   resulting executable to be covered by the GNU General Public
+   License.  Your use of that executable is in no way restricted on
+   account of linking the SANE library code into it.
+
+   This exception does not, however, invalidate any other reasons why
+   the executable file might be covered by the GNU General Public
+   License.
+
+   If you submit changes to SANE to the maintainers to be included in
+   a subsequent release, you agree by submitting the changes that
+   those changes may be distributed with this exception intact.
+
+   If you write modifications of your own for SANE, it is your choice
+   whether to permit this exception to apply to your modifications.
+   If you do not wish that, delete this exception notice.
+
+   This file implements a backend for the HP4200C flatbed scanner
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include "pv8630.h"
+
+int
+lm983x_read_register (int fd, unsigned char reg, unsigned char *data)
+{
+  int retval;
+
+  if (!data)
+    return -EINVAL;
+  retval = pv8630_write_byte (fd, reg, PV8630_REPPADDRESS);
+  if (retval < 0)
+    return retval;
+  return pv8630_read_byte (fd, data, PV8630_RDATA);
+}
+
+int
+lm983x_write_register (int fd, unsigned char reg, unsigned char value)
+{
+  int retval;
+
+  retval = pv8630_write_byte (fd, reg, PV8630_REPPADDRESS);
+  if (retval < 0)
+    return retval;
+  
+  return pv8630_write_byte (fd, value, PV8630_RDATA);
+}
+
+int
+lm983x_dump_registers (int fd)
+{
+  int i;
+  unsigned char value = 0;
+
+  for (i = 0; i < 0x80; i++)
+    {
+      lm983x_read_register (fd, i, &value);
+      printf ("%.2x:0x%.2x", i, value);
+      if ((i + 1) % 8)
+	printf (", ");
+      else
+	printf ("\n");
+    }
+  puts ("");
+  return 0;
+}
+
+
+int
+pv8630_reset_buttons (int fd)
+{
+  lm983x_write_register (fd, 0x59, 0x10);
+  lm983x_write_register (fd, 0x59, 0x90);
+  return 0;
+}
+
+int
+lm983x_lamp_off (int fd)
+{
+  lm983x_write_register (fd, 0x07, 0x00);
+  lm983x_write_register (fd, 0x2c, 0x00);
+  lm983x_write_register (fd, 0x2d, 0x01);
+  lm983x_write_register (fd, 0x2e, 0x3f);
+  lm983x_write_register (fd, 0x2f, 0xff);
+  return 0;
+}
+
+int
+lm983x_lamp_on (int fd)
+{
+    lm983x_write_register (fd, 0x07, 0x00);
+    lm983x_write_register (fd, 0x2c, 0x3f);
+    lm983x_write_register (fd, 0x2d, 0xff);
+    lm983x_write_register (fd, 0x2e, 0x00);
+    lm983x_write_register (fd, 0x2f, 0x01);
+    return 0;
+}
+
+/*
+ * This function prints what button was pressed (the time before this
+ * code was executed).
+ */
+
+int
+hp4200c_what_button (int fd)
+{
+  unsigned char button;
+  
+  pv8630_read_buttons(fd, &button);
+  
+  if (button & 0x08)
+    puts ("Scan");
+  if (button & 0x10)
+    puts ("Copy");
+  if (button & 0x20)
+    puts ("E-mail");
+  if ((button & 0x38) == 0)
+    puts ("None");
+
+  pv8630_reset_buttons (fd);
+  return 0;
+}
+
+
+int
+lm983x_ini_scanner (int fd, unsigned char *regs)
+{
+  unsigned char inittable[] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x02, 0x1A, 0x00, 0x0A, 0x60, 0x2F, 0x13, 0x06,
+    0x17, 0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x0B,
+    0x00, 0x00, 0x00, 0x00, 0x0D, 0x21, 0x00, 0x40,
+    0x15, 0x18, 0x00, 0x40, 0x02, 0x98, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x30,
+    0x25, 0x25, 0x24, 0x28, 0x24, 0x28, 0x00, 0x00,
+    0x00, 0x00, 0x06, 0x1D, 0x00, 0x13, 0x05, 0x48,
+    0x01, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x02,
+    0x00, 0x15, 0x00, 0x45, 0x00, 0x10, 0x08, 0x17,
+    0x2B, 0x90, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00};
+  
+  unsigned char daisy[] = {0x99, 0x66, 0xcc, 0x33};
+  unsigned char *regdata;
+  unsigned int i;
+  
+  pv8630_write_byte (fd, 0x02, PV8630_RMODE);
+  for (i = 0; i < sizeof (daisy); i++)
+    {
+      pv8630_write_byte(fd, daisy[i], PV8630_RDATA);
+    }
+  pv8630_write_byte (fd, 0x16, PV8630_RMODE);
+  lm983x_write_register (fd, 0x42, 0x06);
+
+  if (!regs)
+    return 0;
+    //    regdata = inittable;
+  else
+    regdata = regs;
+
+  for (i = 8; i < 0x60; i++)
+    {
+      lm983x_write_register (fd, i, regdata[i]);
+    }
+  for (i = 0x60; i < 0x70; i++)
+    {
+      lm983x_write_register (fd, i, 0);
+    }
+  lm983x_write_register (fd, 0x70, 0x70);
+  for (i = 0x71; i < 0x80; i++)
+    {
+      lm983x_write_register (fd, i, 0);
+    }
+  return 0;
+}
+
+int
+lm983x_reset (int fd)
+{
+  lm983x_write_register (fd, 0x07, 0x08);
+  usleep(100);
+  lm983x_write_register (fd, 0x07, 0x00);
+  usleep(100);
+
+  return 0;
+}
diff -Naur sane-1.0.2/backend/lm983x.h sane-1.0.2-hp4200/backend/lm983x.h
--- sane-1.0.2/backend/lm983x.h	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/lm983x.h	Thu Aug 10 22:18:17 2000
@@ -0,0 +1,194 @@
+/* sane - Scanner Access Now Easy.
+
+   Copyright (C) 2000 Adrian Perez Jorge
+
+   This file is part of the SANE package.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+   As a special exception, the authors of SANE give permission for
+   additional uses of the libraries contained in this release of SANE.
+
+   The exception is that, if you link a SANE library with other files
+   to produce an executable, this does not by itself cause the
+   resulting executable to be covered by the GNU General Public
+   License.  Your use of that executable is in no way restricted on
+   account of linking the SANE library code into it.
+
+   This exception does not, however, invalidate any other reasons why
+   the executable file might be covered by the GNU General Public
+   License.
+
+   If you submit changes to SANE to the maintainers to be included in
+   a subsequent release, you agree by submitting the changes that
+   those changes may be distributed with this exception intact.
+
+   If you write modifications of your own for SANE, it is your choice
+   whether to permit this exception to apply to your modifications.
+   If you do not wish that, delete this exception notice.
+
+   This file implements a backend for the HP4200C flatbed scanner
+*/
+
+
+#define INPUT_SIGNAL_POLARITY_NEGATIVE 0
+#define INPUT_SIGNAL_POLARITY_POSITIVE 1
+#define CDS_OFF 0
+#define CDS_ON (1 << 1)
+#define SENSOR_EVENODD (1 << 2) 
+#define SENSOR_STANDARD 0
+#define SENSOR_RESOLUTION_300 0
+#define SENSOR_RESOLUTION_600 (1 << 3)
+#define LINE_SKIPPING_COLOR_PHASE_DELAY(n) (((n) & 0x0f) << 4)
+
+#define PHI1_POLARITY_POSITIVE 0
+#define PHI1_POLARITY_NEGATIVE 1
+#define PHI2_POLARITY_POSITIVE 0
+#define PHI2_POLARITY_NEGATIVE (1 << 1)
+#define RS_POLARITY_POSITIVE 0
+#define RS_POLARITY_NEGATIVE (1 << 2)
+#define CP1_POLARITY_POSITIVE 0
+#define CP1_POLARITY_NEGATIVE (1 << 3)
+#define CP2_POLARITY_POSITIVE 0
+#define CP2_POLARITY_NEGATIVE (1 << 4)
+#define TR1_POLARITY_POSITIVE 0
+#define TR1_POLARITY_NEGATIVE (1 << 5)
+#define TR2_POLARITY_POSITIVE 0
+#define TR2_POLARITY_NEGATIVE (1 << 6)
+
+#define PHI1_OFF 0
+#define PHI1_ACTIVE 1
+#define PHI2_OFF 0
+#define PHI2_ACTIVE (1 << 1)
+#define RS_OFF 0
+#define RS_ACTIVE (1 << 2)
+#define CP1_OFF 0
+#define CP1_ACTIVE (1 << 3)
+#define CP2_OFF 0
+#define CP2_ACTIVE (1 << 4)
+#define TR1_OFF 0
+#define TR1_ACTIVE (1 << 5)
+#define TR2_OFF 0
+#define TR2_ACTIVE (1 << 6)
+#define NUMBER_OF_TR_PULSES(n) (((n) - 1) << 7)
+
+#define TR_PULSE_DURATION(n) ((n) & 0x0f)
+#define TR_PHI1_GUARDBAND_DURATION(n) (((n) & 0x0f) << 4)
+
+#define CIS_TR1_TIMING_OFF 0
+#define CIS_TR1_TIMING_MODE1 1
+#define CIS_TR1_TIMING_MODE2 2
+#define FAKE_OPTICAL_BLACK_PIXELS_OFF 0
+#define FAKE_OPTICAL_BLACK_PIXELS_ON (1 << 2)
+
+#define PIXEL_RATE_3_CHANNELS 0
+#define LINE_RATE_3_CHANNELS 1
+#define MODEA_1_CHANNEL 4
+#define MODEB_1_CHANNEL 5
+#define GRAY_CHANNEL_RED 0
+#define GRAY_CHANNEL_GREEN (1 << 3)
+#define GRAY_CHANNEL_BLU (2 << 3)
+
+#define TR_RED(n) ((n) << 5)
+#define TR_GREEN(n) ((n) << 6)
+#define TR_BLUE(n) ((n) << 7)
+
+#define TR_RED_DROP(n) (n)
+#define TR_GREEN_DROP(n) ((n) << 2)
+#define TR_BLUE_DROP(n) ((n) << 4)
+
+#define ILLUMINATION_MODE(n) (n)
+
+#define HIBYTE(n) (((n) >> 8) & 0xff)
+#define LOBYTE(n) ((n) & 0xff)
+
+#define EPP_MODE 0
+#define NIBBLE_MODE 1
+
+#define PPORT_DRIVE_CURRENT(n) ((n) << 1)
+
+#define RAM_SIZE_64 0
+#define RAM_SIZE_128 1
+#define RAM_SIZE_256 2
+
+#define SRAM_DRIVER_CURRENT(n) ((n) << 2)
+#define SRAM_BANDWIDTH_4 0
+#define SRAM_BANDWIDTH_8 (1 << 4)
+#define SCANNING_FULL_DUPLEX 0
+#define SCANNING_HALF_DUPLEX (1 << 5)
+
+#define FULL_STEPPING 0
+#define MICRO_STEPPING 1
+#define CURRENT_SENSING_PHASES(n) (((n) - 1) << 1)
+#define PHASE_A_POLARITY_POSITIVE 0
+#define PHASE_A_POLARITY_NEGATIVE (1 << 2)
+#define PHASE_B_POLARITY_POSITIVE 0
+#define PHASE_B_POLARITY_NEGATIVE (1 << 3)
+#define STEPPER_MOTOR_TRISTATE 0
+#define STEPPER_MOTOR_OUTPUT (1 << 4)
+
+#define ACCELERATION_PROFILE_STOPPED(n) (n)
+#define ACCELERATION_PROFILE_25P(n) ((n) << 2)
+#define ACCELERATION_PROFILE_50P(n) ((n) << 4)
+
+#define NON_REVERSING_EXTRA_LINES(n) (n)
+#define FIRST_LINE_TO_PROCESS(n) ((n) << 3)
+
+#define KICKSTART_STEPS(n) (n)
+#define HOLD_CURRENT_TIMEOUT(n) ((n) << 3)
+
+#define PAPER_SENSOR_1_POLARITY_LOW  0
+#define PAPER_SENSOR_1_POLARITY_HIGH 1
+#define PAPER_SENSOR_1_TRIGGER_LEVEL 0
+#define PAPER_SENSOR_1_TRIGGER_EDGE  (1 << 1)
+#define PAPER_SENSOR_1_NO_STOP_SCAN  0
+#define PAPER_SENSOR_1_STOP_SCAN     (1 << 2)
+#define PAPER_SENSOR_2_POLARITY_LOW  0
+#define PAPER_SENSOR_2_POLARITY_HIGH (1 << 3)
+#define PAPER_SENSOR_2_TRIGGER_LEVEL 0
+#define PAPER_SENSOR_2_TRIGGER_EDGE  (1 << 4)
+#define PAPER_SENSOR_2_NO_STOP_SCAN  0
+#define PAPER_SENSOR_2_STOP_SCAN     (1 << 5)
+
+#define	MISCIO_1_TYPE_INPUT          0
+#define	MISCIO_1_TYPE_OUTPUT         1
+#define	MISCIO_1_POLARITY_LOW        0
+#define	MISCIO_1_POLARITY_HIGH       (1 << 1)
+#define	MISCIO_1_TRIGGER_LEVEL       0
+#define	MISCIO_1_TRIGGER_EDGE        (1 << 2)
+#define	MISCIO_1_OUTPUT_STATE_LOW    0
+#define	MISCIO_1_OUTPUT_STATE_HIGH   (1 << 3)
+#define	MISCIO_2_TYPE_INPUT          0
+#define	MISCIO_2_TYPE_OUTPUT         (1 << 4)
+#define	MISCIO_2_POLARITY_LOW        0
+#define	MISCIO_2_POLARITY_HIGH       (1 << 5)
+#define	MISCIO_2_TRIGGER_LEVEL       0
+#define	MISCIO_2_TRIGGER_EDGE        (1 << 6)
+#define	MISCIO_2_OUTPUT_STATE_LOW    0
+#define	MISCIO_2_OUTPUT_STATE_HIGH   (1 << 7)
+
+#define PIXEL_PACKING(n) ((n) << 3)
+#define DATAMODE(n) ((n) << 5)
+
+int lm983x_read_register (int fd, unsigned char reg, unsigned char *data);
+int lm983x_write_register (int fd, unsigned char reg, unsigned
+			   char value);
+int lm983x_dump_registers (int fd);
+int lm983x_lamp_off (int fd);
+int lm983x_lamp_on (int fd);
+int hp4200c_what_button (int fd);
+int lm983x_ini_scanner (int fd, unsigned char *regs);
+int lm983x_reset (int fd);
diff -Naur sane-1.0.2/backend/pv8630.c sane-1.0.2-hp4200/backend/pv8630.c
--- sane-1.0.2/backend/pv8630.c	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/pv8630.c	Fri Aug  4 19:52:22 2000
@@ -0,0 +1,119 @@
+/* sane - Scanner Access Now Easy.
+
+   Copyright (C) 2000 Adrian Perez Jorge
+
+   This file is part of the SANE package.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+   As a special exception, the authors of SANE give permission for
+   additional uses of the libraries contained in this release of SANE.
+
+   The exception is that, if you link a SANE library with other files
+   to produce an executable, this does not by itself cause the
+   resulting executable to be covered by the GNU General Public
+   License.  Your use of that executable is in no way restricted on
+   account of linking the SANE library code into it.
+
+   This exception does not, however, invalidate any other reasons why
+   the executable file might be covered by the GNU General Public
+   License.
+
+   If you submit changes to SANE to the maintainers to be included in
+   a subsequent release, you agree by submitting the changes that
+   those changes may be distributed with this exception intact.
+
+   If you write modifications of your own for SANE, it is your choice
+   whether to permit this exception to apply to your modifications.
+   If you do not wish that, delete this exception notice.
+
+   This file implements a backend for the HP4200C flatbed scanner
+
+   Support functions for the PV8630 parallel port bridge.
+*/
+
+#include <sys/ioctl.h>
+#include <errno.h>
+#include "pv8630.h"
+
+int
+pv8630_write_byte (int fd, unsigned char data, unsigned int port)
+{
+  pv8630_outrequest_args_t ioargs;
+
+  ioargs.request = PV8630_REQ_WRITEBYTE;
+  ioargs.value = data;
+  ioargs.index = port;
+  return ioctl (fd, PV8630_IOCTL_OUTREQUEST, &ioargs);
+}
+
+int
+pv8630_read_byte (int fd, unsigned char *pdata, unsigned int port)
+{
+  int retval;
+  pv8630_inrequest_args_t ioargs;
+
+  if (!pdata)
+    return -EINVAL;
+
+  ioargs.request = PV8630_REQ_READBYTE;
+  ioargs.value = 0;
+  ioargs.index = port;
+  retval = ioctl (fd, PV8630_IOCTL_INREQUEST, &ioargs);
+  if (retval >= 0)
+    {
+      *pdata = ioargs.data;
+    }
+  return retval;
+}
+
+int
+pv8630_init_eppbulk_read (int fd, unsigned long int nbytes)
+{
+  pv8630_outrequest_args_t ioargs;
+
+  ioargs.value = nbytes & 0xffff;
+  ioargs.index = nbytes >> 16;
+  ioargs.request = PV8630_REQ_EPPBULKREAD;
+  return ioctl (fd, PV8630_IOCTL_OUTREQUEST, &ioargs);
+}
+
+int
+pv8630_init_eppbulk_write (int fd, unsigned long int nbytes)
+{
+  pv8630_outrequest_args_t ioargs;
+
+  ioargs.value = nbytes & 0xffff;
+  ioargs.index = nbytes >> 16;
+  ioargs.request = PV8630_REQ_EPPBULKWRITE;
+  return ioctl (fd, PV8630_IOCTL_OUTREQUEST, &ioargs);
+}
+
+int
+pv8630_read_buttons (int fd, unsigned char *buttons)
+{
+  if (!buttons)
+    return -EINVAL;
+
+  /* enable SPP mode. */
+  pv8630_write_byte(fd, 0x02, PV8630_RMODE);
+  pv8630_read_byte(fd, buttons, PV8630_RSTATUS);
+
+  /* enable EPP mode & disable EPP timeout. */
+  pv8630_write_byte(fd, 0x16, PV8630_RMODE);
+
+  return 0;
+}
diff -Naur sane-1.0.2/backend/pv8630.h sane-1.0.2-hp4200/backend/pv8630.h
--- sane-1.0.2/backend/pv8630.h	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/backend/pv8630.h	Fri Aug 11 16:22:41 2000
@@ -0,0 +1,92 @@
+/* sane - Scanner Access Now Easy.
+
+   Copyright (C) 2000 Adrian Perez Jorge
+
+   This file is part of the SANE package.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+   As a special exception, the authors of SANE give permission for
+   additional uses of the libraries contained in this release of SANE.
+
+   The exception is that, if you link a SANE library with other files
+   to produce an executable, this does not by itself cause the
+   resulting executable to be covered by the GNU General Public
+   License.  Your use of that executable is in no way restricted on
+   account of linking the SANE library code into it.
+
+   This exception does not, however, invalidate any other reasons why
+   the executable file might be covered by the GNU General Public
+   License.
+
+   If you submit changes to SANE to the maintainers to be included in
+   a subsequent release, you agree by submitting the changes that
+   those changes may be distributed with this exception intact.
+
+   If you write modifications of your own for SANE, it is your choice
+   whether to permit this exception to apply to your modifications.
+   If you do not wish that, delete this exception notice.
+
+   This file implements a backend for the HP4200C flatbed scanner
+
+   Support functions for the PV8630 parallel port bridge.
+*/
+
+#include <asm/types.h>
+
+#define PV8630_IOCTL_INREQUEST 69
+#define PV8630_IOCTL_OUTREQUEST  70
+
+#define PV8630_REQ_READBYTE         0x00
+#define PV8630_REQ_WRITEBYTE        0x01
+#define PV8630_REQ_EPPBULKREAD      0x02
+#define PV8630_REQ_EPPBULKWRITE     0x03
+#define PV8630_REQ_FLUSHBUFFER      0x04
+#define PV8630_REQ_ENABLEINTERRUPT  0x05
+#define PV8630_REQ_DISABLEINTERRUPT 0x06
+#define PV8630_REQ_READWORD         0x08
+#define PV8630_REQ_WRITEWORD        0x09
+
+#define PV8630_RDATA       0x00
+#define PV8630_REPPADDRESS 0x01
+#define PV8630_RMODE       0x03
+#define PV8630_RSTATUS     0x04
+
+
+struct _pv8630_outrequest_args_t
+{
+  __u8 request;
+  __u16 value;
+  __u16 index;
+};
+
+typedef struct _pv8630_outrequest_args_t pv8630_outrequest_args_t;
+
+struct _pv8630_inrequest_args_t
+{
+  __u8 data;
+  __u8 request;
+  __u16 value;
+  __u16 index;
+};
+
+typedef struct _pv8630_inrequest_args_t pv8630_inrequest_args_t;
+
+int pv8630_write_byte (int fd, unsigned char data, unsigned int port);
+int pv8630_read_byte (int fd, unsigned char *pdata, unsigned int port);
+int pv8630_init_eppbulk_read (int fd, unsigned long int nbytes);
+int pv8630_init_eppbulk_write (int fd, unsigned long int nbytes);
+int pv8630_read_buttons (int fd, unsigned char *buttons);
diff -Naur sane-1.0.2/tools/hp4200offd.c sane-1.0.2-hp4200/tools/hp4200offd.c
--- sane-1.0.2/tools/hp4200offd.c	Thu Jan  1 00:00:00 1970
+++ sane-1.0.2-hp4200/tools/hp4200offd.c	Sat Aug 19 23:56:49 2000
@@ -0,0 +1,68 @@
+/*
+ * hp4200offd.c - a little daemon to turn off the lamp on the HP4200
+ * scanner after a period of inactivity (default 2 minutes).
+ * Based on `umaxoffd.c' for the Umax 1220U scanner by Paul Mackerras.
+ *
+ * Copyright 1999 Paul Mackerras.
+ *
+ * 17-August-2000: Adrian Perez Jorge
+ *  Modified for the hp4200 scanner.
+ *
+ *  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.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include "../backend/lm983x.h"
+#include "../backend/pv8630.h"
+
+int fd;
+
+main(int ac, char **av)
+{
+	unsigned long t, offt;
+	struct timeval tnow;
+	int idle_limit, ts;
+	struct stat sbuf;
+
+	idle_limit = 0;
+	if (ac > 1)
+		idle_limit = atoi(av[1]);
+	if (idle_limit <= 0)
+		idle_limit = 120;
+
+	offt = 0;
+	ts = 0;
+	for (;;) {
+		if (ts > 0) {
+			if (ts > 60)
+				ts = 60;
+			sleep(ts);
+		}
+		ts = idle_limit;
+		if (stat("/dev/usbscanner", &sbuf) < 0)
+			continue;
+		t = sbuf.st_atime;
+		if (t <= offt)
+			continue;	/* lamp is already off */
+		gettimeofday(&tnow, NULL);
+		if (tnow.tv_sec >= t + idle_limit) {
+			fd = open("/dev/usbscanner", O_RDWR | O_EXCL);
+			if (fd < 0)
+				continue;	/* not plugged in or in use */
+                        lm983x_ini_scanner (fd, NULL);
+			lm983x_lamp_off (fd);
+			close(fd);
+			gettimeofday(&tnow, NULL);
+			offt = t = tnow.tv_sec;
+		}
+		ts = t + idle_limit - tnow.tv_sec;
+	}
+	exit(0);
+}

