/* sane - Scanner Access Now Easy.
   Copyright (C) 2007 Ilia Sotnikov <hostcc@gmail.com>
   HP ScanJet 4570c support by Markham Thomas
   ADF page detection and high DPI fixes by Bernard Badeer
   scanbd integration by Damiano Scaramuzza and Bernard Badeer
   This file is part of the SANE package.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice.

   This file is part of a SANE backend for
   HP ScanJet 4500C/4570C/5500C/5550C/5590/7650 Scanners
*/

#include "../include/sane/config.h"

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif /* HAVE_NETINET_IN_H */
#include <errno.h>
#include <string.h>

#include "../include/sane/sanei_debug.h"
#include "../include/_stdint.h"
#include "hp5590_low.h"
#include "hp5590_cmds.h"

static const struct hp5590_model
hp5590_models[] = {
  {
    SCANNER_HP4570,
    0x03f0, 0x1305, "SILITEKIElwood",
    "4570C/5500C", "Workgroup scanner",
    PF_NONE
  },
  {
    SCANNER_HP5550,
    0x03f0, 0x1205, "SILITEKIPenguin",
    "4500C/5550C", "Workgroup scanner",
    PF_NO_USB_IN_USB_ACK                /* These devices need no
                                         * acknowledgment after USB-in-USB
                                         * commands */
  },
  {
    SCANNER_HP5590,
    0x03f0, 0x1705, "SILITEKIPenguin",
    "5590", "Workgroup scanner",
    PF_NONE
  },
  {
    SCANNER_HP7650,
    0x03f0, 0x1805, "SILITEKIArnold",
    "7650", "Document scanner",
    PF_NONE
  }
};

/* Debug levels */
#define DBG_err         0
#define DBG_proc        10
#define DBG_verbose     20
#define DBG_cmds        40

#define hp5590_cmds_assert(exp) if(!(exp)) { \
  DBG (DBG_err, "Assertion '%s' failed at %s:%u\n", #exp, __FILE__, __LINE__);\
  return SANE_STATUS_INVAL; \
}

#define WAKEUP_TIMEOUT          90

/* First byte of init (0x12 cmd) response */
#define INIT_FLAG_TMA           1 << 0
#define INIT_FLAG_ADF           1 << 1
#define INIT_FLAG_LCD           1 << 3

/* Power (0x24 cmd) */
#define POWER_FLAG_ON           1 << 1

/* ADF (0x03 cmd) */
#define FLAG_ADF_EMPTY          1 << 1

#define PART_NUMBER_LEN         10

#define REVERSE_MAP_LEN         128 * 1024 / sizeof(uint16_t)
#define FORWARD_MAP_LEN         128 * 1024 / sizeof(uint16_t)

/* Button flags */
/* From left to right */
/* 1:   Power
 * 1:   Scan
 * 2:   Collect
 * 3:   File
 * 4:   Email
 * 5:   Copy
 * 6,7: Up/down
 * 8:   Mode
 * 9:   Cancel
 */
#define BUTTON_FLAG_EMAIL       1 << 15
#define BUTTON_FLAG_COPY        1 << 14
#define BUTTON_FLAG_DOWN        1 << 13
#define BUTTON_FLAG_MODE        1 << 12
#define BUTTON_FLAG_UP          1 << 11
#define BUTTON_FLAG_FILE        1 << 9
#define BUTTON_FLAG_POWER       1 << 5
#define BUTTON_FLAG_SCAN        1 << 2
#define BUTTON_FLAG_COLLECT     1 << 1
#define BUTTON_FLAG_CANCEL      1 << 0

#define CMD_INIT                0x0012
#define CMD_EEPROM_ADDR         0x00f2
#define CMD_EEPROM_READ         0x0bf0
#define CMD_EEPROM_WRITE        0x0bf1
#define CMD_DATA_STATUS         0x0001
#define CMD_STOP_SCAN           0x011b
#define CMD_CONTROL_LAMP        0x00c0
#define CMD_POWER_STATUS        0x0024
#define CMD_SELECT_SOURCE       0x00d6
#define CMD_MISC_STATUS         0x0003
#define CMD_LOCK_UNLOCK         0x0000
#define CMD_SET_BASE_DPI        0x0015
#define CMD_SET_COLOR_MAP       0x0240
#define CMD_SET_SCAN_PARAMS     0x0025
#define CMD_GET_IMAGE_PARAMS    0x0034
#define CMD_START_SCAN          0x051b
#define CMD_BUTTON_STATUS       0x0020
#define CMD_LCD_STATUS          0x0021

struct init_resp
{
  uint8_t       flags;                  /* bit 0 - TMA, bit 1 - ADF, bit 3 - LCD present */
  uint8_t       id[15];                 /* SILITEKPenguin */
  uint8_t       pad1[9];                /* 00 00 00 00 00 00 00 00 00 */
  uint8_t       version[5];             /* 0.0.67 */
  uint16_t      max_dpi_x;              /* 09 60 = 2400 */
  uint16_t      max_dpi_y;              /* 09 60 = 2400 */
  uint16_t      max_pixels_x;           /* 4F B0 = 20400 (20400 / 2400 = 8.5")   */
  uint16_t      max_pixels_y;           /* 6D E0 = 28128 (28128 / 2400 = 11.72") */
  uint8_t       pad2[8];                /* 00 00 00 00 00 00 00 00 */
  uint16_t      motor_param_normal;     /* 00 64 = 100 */
  uint16_t      motor_param_max;        /* 03 E8 = 1000 */
} __attribute__ ((packed));

struct power_resp
{
  uint8_t flags;
  uint16_t unk1;
} __attribute__ ((packed));

/*
 * 215.9 mm x 297.2 mm
 * 8.5" x 11.72"
 *
 *   50 :  425.00 x  586.00
 *   75 :  637.50 x  879.50
 *  100 :  850.00 x 1172.00
 *  150 : 1275.00 x 1758.00 (base DPI)
 *  200 : 1700.00 x 2344.00
 *  300 : 2550.00 x 3516.00 (base DPI)
 *  400 : 3400.00 x 4688.00
 *  600 : 5100.00 x 7032.00 (base DPI)
 */

#define SCAN_PARAMS_SOURCE_TMA_NEGATIVES        1 << 0
#define SCAN_PARAMS_SOURCE_TMA_SLIDES           1 << 1
#define SCAN_PARAMS_SOURCE_ADF                  1 << 2
#define SCAN_PARAMS_SOURCE_FLATBED              1 << 3
#define SCAN_PARAMS_SOURCE_SIMPLEX              1 << 4
#define SCAN_PARAMS_SOURCE_DUPLEX               1 << 6

struct scan_params
{
  uint8_t source;               /*
                                 * TMA Negatives        : 11 = 17
                                 * TMA Slides           : 12 = 18
                                 * ADF                  : 14 = 20
                                 * Flatbed              : 18 = 24
                                 * ADF Duplex           : 54 = 84
                                 */

  uint16_t dpi_x;               /*
                                 *  50  : 00 64 = 100
                                 *  75  : 00 64 = 100
                                 * 100  : 00 64 = 100
                                 * 150  : 00 c8 = 200
                                 * 200  : 00 c8 = 200
                                 * 300  : 01 2c = 300
                                 * 400  : 02 58 = 600
                                 * 600  : 02 58 = 600
                                 * 1200 : 04 b0 = 1200
                                 */

  uint16_t dpi_y;               /*
                                 *  50  : 00 64 = 100
                                 *  75  : 00 64 = 100
                                 * 100  : 00 64 = 100
                                 * 150  : 00 c8 = 200
                                 * 200  : 00 c8 = 200
                                 * 300  : 01 2c = 300
                                 * 400  : 02 58 = 600
                                 * 600  : 02 58 = 600
                                 * 1200 : 04 b0 = 1200
                                 */

  uint16_t top_x;               /*
                                 * pixels * (Base DPI / current DPI)
                                 * 00 00, 01 6e = 366 (x = 425 - 302 = 123)
                                 * 04 b0 = 1200 (x = 425 - 24 = 401)
                                 */

  uint16_t top_y;               /*
                                 * pixels * (Base DPI / current DPI)
                                 * 00 00, 06 99 = 1689 (y = 585 - 21 = 564)
                                 */

  uint16_t size_x;              /* X pixels in Base DPI (CMD 15)
                                 *  50          : 04f8 =  1272                           ; 150
                                 *  75          : 04f8 =  1272                           ; 150
                                 * 100          : 04f8 =  1272                           ; 150
                                 * 100 TMA      : 00fc =   252                           ; 150
                                 * 150          : 09f6 =  2550, 09f0 = 2544, 09f4 = 2548 ; 300
                                 * 200          : 09f0 =  2544, 09f6 = 2550, 09f6 = 2550 ; 300
                                 * 300          : 09f6 =  2550, 09f0 = 2544, 09f4 = 2548 ; 300
                                 * 300 TMA      : 01fc =   508                           ; 300
                                 * 400          : 13ec =  5100                           ; 600
                                 * 600          : 13e8 =  5096, 13ec = 5100 ,13ec = 5100 ; 600
                                 * 1200         : 27a8 = 10152                           ; 1200
                                 */

  uint16_t size_y;              /* Y pixels in Base DPI (CMD 15)
                                 *  50          : 06db =  1755 ; 150
                                 *  75          : 06da =  1754 ; 150
                                 * 100          : 06db =  1755 ; 150
                                 * 100 TMA      : 0384 =   900 ; 150
                                 * 150          : 0db6 =  3510 ; 300
                                 * 200          : 0db6 =  3510 ; 300
                                 * 300          : 0db6 =  3510 ; 300
                                 * 300 TMA      : 0708 =  1800 ; 300
                                 * 400          : 1b6c =  7020 ; 600
                                 * 600          : 1b6c =  7020 ; 600
                                 * 1200         : 36d8 = 14040 ; 1200
                                 */

  uint16_t unk1;                /* 00 80 */

  uint16_t bw_gray_flag;        /*
                                 * 00 40 - bw (ntsc gray)/gray,
                                 * 00 20 - bw (by green band),
                                 * 00 10 - bw (by red band),
                                 * 00 30 - bw (by blue band),
                                 * 00 00 - color
                                 */

  uint8_t pixel_bits;           /*
                                 * bw 50/75/150/400                             : 08 =  8
                                 * bw 100/200/300/600/1200                      : 01 =  1
                                 * gray 50/75/100/150/200/400/600               : 08 =  8
                                 * color 24 bit 50/75/100/150/200/400/600       : 18 = 24
                                 * color 48 bit 100/200                         : 30 = 48
                                 */

  uint16_t flags;               /*
                                 * 50/75/100/150/200/300                : e8 40 = 59456
                                 * 400/600/1200                         : c8 40 = 51264
                                 */

  uint16_t motor_param1;        /*
                                 * 00 64 = 100
                                 */
  uint16_t motor_param2;        /*
                                 * 00 64 = 100 - ADF, Flatbed, TMA slides
                                 * 00 c8 = 200 - TMA Negatives
                                 */
  uint16_t motor_param3;        /*
                                 * 00 64 = 100 - ADF, Flatbed, TMA slides
                                 * 01 90 = 400 - TMA negatives
                                 */
  uint32_t pad1;                /* 00 00 00 00 */
  uint16_t pad2;                /* 00 00 */
  uint8_t mode;                 /* 00 - normal scan, 04 - preview scan */
  uint16_t pad3;                /* 00 00 */

  uint16_t line_width;          /* Based on current .dpi_x
                                 * bw 50                                : 03 50 =   848
                                 * gray 50                              : 03 50 =   848
                                 * color 50                             : 09 f0 =  2544 (3 * gray)
                                 *
                                 * bw 75                                : 03 50 =   848
                                 * gray 75                              : 03 50 =   848
                                 * color 75                             : 09 f0 =  2544 (3 * gray)
                                 *
                                 * bw 100                               : 00 6a =   106
                                 * gray 100                             : 03 50 =   848 (8 * bw)
                                 * color 100(24)                        : 09 f0 =  2544 (3 * gray)
                                 * color 100(48)                        : 13 e0 =  5088 (2 * color 24)
                                 * color 100(48) TMA                    : 03 f0 = 1008
                                 *
                                 * bw 150                               : 06 a4 =  1700
                                 * gray 150                             : 06 a4 =  1700
                                 * color 150                            : 13 ec =  5100 (3 * gray)
                                 *
                                 * bw 200                               : 00 d4 =   212
                                 * gray 200                             : 06 a4 =  1700 (8 * bw)
                                 * color 200(24)                        : 13 ec =  5100 (3 * gray)
                                 * color 200(48)                        : 27 a8 = 10152
                                 *
                                 * bw 300                               : 01 3e =   318
                                 * gray 300                             : 09 f4 =  2548 (8 * bw)
                                 * color 300                            : 1d dc =  7644 (3 * gray)
                                 * color 300(48) TMA                    : 0b e8 = 3048
                                 *
                                 * bw 400                               : 13 ec =  5100
                                 * gray 400                             : 13 ec =  5100
                                 * color 400                            : 3b c4 = 15300 (3 * gray)
                                 *
                                 * bw 600                               : 02 7d =   637
                                 * gray 600                             : 13 ec =  5100 (8 * bw)
                                 * color 600                            : 3b c4 = 15300 (3 * gray)
                                 *
                                 * bw 1200                              : 04 f5 =  1269
                                 */
} __attribute__ ((packed));

struct image_params
{
  uint8_t signature;            /* c0 */
  uint8_t pad1;         /* 00 */
  uint32_t image_size;          /*
                                 * bw 50                : 00 0f 23 a0 =    992 160
                                 * gray 50              : 00 0f 23 a0 =    992 160
                                 * color 50             : 00 2d 6a e0 =  2 976 480
                                 *
                                 * bw 75                : 00 0f 20 50 =    991 312
                                 * gray 75              : 00 0f 20 50 =    991 312
                                 * color 75             : 00 2d 60 f0 =  2 973 936
                                 * color 75(48)         : 00 5a 86 40 =  5 932 608
                                 *
                                 * bw 100               : 00 01 e4 74 =    124 020
                                 * gray 100             : 00 0f 23 a0 =    992 160
                                 * color 100            : 00 2d 6a e0 =  2 976 480
                                 * color 100(48)        : 00 5a 68 10 =  5 924 880
                                 * color 100(48), preview: 00 5a d5 c0 = 5 952 960
                                 *
                                 * bw 150               : 00 3c b3 10 =   3 978 000
                                 * gray 150             : 00 3c b3 10 =   3 978 000
                                 * color 150            : 00 b6 19 30 =  11 934 000
                                 * color 150(48)        : 01 6a 7b a0 =  23 755 680
                                 *
                                 * bw 200               : 00 07 91 d0 =     496 080
                                 * gray 200             : 00 3c b3 10 =   3 978 000
                                 * color 200            : 00 b6 19 30 =  11 934 000
                                 * color 200(48)        : 01 6a f3 a0 =  23 786 400
                                 *
                                 * bw 300               : 00 11 08 14 =   1 116 180
                                 * gray 300             : 00 88 77 78 =   8 943 480
                                 * color 300            : 01 99 66 68 =  26 830 440
                                 *
                                 * bw 400               : 02 22 4b 90 =  35 802 000
                                 * gray 400             : 02 22 4b 90 =  35 802 000
                                 * color 400            : 06 66 e2 b0 = 107 406 000
                                 *
                                 * bw 600               : 00 44 3b bc =   4 471 740
                                 * gray 600             : 02 22 4b 90 =  35 802 000
                                 * color 600            : 06 66 e2 b0 = 107 406 000
                                 */
  uint16_t pad2;                /* 00 00 */
  uint16_t line_width;
  uint16_t real_size_y;
  uint32_t pad3;                /* 00 00 00 00 */
} __attribute__ ((packed));

struct lamp_state
{
  uint8_t       unk1;           /* 02 */
  uint8_t       flag;           /* 01 on start, 02 - TMA, 03 - all other */
  uint16_t      turnoff_time;   /* 0a 0a, 03 36, 0f 36 */
} __attribute__ ((packed));

struct color_map
{
  uint8_t       color1[6];      /* 00 00 00 00 01 00 */
  uint8_t       color2[6];      /* 00 00 00 00 01 00 */
  uint8_t       color3[6];      /* 00 00 00 00 01 00 */
} __attribute__ ((packed));

struct reg_03
{
  uint8_t       unk1;           /* 0x0b - ADF ready, 0x03 - not */
  uint8_t       unk2;           /* 0x80 */
  uint8_t       adf_flags;      /* 0x01 - ADF ready when selected, 0x02 - not */
} __attribute__ ((packed));

/******************************************************************************/
static SANE_Status
hp5590_model_def (enum hp_scanner_types scanner_type,
                  const struct hp5590_model ** model)
{
  unsigned int i;

  hp5590_cmds_assert (model != NULL);

  for (i = 0; i < sizeof (hp5590_models) / sizeof (struct hp5590_model); i++)
    {
      if (hp5590_models[i].scanner_type == scanner_type)
        {
          *model = &hp5590_models[i];
          return SANE_STATUS_GOOD;
        }
    }

  return SANE_STATUS_INVAL;
}

/******************************************************************************/
static SANE_Status
hp5590_vendor_product_id (enum hp_scanner_types scanner_type,
                          SANE_Word * vendor_id,
                          SANE_Word * product_id)
{
  const struct hp5590_model     *model;
  SANE_Status                   ret;

  hp5590_cmds_assert (vendor_id != NULL);
  hp5590_cmds_assert (product_id != NULL);

  ret = hp5590_model_def (scanner_type, &model);
  if (ret != SANE_STATUS_GOOD)
    return ret;
  *vendor_id = model->usb_vendor_id;
  *product_id = model->usb_product_id;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_init_scanner (SANE_Int dn,
                     enum proto_flags proto_flags,
                     struct scanner_info ** info,
                     enum hp_scanner_types scanner_type)
{
  struct init_resp              init_resp;
  char                          id_buf[sizeof (init_resp.id) + 1];
  char                          ver_buf[sizeof (init_resp.version) + 1];
  SANE_Status                   ret;
  const struct hp5590_model     *scanner_model;

  /*
   * 0A 53 49 4C 49 54 45 4B 49 50 65 6E 67 75 69 6E  .SILITEKIPenguin
   * 00 00 00 00 00 00 00 00 00 30 2E 30 36 37 09 60  .........0.067..
   * 09 60 4F B0 6D E0 00 00 00 00 00 00 00 00 00 64  ..O.m..........d
   * 03 E8                                            ..
   */
  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (init_resp) == 50);

  /* Init scanner */
  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_INIT,
                    (unsigned char *) &init_resp,
                    sizeof (init_resp), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  memset (id_buf, 0, sizeof (id_buf));
  memcpy (id_buf, init_resp.id, sizeof (id_buf) - 1);
  scanner_model = NULL;
  if (scanner_type != SCANNER_NONE)
    {
      unsigned int i;
      for (i = 0; i < sizeof (hp5590_models) / sizeof (struct hp5590_model);
           i++)
        {
          if (hp5590_models[i].scanner_type == scanner_type)
            {
              if (strcmp (id_buf, hp5590_models[i].vendor_id) != 0)
                {
                  DBG (DBG_err, "%s: Vendor id mismatch for scanner HP%s - "
                       "required  '%s', got '%s'\n",
                       __func__,
                       hp5590_models[i].model,
                       hp5590_models[i].vendor_id, id_buf);
                  return SANE_STATUS_INVAL;
                }
              scanner_model = &hp5590_models[i];
              break;
            }
        }
      hp5590_cmds_assert (scanner_model != NULL);
    }

  if (scanner_model)
    {
      DBG (DBG_cmds, "HP%s flags (0x%02x)\n", scanner_model->model, init_resp.flags);

      DBG (DBG_cmds, "HP%s flags: ADF %s, TMA %s, LCD %s\n",
           scanner_model->model,
           init_resp.flags & INIT_FLAG_ADF ? "yes" : "no",
           init_resp.flags & INIT_FLAG_TMA ? "yes" : "no",
           init_resp.flags & INIT_FLAG_LCD ? "yes" : "no");


      memset (ver_buf, 0, sizeof (ver_buf));
      memcpy (ver_buf, init_resp.version, sizeof (ver_buf) - 1);
      DBG (DBG_cmds, "HP%s firmware version: %s\n", scanner_model->model, ver_buf);

      DBG (DBG_cmds, "HP%s max resolution X: %u DPI\n",
           scanner_model->model, ntohs (init_resp.max_dpi_x));
      DBG (DBG_cmds, "HP%s max resolution Y: %u DPI\n",
           scanner_model->model, ntohs (init_resp.max_dpi_y));
      DBG (DBG_cmds, "HP%s max pixels X: %u\n",
           scanner_model->model, ntohs (init_resp.max_pixels_x));
      DBG (DBG_cmds, "HP%s max pixels Y: %u\n",
           scanner_model->model, ntohs (init_resp.max_pixels_y));
      DBG (DBG_cmds, "HP%s max size X: %.3f inches\n",
           scanner_model->model,
           ntohs (init_resp.max_pixels_x) * 1.0 /
           ntohs (init_resp.max_dpi_x));
      DBG (DBG_cmds, "HP%s max size Y: %.3f inches\n", scanner_model->model,
           ntohs (init_resp.max_pixels_y) * 1.0 /
           ntohs (init_resp.max_dpi_y));
      DBG (DBG_cmds, "HP%s normal motor param: %u, max motor param: %u\n",
           scanner_model->model, ntohs (init_resp.motor_param_normal),
           ntohs (init_resp.motor_param_max));
    }

  if (info)
    {
      *info = malloc (sizeof (struct scanner_info));
      if (!*info)
        {
          DBG (DBG_err, "Memory allocation failed\n");
          return SANE_STATUS_NO_MEM;
        }
      memset (*info, 0, sizeof (struct scanner_info));

      (*info)->max_dpi_x = ntohs (init_resp.max_dpi_x);
      (*info)->max_dpi_y = ntohs (init_resp.max_dpi_y);
      (*info)->max_pixels_x = ntohs (init_resp.max_pixels_x) - 1;
      (*info)->max_pixels_y = ntohs (init_resp.max_pixels_y) + 1;
      (*info)->max_size_x = (*info)->max_pixels_x * 1.0 / (*info)->max_dpi_x;
      (*info)->max_size_y = (*info)->max_pixels_y * 1.0 / (*info)->max_dpi_y;
      (*info)->features = FEATURE_NONE;
      if (init_resp.flags & INIT_FLAG_LCD)
        (*info)->features |= FEATURE_LCD;
      if (init_resp.flags & INIT_FLAG_ADF)
        (*info)->features |= FEATURE_ADF;
      if (init_resp.flags & INIT_FLAG_TMA)
        (*info)->features |= FEATURE_TMA;
      if (scanner_model)
        {
          (*info)->model = scanner_model->model;
          (*info)->kind = scanner_model->kind;
        }
    }

  ret = hp5590_get_status (dn, proto_flags);
  if (ret != SANE_STATUS_GOOD)
    {
      DBG (DBG_err, "%s: scanner reports non-zero status: %s\n",
           __func__, sane_strstatus (ret));
      return ret;
    }
  DBG (DBG_cmds, "%s: scanner status OK\n", __func__);

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_eeprom (SANE_Int dn,
                    enum proto_flags proto_flags,
                    unsigned int addr,
                    unsigned char *data, unsigned int size)
{
  uint8_t eeprom_addr = addr;
  SANE_Status ret;

  hp5590_cmds_assert (data != NULL);
  hp5590_cmds_assert (sizeof (eeprom_addr) == 1);

  DBG (DBG_proc, "%s\n", __func__);
  DBG (DBG_proc, "Reading EEPROM: addr %04x, size %u\n", addr, size);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_EEPROM_ADDR,
                    (unsigned char *) &eeprom_addr,
                    sizeof (eeprom_addr), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_EEPROM_READ, data, size, CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_write_eeprom (SANE_Int dn,
                     enum proto_flags proto_flags,
                     unsigned int addr,
                     unsigned char *data, unsigned int size)
{
  uint8_t eeprom_addr = addr;
  SANE_Status ret;

  hp5590_cmds_assert (data != NULL);
  hp5590_cmds_assert (sizeof (eeprom_addr) == 1);

  DBG (DBG_proc, "%s\n", __func__);
  DBG (DBG_proc, "Writing EEPROM: addr %04x, size: %u\n", addr, size);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_EEPROM_ADDR,
                    (unsigned char *) &eeprom_addr,
                    sizeof (eeprom_addr), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_EEPROM_WRITE, data, size, CORE_DATA);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_scan_count (SANE_Int dn,
                        enum proto_flags proto_flags,
                        unsigned int *count)
{
  uint32_t scan_count;
  SANE_Status ret;

  hp5590_cmds_assert (count != NULL);
  hp5590_cmds_assert (sizeof (scan_count) == 4);

  DBG (DBG_proc, "%s\n", __func__);
  DBG (DBG_proc, "Reading scan count\n");

  ret = hp5590_read_eeprom (dn,
                            proto_flags,
                            0x00,
                            (unsigned char *) &scan_count,
                            sizeof (scan_count));

  if (ret != SANE_STATUS_GOOD)
    return ret;

  /* Host order */
  *count = scan_count;

  DBG (DBG_proc, "Scan count %u\n", *count);

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_inc_scan_count (SANE_Int dn,
                       enum proto_flags proto_flags)
{
  uint32_t scan_count;
  uint32_t count;
  uint32_t new_count;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);
  hp5590_cmds_assert (sizeof (scan_count) == 4);

  ret = hp5590_read_scan_count (dn, proto_flags, &count);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  scan_count = ++count;
  DBG (DBG_verbose, "Scan count = %u\n", scan_count);

  ret = hp5590_write_eeprom (dn,
                             proto_flags,
                             0x00,
                             (unsigned char *) &scan_count,
                             sizeof (scan_count));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  /* Verify its setting */
  ret = hp5590_read_scan_count (dn, proto_flags, &new_count);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  if (count != new_count)
    {
      DBG (DBG_err, "Scan count wasn't set\n");
      return SANE_STATUS_IO_ERROR;
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_max_scan_count (SANE_Int dn,
                            enum proto_flags proto_flags,
                            unsigned int *max_count)
{
  uint8_t max_scan_count[3];
  SANE_Status ret;

  hp5590_cmds_assert (max_count != NULL);
  hp5590_cmds_assert (sizeof (max_scan_count) == 3);

  DBG (DBG_proc, "%s\n", __func__);
  DBG (DBG_proc, "Reading max scan count\n");

  ret = hp5590_read_eeprom (dn,
                            proto_flags,
                            0x10,
                            (unsigned char *) max_scan_count,
                            sizeof (max_scan_count));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  /* Host order */
  *max_count = 0;
  memcpy (max_count, max_scan_count, sizeof (max_scan_count));

  DBG (DBG_proc, "Max scan count %u\n", *max_count);

  return SANE_STATUS_GOOD;
}

/***************************************************************************
 *
 * EEPROM contents:
 *
 * 0000: 6A 11 00 00 FF FF FF FF FF FF FF FF 09 0E 0F 00  j...............
 * 0010: 0C 13 0F 00 00 3A 00 FF FF FF 4E 35 39 45 54 52  ..........N59ETR
 * 0020: 31 52 4D 00 FF FF 00 16 00 0A 00 0D 00 11 00 10  1RM.............
 * 0030: FF FF FF FF FF FF FF FF FF 00 FF FF FF FF FF FF  ................
 * 0040: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 0050: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 0060: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 0070: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 0080: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 0090: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 00A0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 00B0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 00C0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 00D0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 00E0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................
 * 00F0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF     ...............
 *
 * Addr 0x00, len: 0x04 - scan count (little-endian)
 * Addr 0x1A, len: 0x0A - part number (w/o first 'CN' letters)
 * Addr 0x10, len: 0x03 - max scan count (little-endian) (0C 13 0F)
 *
 */

/******************************************************************************/
static __sane_unused__ SANE_Status
hp5590_read_eeprom_all_cmd (SANE_Int dn,
                            enum proto_flags proto_flags)
{
  uint8_t eeprom[255];
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  ret = hp5590_read_eeprom (dn,
                            proto_flags,
                            0x00,
                            (unsigned char *) eeprom,
                            sizeof (eeprom));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_verbose, "hp5590_read_eeprom_all_cmd: rc = %d\n", ret);
  {
    const int LEN = 4096;
    char buf[LEN];
    char* p = buf;
    for (size_t i = 0; i < sizeof(eeprom); ++i) {
      if (i % 16 == 0) {
        int n = sprintf(p, "\n%04x ", (int)i);
        if (n < 0) {
          break;
        }
        p += n;
      }
      int n = sprintf(p, " %02x", eeprom[i]);
      if (n < 0 ) {
        break;
      }
      p += n;
    }
    *p = '\0';
    DBG (DBG_verbose, "dump:%s\n", buf);
  }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_part_number (SANE_Int dn,
                         enum proto_flags proto_flags)
{
  unsigned int part_number_len = PART_NUMBER_LEN;
  unsigned char part_number[PART_NUMBER_LEN + 1];
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  memset (part_number, 0, sizeof (part_number));
  ret = hp5590_read_eeprom (dn,
                            proto_flags,
                            0x1a,
                            part_number, part_number_len);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_cmds, "Part number: '%s'\n", part_number);

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_is_data_available (SANE_Int dn,
                          enum proto_flags proto_flags)
{
  uint8_t data_status;
  SANE_Status ret;
  SANE_Bool data_available;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (data_status) == 1);
  data_available = SANE_FALSE;

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_DATA_STATUS,
                    (unsigned char *) &data_status,
                    sizeof (data_status), CORE_DATA);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_cmds, "%s: Data status: %02x\n", __func__, data_status);

  if (data_status == 0x40)
    data_available = SANE_TRUE;

  DBG (DBG_cmds, "%s: Data is %s\n",
       __func__,
       data_available == SANE_TRUE ? "available" : "not available");

  return data_available == SANE_TRUE ? SANE_STATUS_GOOD : SANE_STATUS_NO_DOCS;
}

/******************************************************************************/
static SANE_Status
hp5590_stop_scan (SANE_Int dn,
                  enum proto_flags proto_flags)
{
  uint8_t reg_011b = 0x40;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (reg_011b) == 1);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_STOP_SCAN,
                    (unsigned char *) &reg_011b,
                    sizeof (reg_011b), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  /* FIXME */

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_turnon_lamp (SANE_Int dn,
                    enum proto_flags proto_flags,
                    enum hp5590_lamp_state state)
{
  struct lamp_state lamp_state;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (lamp_state) == 4);

  if (state == LAMP_STATE_TURNON)
    {
      /* Turn on lamp */
      lamp_state.unk1 = 0x02;
      lamp_state.flag = 0x01;
      lamp_state.turnoff_time = htons (0x0a0a);
      DBG (DBG_cmds, "%s: turning lamp on\n", __func__);
    }

  if (state == LAMP_STATE_TURNOFF)
    {
      /* Turn off lamp */
      lamp_state.unk1 = 0x02;
      lamp_state.flag = 0x02;
      lamp_state.turnoff_time = htons (0x0a0a);
      DBG (DBG_cmds, "%s: turning lamp off\n", __func__);
    }

  if (state == LAMP_STATE_SET_TURNOFF_TIME)
    {
      /* Turn on lamp */
      lamp_state.unk1 = 0x02;
      lamp_state.flag = 0x03;
      lamp_state.turnoff_time = htons (0x0336);
      DBG (DBG_cmds, "%s: setting turnoff time\n", __func__);
    }

  if (state == LAMP_STATE_SET_TURNOFF_TIME_LONG)
    {
      /* Turn on lamp */
      lamp_state.unk1 = 0x02;
      lamp_state.flag = 0x03;
      lamp_state.turnoff_time = htons (0x0f36);
      DBG (DBG_cmds, "%s: setting long turnoff time\n", __func__);
    }

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_CONTROL_LAMP,
                    (unsigned char *) &lamp_state,
                    sizeof (lamp_state), CORE_DATA);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  if (state == LAMP_STATE_TURNON)
    {
      ret = hp5590_init_scanner (dn, proto_flags, NULL, SCANNER_NONE);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_power_status (SANE_Int dn,
                     enum proto_flags proto_flags)
{
  struct power_resp power_resp;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (power_resp) == 3);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_POWER_STATUS,
                    (unsigned char *) &power_resp,
                    sizeof (power_resp), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_cmds, "Power status: %s (%02x)\n",
       power_resp.flags & POWER_FLAG_ON ? "on" : "off", power_resp.flags);

  if (!(power_resp.flags & POWER_FLAG_ON))
    {
      DBG (DBG_cmds, "Turning lamp on\n");
      ret = hp5590_turnon_lamp (dn, proto_flags, LAMP_STATE_TURNON);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_error_code (SANE_Int dn,
                        enum proto_flags proto_flags,
                        unsigned int *adf_flags)
{
  struct reg_03 reg_03;
  SANE_Status   ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (reg_03) == 3);
  hp5590_cmds_assert (adf_flags != NULL);

  memset (&reg_03, 0, sizeof (reg_03));
  *adf_flags = 0;
  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN,
                    CMD_MISC_STATUS,
                    (unsigned char *) &reg_03,
                    sizeof (reg_03), CORE_NONE);

  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_cmds, "%s: adf_flags: %04x\n", __func__, reg_03.adf_flags);
  DBG (DBG_cmds, "%s: unk1     : %04x\n", __func__, reg_03.unk1);
  DBG (DBG_cmds, "%s: unk2     : %04x\n", __func__, reg_03.unk2);

  *adf_flags = reg_03.adf_flags;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_reset_scan_head (SANE_Int dn,
                        enum proto_flags proto_flags)
{
  SANE_Status   ret;

  DBG (DBG_proc, "%s\n", __func__);

  ret = hp5590_turnon_lamp (dn, proto_flags, LAMP_STATE_TURNOFF);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  usleep (100 * 1000);

  ret = hp5590_turnon_lamp (dn, proto_flags, LAMP_STATE_TURNON);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_select_source_and_wakeup (SANE_Int dn,
                                 enum proto_flags proto_flags,
                                 enum scan_sources source,
                                 SANE_Bool extend_lamp_timeout)
{
  uint8_t       reg_d6 = 0x04;
  SANE_Status   ret;
  unsigned int  adf_flags;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (reg_d6) == 1);

  if (source == SOURCE_TMA_SLIDES || source == SOURCE_TMA_NEGATIVES)
    {
      ret = hp5590_turnon_lamp (dn, proto_flags, LAMP_STATE_TURNOFF);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }
  else
    {
      ret = hp5590_turnon_lamp (dn,
                                proto_flags,
                                extend_lamp_timeout == SANE_TRUE ?
                                LAMP_STATE_SET_TURNOFF_TIME_LONG :
                                LAMP_STATE_SET_TURNOFF_TIME);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }

  switch (source)
    {
      case SOURCE_ADF:
      case SOURCE_ADF_DUPLEX:
        reg_d6 = 0x03;
        break;
      case SOURCE_FLATBED:
        reg_d6 = 0x04;
        break;
      case SOURCE_TMA_SLIDES:
        reg_d6 = 0x02;
        break;
      case SOURCE_TMA_NEGATIVES:
        reg_d6 = 0x01;
        break;
      case SOURCE_NONE:
        DBG (DBG_err, "Scan source not selected\n");
        return SANE_STATUS_INVAL;
      default:
        DBG (DBG_err, "Unknown scan source: %u\n", source);
        return SANE_STATUS_INVAL;
    }

  DBG (DBG_cmds, "Scan source: %u\n", reg_d6);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_SELECT_SOURCE,
                    (unsigned char *) &reg_d6,
                    sizeof (reg_d6), CORE_NONE);

  if (ret != SANE_STATUS_GOOD && ret != SANE_STATUS_DEVICE_BUSY)
    return ret;

  ret = hp5590_read_error_code (dn, proto_flags, &adf_flags);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  if (adf_flags & FLAG_ADF_EMPTY)
    {
      DBG (DBG_cmds, "ADF empty\n");
      return SANE_STATUS_NO_DOCS;
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_lock_unlock_scanner (SANE_Int dn,
                            enum proto_flags proto_flags)
{
  uint8_t       reg_00 = 0x01;
  SANE_Status   ret;
  unsigned int  adf_flags;
  unsigned int  waiting;

  DBG (DBG_proc, "%s\n", __func__);
  hp5590_cmds_assert (sizeof (reg_00) == 1);

  for (waiting = 0; waiting < WAKEUP_TIMEOUT; waiting++, sleep (1))
    {
      ret = hp5590_cmd (dn,
                        proto_flags,
                        CMD_VERIFY,
                        CMD_LOCK_UNLOCK,
                        (unsigned char *) &reg_00,
                        sizeof (reg_00), CORE_NONE);
      if (ret == SANE_STATUS_GOOD)
        break;

      if (ret != SANE_STATUS_DEVICE_BUSY)
        return ret;

      DBG (DBG_cmds, "Waiting for scanner...\n");
      ret = hp5590_read_error_code (dn, proto_flags, &adf_flags);
      if (ret != SANE_STATUS_GOOD)
        return ret;

      if (adf_flags & FLAG_ADF_EMPTY)
        {
          DBG (DBG_cmds, "ADF empty\n");
          return SANE_STATUS_NO_DOCS;
        }
    }

  if (waiting == WAKEUP_TIMEOUT)
    return SANE_STATUS_DEVICE_BUSY;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_set_base_dpi (SANE_Int dn,
                     enum proto_flags proto_flags,
                     struct scanner_info *scanner_info,
                     unsigned int base_dpi)
{
  uint16_t _base_dpi;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (scanner_info != NULL);
  hp5590_cmds_assert (base_dpi != 0);
  hp5590_cmds_assert (sizeof (_base_dpi) == 2);

  if (base_dpi > scanner_info->max_dpi_x
      || base_dpi > scanner_info->max_dpi_y)
    {
      DBG (DBG_err, "Base DPI too large "
           "(given: %u, max X DPI: %u, max Y DPI: %u)\n",
           base_dpi,
           scanner_info->max_dpi_x,
           scanner_info->max_dpi_y);
      return SANE_STATUS_INVAL;
    }

  _base_dpi = htons (base_dpi);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_SET_BASE_DPI,
                    (unsigned char *) &_base_dpi,
                    sizeof (_base_dpi), CORE_DATA);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_set_color_map (SANE_Int dn,
                      enum proto_flags proto_flags,
                      unsigned int base_dpi)
{
  struct color_map color_map;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (color_map) == 18);
  hp5590_cmds_assert (base_dpi != 0);

  memset (&color_map, 0, sizeof (color_map));
  if (base_dpi < 2400)
    {
      color_map.color1[4] = 0x01;
      color_map.color2[4] = 0x01;
      color_map.color3[4] = 0x01;
    }
  else
    {
      if (0) {
        /* Does not work with hp5590 and 2400 dpi. */
        color_map.color1[2] = 0xff;
        color_map.color1[3] = 0x01;
        color_map.color1[4] = 0x04;
        color_map.color1[5] = 0x02;

        color_map.color2[2] = 0xff;
        color_map.color2[3] = 0x01;
        color_map.color2[4] = 0x04;
        color_map.color2[5] = 0x02;

        color_map.color3[2] = 0xff;
        color_map.color3[3] = 0x01;
        color_map.color3[4] = 0x04;
        color_map.color3[5] = 0x02;

      } else {
        /* Needs documentation. */
        color_map.color1[2] = 0x00;
        color_map.color1[3] = 0x00;
        color_map.color1[4] = 0x01;
        color_map.color1[5] = 0x00;

        color_map.color2[2] = 0x00;
        color_map.color2[3] = 0x00;
        color_map.color2[4] = 0x01;
        color_map.color2[5] = 0x00;

        color_map.color3[2] = 0x00;
        color_map.color3[3] = 0x00;
        color_map.color3[4] = 0x01;
        color_map.color3[5] = 0x00;
      }
    }

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_SET_COLOR_MAP,
                    (unsigned char *) &color_map,
                    sizeof (color_map), CORE_DATA);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************
 * Calculate base DPI
 * Base DPI is what image dimensions are calculated on (top X,Y; bottom X,Y)
 * Calculated according the following rules:
 * Base DPI is 150 when   0 <  DPI < 150;
 * Base DPI is 300 when 150 <= DPI <= 300;
 * Base DPI is 600 when 300 < DPI <= 600;
 * Base DPI is 1200 when 600 < DPI;
 */
static SANE_Status
calc_base_dpi (unsigned int dpi, unsigned int *base_dpi)
{

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (base_dpi != NULL);
  hp5590_cmds_assert (dpi != 0);

  *base_dpi = 0;

  if (dpi < 150)
    {
      *base_dpi = 150;
      return SANE_STATUS_GOOD;
    }

  if (dpi >= 150 && dpi <= 300)
    {
      *base_dpi = 300;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 300 && dpi <= 600)
    {
      *base_dpi = 600;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 600 && dpi <= 1200)
    {
      *base_dpi = 1200;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 1200 && dpi <= 2400)
    {
      *base_dpi = 2400;
      return SANE_STATUS_GOOD;
    }

  DBG (DBG_err, "Error calculating base DPI (given DPI: %u)\n", dpi);
  return SANE_STATUS_INVAL;
}

/******************************************************************************/
static SANE_Status
calc_scanner_dpi (unsigned int dpi, unsigned int *scanner_dpi)
{
  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (scanner_dpi != NULL);
  hp5590_cmds_assert (dpi != 0);

  if (dpi <= 100)
    {
      *scanner_dpi = 100;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 100 && dpi <= 200)
    {
      *scanner_dpi = 200;
      return SANE_STATUS_GOOD;
    }

  if (dpi == 300)
    {
      *scanner_dpi = 300;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 300 && dpi <= 600)
    {
      *scanner_dpi = 600;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 600 && dpi <= 1200)
    {
      *scanner_dpi = 1200;
      return SANE_STATUS_GOOD;
    }

  if (dpi > 1200 && dpi <= 2400)
    {
      *scanner_dpi = 2400;
      return SANE_STATUS_GOOD;
    }

  DBG (DBG_err, "Error calculating scanner DPI (given DPI: %u)\n", dpi);
  return SANE_STATUS_INVAL;
}

/******************************************************************************/
static SANE_Status
hp5590_calc_pixel_bits (unsigned int dpi, enum color_depths color_depth,
                        unsigned int *pixel_bits)
{
  unsigned int scanner_dpi;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (pixel_bits != NULL);
  hp5590_cmds_assert (dpi != 0);

  ret = calc_scanner_dpi (dpi, &scanner_dpi);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  if (color_depth == DEPTH_COLOR_48)
    {
      *pixel_bits = 48;
      return SANE_STATUS_GOOD;
    }

  if (color_depth == DEPTH_COLOR_24)
    {
      *pixel_bits = 24;
      return SANE_STATUS_GOOD;
    }

  if (color_depth == DEPTH_GRAY)
    {
      *pixel_bits = 8;
      return SANE_STATUS_GOOD;
    }

  if (color_depth == DEPTH_BW)
    {
      if (dpi == scanner_dpi)
        *pixel_bits = 1;
      else
        *pixel_bits = 8;
      return SANE_STATUS_GOOD;
    }

  DBG (DBG_err, "Error calculating pixel bits (given DPI: %u)\n", dpi);
  return SANE_STATUS_INVAL;
}

/******************************************************************************/
static SANE_Status
hp5590_set_scan_area (SANE_Int dn,
                      enum proto_flags proto_flags,
                      struct scanner_info *scanner_info,
                      unsigned int top_x, unsigned int top_y,
                      unsigned int width, unsigned int height,
                      unsigned int dpi, enum color_depths color_depth,
                      enum scan_modes scan_mode,
                      enum scan_sources scan_source)
{
  struct scan_params    scan_params;
  unsigned int          scanner_top_x;
  unsigned int          scanner_top_y;
  unsigned int          scanner_pixels_x;
  unsigned int          scanner_pixels_y;
  unsigned int          base_dpi;
  unsigned int          scanner_dpi;
  unsigned int          pixel_bits;
  unsigned int          scanner_line_width;
  unsigned int          max_pixels_x_current_dpi;
  unsigned int          max_pixels_y_current_dpi;
  unsigned int          pixels_x;
  unsigned int          pixels_y;
  SANE_Status           ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (scan_params) == 37);
  hp5590_cmds_assert (dpi != 0);
  hp5590_cmds_assert (scanner_info != NULL);

  memset (&scan_params, 0, sizeof (scan_params));

  scan_params.source = SCAN_PARAMS_SOURCE_SIMPLEX;
  if (scan_source == SOURCE_ADF)
    scan_params.source |= SCAN_PARAMS_SOURCE_ADF;
  if (scan_source == SOURCE_ADF_DUPLEX)
    scan_params.source |= SCAN_PARAMS_SOURCE_ADF | SCAN_PARAMS_SOURCE_DUPLEX;
  if (scan_source == SOURCE_FLATBED)
    scan_params.source |= SCAN_PARAMS_SOURCE_FLATBED;
  if (scan_source == SOURCE_TMA_SLIDES)
    scan_params.source |= SCAN_PARAMS_SOURCE_TMA_SLIDES;
  if (scan_source == SOURCE_TMA_NEGATIVES)
    scan_params.source |= SCAN_PARAMS_SOURCE_TMA_NEGATIVES;

  DBG (DBG_cmds, "Scan params. source : 0x%04x\n", scan_params.source);

  DBG (DBG_cmds, "DPI: %u\n", dpi);
  if (dpi > scanner_info->max_dpi_x || dpi > scanner_info->max_dpi_y)
    {
      DBG (DBG_err, "DPI too large "
           "(given: %u, max X DPI: %u, max Y DPI: %u)\n",
           dpi, scanner_info->max_dpi_x, scanner_info->max_dpi_y);
      return SANE_STATUS_INVAL;
    }

  ret = calc_base_dpi (dpi, &base_dpi);
  if (ret != SANE_STATUS_GOOD)
    return ret;
  DBG (DBG_cmds, "Base DPI: %u\n", base_dpi);

  ret = calc_scanner_dpi (dpi, &scanner_dpi);
  if (ret != SANE_STATUS_GOOD)
    return ret;
  DBG (DBG_cmds, "Scanner DPI: %u\n", scanner_dpi);

  scan_params.dpi_x = htons (scanner_dpi);
  scan_params.dpi_y = htons (scanner_dpi);

  DBG (DBG_cmds, "DPI X: 0x%04x\n", scanner_dpi);
  DBG (DBG_cmds, "DPI Y: 0x%04x\n", scanner_dpi);

  ret = hp5590_calc_pixel_bits (dpi, color_depth, &pixel_bits);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_cmds, "Pixel bits: %u\n", pixel_bits);

  scan_params.pixel_bits = pixel_bits;

  scan_params.bw_gray_flag = 0;
  if (color_depth == DEPTH_BW || color_depth == DEPTH_GRAY)
    scan_params.bw_gray_flag = htons (0x40);

  scan_params.flags = htons (0xe840);
  if (dpi > 300 && dpi <= 1200)
    scan_params.flags = htons (0xc840);
  if (dpi > 1200)
    scan_params.flags = htons (0xc040);

  scan_params.motor_param1 = htons (100);
  scan_params.motor_param2 = htons (100);
  scan_params.motor_param3 = htons (100);
  if (scan_source == SOURCE_TMA_NEGATIVES)
    {
      scan_params.motor_param2 = htons (200);
      scan_params.motor_param3 = htons (400);
    }

  scan_params.unk1 = htons (0x80);

  scan_params.mode = 0;
  if (scan_mode == MODE_PREVIEW)
    scan_params.mode = 0x04;

  max_pixels_x_current_dpi = (float) scanner_info->max_size_x * dpi;
  max_pixels_y_current_dpi = (float) scanner_info->max_size_y * dpi;
  if (   scan_source == SOURCE_TMA_NEGATIVES
      || scan_source == SOURCE_TMA_SLIDES)
    {
      max_pixels_x_current_dpi = (float) (TMA_MAX_X_INCHES * dpi);
      max_pixels_y_current_dpi = (float) (TMA_MAX_Y_INCHES * dpi);
    }
  /* In ADF mode the device can scan up to ADF_MAX_Y_INCHES, which is usually
   * bigger than what scanner reports back during initialization
   */
  if ( scan_source == SOURCE_ADF )
    max_pixels_y_current_dpi = (float) (ADF_MAX_Y_INCHES * dpi);

  /* Allow two times of max pixels for ADF Duplex mode */
  if (scan_source == SOURCE_ADF_DUPLEX)
    max_pixels_y_current_dpi *= 2;

  pixels_x = width;
  pixels_y = height;

  scanner_top_x = (float) (top_x * (1.0 * base_dpi / dpi));
  scanner_top_y = (float) (top_y * (1.0 * base_dpi / dpi));
  scanner_pixels_x = (float) (pixels_x * (1.0 * base_dpi / dpi));
  scanner_pixels_y = (float) (pixels_y * (1.0 * base_dpi / dpi));

  DBG (DBG_cmds, "Top X: %u, top Y: %u, size X: %u, size Y: %u\n",
       top_x, top_y, pixels_x, pixels_y);
  DBG (DBG_cmds, "Scanner top X: %u, top Y: %u, size X: %u, size Y: %u\n",
       scanner_top_x, scanner_top_y,
       scanner_pixels_x, scanner_pixels_y);

  if (top_x + pixels_x > max_pixels_x_current_dpi)
    {
      DBG (DBG_err, "Top X (%u) + pixels X (%u) exceeds max X %u\n",
           top_x, pixels_x, max_pixels_x_current_dpi);
      return SANE_STATUS_INVAL;
    }

  if (top_y + pixels_y > max_pixels_y_current_dpi)
    {
      DBG (DBG_err, "Top Y (%u) + pixels Y (%u) exceeds max Y %u\n",
           top_y, pixels_y, max_pixels_y_current_dpi);
      return SANE_STATUS_INVAL;
    }

  scan_params.top_x = htons (scanner_top_x);
  scan_params.top_y = htons (scanner_top_y);
  scan_params.size_x = htons (scanner_pixels_x);
  scan_params.size_y = htons (scanner_pixels_y);

  scanner_line_width = (float) (pixels_x
                                * (1.0 * scanner_dpi / dpi) / 8 * pixel_bits);

  /* Scanner hangs at scan command if line width less than 18 */
  if (scanner_line_width < 18)
    {
      DBG (DBG_err, "Line width too small, extending to minimum\n");
      scanner_line_width = 18;
    }
  scan_params.line_width = htons (scanner_line_width);
  DBG (DBG_cmds, "Line width: %u\n", scanner_line_width);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_SET_SCAN_PARAMS,
                    (unsigned char *) &scan_params,
                    sizeof (scan_params), CORE_DATA);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_image_params (SANE_Int dn,
                          enum proto_flags proto_flags)
{
  struct image_params image_params;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (image_params) == 16);

  memset (&image_params, 0, sizeof (image_params));

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_GET_IMAGE_PARAMS,
                    (unsigned char *) &image_params,
                    sizeof (image_params), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  if (image_params.signature != 0xc0)
    {
      DBG (DBG_err, "Wrong signature for image parameters structure "
           "received (needed 0xc0, got %02x)\n", image_params.signature);
      return SANE_STATUS_IO_ERROR;
    }
  DBG (DBG_cmds, "Received image params:\n");
  DBG (DBG_cmds, "Signature %02x\n", image_params.signature);
  DBG (DBG_cmds, "Image size %lu (%04lx)\n",
       (unsigned long) ntohl (image_params.image_size),
       (unsigned long) ntohl (image_params.image_size));
  DBG (DBG_cmds, "Line width: %u (%02x)\n", ntohs (image_params.line_width),
       ntohs (image_params.line_width));
  DBG (DBG_cmds, "Actual size Y: %u (%02x)\n",
       ntohs (image_params.real_size_y), ntohs (image_params.real_size_y));

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_set_scan_params (SANE_Int dn,
                        enum proto_flags proto_flags,
                        struct scanner_info * scanner_info,
                        unsigned int top_x, unsigned int top_y,
                        unsigned int width, unsigned int height,
                        unsigned int dpi, enum color_depths color_depth,
                        enum scan_modes scan_mode,
                        enum scan_sources scan_source)
{
  unsigned int base_dpi;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (scanner_info != NULL);
  hp5590_cmds_assert (dpi != 0);

  /* Lock scanner */
  ret = hp5590_lock_unlock_scanner (dn, proto_flags);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  /* Set base DPI */
  ret = calc_base_dpi (dpi, &base_dpi);
  if (ret != SANE_STATUS_GOOD)
    {
      /* Unlock scanner */
      hp5590_lock_unlock_scanner (dn, proto_flags);
      return ret;
    }

  DBG (DBG_cmds, "Set base DPI: %u\n", base_dpi);
  ret = hp5590_set_base_dpi (dn, proto_flags, scanner_info, base_dpi);
  if (ret != SANE_STATUS_GOOD)
    {
      /* Unlock scanner */
      hp5590_lock_unlock_scanner (dn, proto_flags);
      return ret;
    }

  /* Set color map */
  ret = hp5590_set_color_map (dn, proto_flags, base_dpi);
  if (ret != SANE_STATUS_GOOD)
    {
      /* Unlock scanner */
      hp5590_lock_unlock_scanner (dn, proto_flags);
      return ret;
    }

  ret = hp5590_set_scan_area (dn,
                              proto_flags,
                              scanner_info,
                              top_x, top_y,
                              width, height,
                              dpi, color_depth, scan_mode, scan_source);
  if (ret != SANE_STATUS_GOOD)
    {
      /* Unlock scanner */
      hp5590_lock_unlock_scanner (dn, proto_flags);
      return ret;
    }

  ret = hp5590_read_image_params (dn, proto_flags);
  if (ret != SANE_STATUS_GOOD)
    {
      /* Unlock scanner */
      hp5590_lock_unlock_scanner (dn, proto_flags);
      return ret;
    }

  /* Unlock scanner */
  ret = hp5590_lock_unlock_scanner (dn, proto_flags);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_send_reverse_calibration_map (SANE_Int dn,
                                     enum proto_flags proto_flags)
{
  unsigned int reverse_map_size = REVERSE_MAP_LEN;
  uint16_t reverse_map[REVERSE_MAP_LEN];
  unsigned int i;
  uint16_t val;
  unsigned int len;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);
  DBG (DBG_proc, "Preparing reverse calibration map\n");
  val = 0xffff;
  len = reverse_map_size / 4;
  for (i = 0; i < len; i++)
    {
      reverse_map[i] = htons (val);
      val -= 1;
    }

  for (i = len; i < len * 2; i++)
    {
      reverse_map[i] = htons (val);
      val -= 1;
    }

  for (i = len * 2; i < len * 3; i++)
    {
      reverse_map[i] = htons (val);
      val -= 1;
    }

  for (i = len * 3; i < len * 4; i++)
    {
      if (1) {
        reverse_map[i] = htons (0xffff);
      } else {
        reverse_map[i] = htons (val);
        val -= 1;
      }
    }

  DBG (DBG_proc, "Done preparing reverse calibration map. n=%u, bytes_per_entry=%zu\n", reverse_map_size, sizeof(uint16_t));

  ret = hp5590_bulk_write (dn,
                           proto_flags,
                           0x2b,
                           (unsigned char *) reverse_map,
                           reverse_map_size * sizeof (uint16_t));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_send_forward_calibration_maps (SANE_Int dn,
                                      enum proto_flags proto_flags)
{
  unsigned int forward_map_size = FORWARD_MAP_LEN;
  uint16_t forward_map[FORWARD_MAP_LEN];
  SANE_Status ret;
  unsigned int i;
  uint16_t val;

  DBG (DBG_proc, "%s\n", __func__);
  DBG (DBG_proc, "Preparing forward calibration map\n");
  val = 0x0000;
  for (i = 0; i < forward_map_size; i++)
    {
      forward_map[i] = htons (val);
      if (val < 0xffff)
        val += 1;
    }
  DBG (DBG_proc, "Done preparing forward calibration map. n=%u, bytes_per_entry=%zu\n", forward_map_size, sizeof(uint16_t));

  ret = hp5590_bulk_write (dn,
                           proto_flags,
                           0x012a,
                           (unsigned char *) forward_map,
                           forward_map_size * sizeof (uint16_t));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_bulk_write (dn,
                           proto_flags,
                           0x022a,
                           (unsigned char *) forward_map,
                           forward_map_size * sizeof (uint16_t));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_bulk_write (dn,
                           proto_flags,
                           0x032a,
                           (unsigned char *) forward_map,
                           forward_map_size * sizeof (uint16_t));
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read (SANE_Int dn,
             enum proto_flags proto_flags,
             unsigned char *bytes, unsigned int size,
             void *state)
{
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (bytes != NULL);
  hp5590_cmds_assert (state != NULL);

  ret = hp5590_bulk_read (dn, proto_flags, bytes, size, state);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_start_scan (SANE_Int dn,
                   enum proto_flags proto_flags)
{
  uint8_t reg_051b = 0x40;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (sizeof (reg_051b) == 1);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_VERIFY,
                    CMD_START_SCAN,
                    (unsigned char *) &reg_051b,
                    sizeof (reg_051b), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_buttons (SANE_Int dn,
                     enum proto_flags proto_flags,
                     enum button_status * status)
{
  uint16_t button_status;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (status != NULL);
  hp5590_cmds_assert (sizeof (button_status) == 2);

  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_BUTTON_STATUS,
                    (unsigned char *) &button_status,
                    sizeof (button_status), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  *status = BUTTON_NONE;

  /* Network order */
  button_status = ntohs (button_status);
  DBG (DBG_cmds, "Button status: %04x\n", button_status);
  DBG (DBG_cmds, "Power: %s, Scan: %s, Collect: %s, File: %s, Email: %s, Copy: %s, "
       "Up: %s, Down: %s, Mode: %s, Cancel: %s\n",
       button_status & BUTTON_FLAG_POWER ? " on" : "off",
       button_status & BUTTON_FLAG_SCAN ? " on" : "off",
       button_status & BUTTON_FLAG_COLLECT ? " on" : "off",
       button_status & BUTTON_FLAG_FILE ? " on" : "off",
       button_status & BUTTON_FLAG_EMAIL ? " on" : "off",
       button_status & BUTTON_FLAG_COPY ? " on" : "off",
       button_status & BUTTON_FLAG_UP ? " on" : "off",
       button_status & BUTTON_FLAG_DOWN ? " on" : "off",
       button_status & BUTTON_FLAG_MODE ? " on" : "off",
       button_status & BUTTON_FLAG_CANCEL ? " on" : "off");

  if (button_status & BUTTON_FLAG_POWER)
    *status = BUTTON_POWER;

  if (button_status & BUTTON_FLAG_SCAN)
    *status = BUTTON_SCAN;

  if (button_status & BUTTON_FLAG_COLLECT)
    *status = BUTTON_COLLECT;

  if (button_status & BUTTON_FLAG_FILE)
    *status = BUTTON_FILE;

  if (button_status & BUTTON_FLAG_EMAIL)
    *status = BUTTON_EMAIL;

  if (button_status & BUTTON_FLAG_COPY)
    *status = BUTTON_COPY;

  if (button_status & BUTTON_FLAG_UP)
    *status = BUTTON_UP;

  if (button_status & BUTTON_FLAG_DOWN)
    *status = BUTTON_DOWN;

  if (button_status & BUTTON_FLAG_MODE)
    *status = BUTTON_MODE;

  if (button_status & BUTTON_FLAG_CANCEL)
    *status = BUTTON_CANCEL;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
hp5590_read_lcd_and_led (SANE_Int dn,
                     enum proto_flags proto_flags,
                     SANE_Int * lcd_counter,
                     enum color_led_status * color_led)
{
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  hp5590_cmds_assert (lcd_counter != NULL);
  hp5590_cmds_assert (color_led != NULL);

  /*
   * Read LCD status bytes and get current value of counter and
   * state of color/black_white LED.
   * data[0x29]: LCD counter value, data[0x2a]: 1=color / 2=black_white.
   */
  uint8_t data[0x30];
  ret = hp5590_cmd (dn,
                    proto_flags,
                    CMD_IN | CMD_VERIFY,
                    CMD_LCD_STATUS,
                    (unsigned char *) &data,
                    sizeof (data), CORE_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  *lcd_counter = data[0x29];
  if (data[0x2a] == 2) {
    *color_led = LED_BLACKWHITE;
  } else {
    *color_led = LED_COLOR;
  }
  DBG (DBG_cmds, "LCD and LED values: lcd=%d, led=%s\n", *lcd_counter,
       *color_led == LED_BLACKWHITE ? "black_white" : "color");

  return SANE_STATUS_GOOD;
}

/* SET SCAN PARAMETERS
==================== 50 =======================
BW 50 (425 x 585)
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

BW 50 reduced top left (261 x 469)
18 00 64 00 64 00 00 00 00 03 0c 05 7f 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 02 08

BW 50 reduced top right (302 x 498)
18 00 64 00 64 01 6e 00 00 03 8a 05 d6 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 02 5c

BW 50 X 0, small width (16 x 585)
18 00 64 00 64 00 00 00 00 00 30 06 db 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 00 20

BW 50 X large, small width (24 x 585)
18 00 64 00 64 04 b0 00 00 00 48 06 db 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 00 30

BW 50 Y 0, small height (425 x 21)
18 00 64 00 64 00 00 00 00 04 f8 00 3f 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

BW 50 Y large, small height (425 x 21)
18 00 64 00 64 00 00 06 99 04 f8 00 3f 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

GRAY 50
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

COLOR 50
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
00 18 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 09 f0

==================== 75 =======================
BW 75
18 00 64 00 64 00 00 00 00 04 f8 06 da 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

GRAY 75
18 00 64 00 64 00 00 00 00 04 f8 06 da 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

COLOR 75
18 00 64 00 64 00 00 00 00 04 f8 06 da 00 80 00
00 18 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 09 f0

COLOR 75 48 bit
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
04 00 00 13 e0

=================== 100 =======================
BW 100
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
40 01 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 00 6a

GRAY 100
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 50

COLOR 100
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
00 18 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 09 f0

COLOR 100 48bit, preview
18 00 64 00 64 00 00 00 00 04 f8 06 db 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
04 00 00 13 e0

COLOR 100 48bit
18 00 64 00 64 00 00 00 00 04 f2 06 db 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 13 c8

COLOR 100 48bit, TMA negatives
11 00 64 00 64 00 00 00 00 00 fc 03 84 00 80 00
00 30 e8 40 00 64 00 c8 01 90 00 00 00 00 00 00
00 00 00 03 f0

COLOR 100 48bit, TMA slides
12 00 64 00 64 00 00 00 00 00 fc 03 84 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 03 f0

=================== 150 =======================
BW 150
18 00 c8 00 c8 00 00 00 00 09 f6 0d b6 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 06 a4

GRAY 150
18 00 c8 00 c8 00 00 00 00 09 f6 0d b6 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 06 a4

COLOR 150
18 00 c8 00 c8 00 00 00 00 09 f6 0d b6 00 80 00
00 18 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 13 ec

COLOR 150 48 bit
18 00 c8 00 c8 00 00 00 00 09 ea 0d b6 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 27 a8

=================== 200 =======================
BW 200
18 00 c8 00 c8 00 00 00 00 09 f0 0d b6 00 80 00
40 01 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 00 d4

GRAY 200
18 00 c8 00 c8 00 00 00 00 09 f6 0d b6 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 06 a4

COLOR 200
18 00 c8 00 c8 00 00 00 00 09 f6 0d b6 00 80 00
00 18 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 13 ec

COLOR 200 48 bit
18 00 c8 00 c8 00 00 00 00 09 f6 0d aa 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 27 d8

=================== 300 =======================
BW 300
18 01 2c 01 2c 00 00 00 00 09 f0 0d b6 00 80 00
40 01 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 01 3e

GRAY 300
18 01 2c 01 2c 00 00 00 00 09 f4 0d b6 00 80 00
40 08 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 09 f4

COLOR 300
18 01 2c 01 2c 00 00 00 00 09 f4 0d b6 00 80 00
00 18 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 1d dc

COLOR 300 48bit, TMA negatives
11 01 2c 01 2c 00 00 00 06 01 fc 07 02 00 80 00
00 30 e8 40 00 64 00 c8 01 90 00 00 00 00 00 00
00 00 00 0b e8

COLOR 300 48bit, TMA slides
12 01 2c 01 2c 00 00 00 00 01 fc 07 08 00 80 00
00 30 e8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 0b e8

==================== 400 ======================
BW 400
18 02 58 02 58 00 00 00 00 13 ec 1b 6c 00 80 00
40 08 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 13 ec

GRAY 400
18 02 58 02 58 00 00 00 00 13 ec 1b 6c 00 80 00
40 08 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 13 ec

COLOR 400
18 02 58 02 58 00 00 00 00 13 ec 1b 6c 00 80 00
00 18 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 3b c4

==================== 600 ======================
BW 600
18 02 58 02 58 00 00 00 00 13 e8 1b 6c 00 80 00
40 01 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 02 7d

GRAY 600
18 02 58 02 58 00 00 00 00 13 ec 1b 6c 00 80 00
40 08 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 13 ec

COLOR 600
18 02 58 02 58 00 00 00 00 13 ec 1b 6c 00 80 00
00 18 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 3b c4

==================== 1200 =====================
BW 1200

18 04 b0 04 b0 00 00 00 00 27 a8 36 d8 00 80 00
40 01 c8 40 00 64 00 64 00 64 00 00 00 00 00 00
00 00 00 04 f5
*/

/* READ SCAN PARAMETERS
====================== 50 =====================
BW 50
c0 00 00 0f 23 a0 00 00 03 50 04 92 00 00 00 00

GRAY 50
c0 00 00 0f 23 a0 00 00 03 50 04 92 00 00 00 00

COLOR 50
c0 00 00 2d 6a e0 00 00 09 f0 04 92 00 00 00 00

====================== 75 =====================
BW 75
c0 00 00 0f 20 50 00 00 03 50 04 91 00 00 00 00

GRAY 75
c0 00 00 0f 20 50 00 00 03 50 04 91 00 00 00 00

COLOR 75
c0 00 00 2d 60 f0 00 00 09 f0 04 91 00 00 00 00

COLOR 75 48 bit
c0 00 00 5a 86 40 00 00 13 e0 04 8e 00 00 00 00

===================== 100 =====================
BW 100
c0 00 00 01 e4 74 00 00 00 6a 04 92 00 00 00 00

GRAY 100
c0 00 00 0f 23 a0 00 00 03 50 04 92 00 00 00 00

COLOR 100
c0 00 00 2d 6a e0 00 00 09 f0 04 92 00 00 00 00

COLOR 100, 48 bit preview
c0 00 00 5a d5 c0 00 00 13 e0 04 92 00 00 00 00

COLOR 100, 48 bit
c0 00 00 5a 68 10 00 00 13 c8 04 92 00 00 00 00

===================== 150 =====================
BW 150
c0 00 00 3c b3 10 00 00 06 a4 09 24 00 00 00 00

GRAY 150
c0 00 00 3c b3 10 00 00 06 a4 09 24 00 00 00 00

COLOR 150
c0 00 00 b6 19 30 00 00 13 ec 09 24 00 00 00 00

COLOR 150 48bit
c0 00 01 6a 7b a0 00 00 27 a8 09 24 00 00 00 00

===================== 200 =====================
BW 200
c0 00 00 07 91 d0 00 00 00 d4 09 24 00 00 00 00

GRAY 200
c0 00 00 3c b3 10 00 00 06 a4 09 24 00 00 00 00

COLOR 200
c0 00 00 b6 19 30 00 00 13 ec 09 24 00 00 00 00

COLOR 200 48 bit
c0 00 01 6a f3 a0 00 00 27 d8 09 1c 00 00 00 00

===================== 300 =====================
BW 300
c0 00 00 11 08 14 00 00 01 3e 0d b6 00 00 00 00

GRAY 300
c0 00 00 88 77 78 00 00 09 f4 0d b6 00 00 00 00

COLOR 300
c0 00 01 99 66 68 00 00 1d dc 0d b6 00 00 00 00

===================== 400 =====================
BW 400
c0 00 02 22 4b 90 00 00 13 ec 1b 6c 00 00 00 00

GRAY 400
c0 00 02 22 4b 90 00 00 13 ec 1b 6c 00 00 00 00

COLOR 400
c0 00 06 66 e2 b0 00 00 3b c4 1b 6c 00 00 00 00

===================== 600 =====================
BW 600
c0 00 00 44 3b bc 00 00 02 7d 1b 6c 00 00 00 00

GRAY 600
c0 00 02 22 4b 90 00 00 13 ec 1b 6c 00 00 00 00

COLOR 600
c0 00 06 66 e2 b0 00 00 3b c4 1b 6c 00 00 00 00
*/
/* vim: sw=2 ts=8
 */
