From bb31b941801e087dec15ff92655ee8f5284c6aab Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 24 Apr 2007 17:56:36 +0000 Subject: [PATCH] + Added files for 'hp5590' backend which supports HP ScanJet 5550/5590/7650 scanners --- backend/hp5590.c | 1328 +++++++++++++++++++++++++ backend/hp5590_cmds.c | 2159 +++++++++++++++++++++++++++++++++++++++++ backend/hp5590_cmds.h | 168 ++++ backend/hp5590_low.c | 948 ++++++++++++++++++ backend/hp5590_low.h | 77 ++ doc/sane-hp5590.man | 62 ++ 6 files changed, 4742 insertions(+) create mode 100644 backend/hp5590.c create mode 100644 backend/hp5590_cmds.c create mode 100644 backend/hp5590_cmds.h create mode 100644 backend/hp5590_low.c create mode 100644 backend/hp5590_low.h create mode 100644 doc/sane-hp5590.man diff --git a/backend/hp5590.c b/backend/hp5590.c new file mode 100644 index 000000000..76e67be98 --- /dev/null +++ b/backend/hp5590.c @@ -0,0 +1,1328 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 2007 Ilia Sotnikov + 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 is part of a SANE backend for HP 5550/5590/7650 Scanners +*/ + +#include "sane/config.h" + +#include +#include +#include + +#include "sane/sane.h" +#define BACKEND_NAME hp5590 +#include "sane/sanei_backend.h" +#include "sane/sanei_usb.h" +#include "sane/saneopts.h" +#include "hp5590_cmds.c" +#include "hp5590_low.c" + +/* Debug levels */ +#define DBG_err 0 +#define DBG_proc 10 +#define DBG_verbose 20 + +#define hp5590_assert(exp) if(!(exp)) { \ + DBG (DBG_err, "Assertion '%s' failed at %s:%u\n", #exp, __FILE__, __LINE__);\ + return SANE_STATUS_INVAL; \ +} + +#define hp5590_assert_void_return(exp) if(!(exp)) { \ + DBG (DBG_err, "Assertion '%s' failed at %s:%u\n", #exp, __FILE__, __LINE__);\ + return; \ +} + +/* #define HAS_WORKING_COLOR_48 */ +#define BUILD 2 +#define USB_TIMEOUT 30 * 1000 + +static SANE_Word +res_list[] = { 6, 100, 200, 300, 600, 1200, 2400 }; + +#define SANE_VALUE_SCAN_SOURCE_FLATBED SANE_I18N("Flatbed") +#define SANE_VALUE_SCAN_SOURCE_ADF SANE_I18N("ADF") +#define SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX SANE_I18N("ADF Duplex") +#define SANE_VALUE_SCAN_SOURCE_TMA_SLIDES SANE_I18N("TMA Slides") +#define SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES SANE_I18N("TMA Negatives") + +#define SANE_VALUE_SCAN_MODE_COLOR_24 SANE_I18N("Color") +#define SANE_VALUE_SCAN_MODE_COLOR_48 SANE_I18N("Color (48 bits)") + +#define SANE_NAME_LAMP_TIMEOUT SANE_I18N("extend-lamp-timeout") +#define SANE_TITLE_LAMP_TIMEOUT SANE_I18N("Extend lamp timeout") +#define SANE_DESC_LAMP_TIMEOUT SANE_I18N("Extends lamp timeout (from 15 minutes to 1 hour)") +#define SANE_NAME_WAIT_FOR_BUTTON SANE_I18N("wait-for-button") +#define SANE_TITLE_WAIT_FOR_BUTTON SANE_I18N("Wait for button") +#define SANE_DESC_WAIT_FOR_BUTTON SANE_I18N("Waits for button before scanning") + +#define MAX_SCAN_SOURCE_VALUE_LEN 24 +#define MAX_SCAN_MODE_VALUE_LEN 24 + +static SANE_Range +range_x, range_y, range_qual; + +static SANE_String_Const +mode_list[] = { + SANE_VALUE_SCAN_MODE_COLOR_24, +#ifdef HAS_WORKING_COLOR_48 + SANE_VALUE_SCAN_MODE_COLOR_48, +#endif /* HAS_WORKING_COLOR_48 */ + SANE_VALUE_SCAN_MODE_GRAY, + SANE_VALUE_SCAN_MODE_LINEART, + NULL +}; + +enum hp5590_opt_idx { + HP5590_OPT_NUM = 0, + HP5590_OPT_TL_X, + HP5590_OPT_TL_Y, + HP5590_OPT_BR_X, + HP5590_OPT_BR_Y, + HP5590_OPT_MODE, + HP5590_OPT_SOURCE, + HP5590_OPT_RESOLUTION, + HP5590_OPT_LAMP_TIMEOUT, + HP5590_OPT_WAIT_FOR_BUTTON, + HP5590_OPT_PREVIEW, + HP5590_OPT_LAST +}; + +struct hp5590_scanner { + struct scanner_info *info; + struct scanner_state *state; + SANE_Device sane; + SANE_Int dn; + float br_x, br_y, tl_x, tl_y; + unsigned int dpi; + enum color_depths depth; + enum scan_sources source; + SANE_Bool extend_lamp_timeout; + SANE_Bool wait_for_button; + SANE_Bool preview; + unsigned int quality; + SANE_Option_Descriptor *opts; + struct hp5590_scanner *next; + unsigned int image_size; + SANE_Int transferred_image_size; + void *bulk_read_state; + SANE_Bool scanning; +}; + +static +struct hp5590_scanner *scanners_list; + +/******************************************************************************/ +static SANE_Status +calc_image_params (struct hp5590_scanner *scanner, + unsigned int *pixel_bits, + unsigned int *pixels_per_line, + unsigned int *bytes_per_line, + unsigned int *lines, + unsigned int *image_size) +{ + unsigned int _pixel_bits; + SANE_Status ret; + unsigned int _pixels_per_line; + unsigned int _bytes_per_line; + unsigned int _lines; + unsigned int _image_size; + float var; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + if (!scanner) + return SANE_STATUS_INVAL; + + ret = hp5590_calc_pixel_bits (scanner->dpi, scanner->depth, &_pixel_bits); + if (ret != SANE_STATUS_GOOD) + return ret; + + var = (float) (1.0 * (scanner->br_x - scanner->tl_x) * scanner->dpi); + _pixels_per_line = var; + if (var > _pixels_per_line) + _pixels_per_line++; + + var = (float) (1.0 * (scanner->br_y - scanner->tl_y) * scanner->dpi); + _lines = var; + if (var > _lines) + _lines++; + + var = (float) (1.0 * _pixels_per_line / 8 * _pixel_bits); + _bytes_per_line = var; + if (var > _bytes_per_line) + _bytes_per_line++; + + _image_size = _lines * _bytes_per_line; + + DBG (DBG_verbose, "%s: pixel_bits: %u, pixels_per_line: %u, " + "bytes_per_line: %u, lines: %u, image_size: %u\n", + __FUNCTION__, + _pixel_bits, _pixels_per_line, _bytes_per_line, _lines, _image_size); + + if (pixel_bits) + *pixel_bits = _pixel_bits; + + if (pixels_per_line) + *pixels_per_line = _pixels_per_line; + + if (bytes_per_line) + *bytes_per_line = _bytes_per_line; + + if (lines) + *lines = _lines; + + if (image_size) + *image_size = _image_size; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +attach_usb_device (SANE_String_Const devname, + enum hp_scanner_types hp_scanner_type) +{ + struct scanner_info *info; + struct hp5590_scanner *scanner, *ptr; + unsigned int max_count, count; + SANE_Int dn; + SANE_Status ret; + + DBG (DBG_proc, "%s: Opening USB device\n", __FUNCTION__); + if (sanei_usb_open (devname, &dn) != SANE_STATUS_GOOD) + return SANE_STATUS_IO_ERROR; + DBG (DBG_proc, "%s: USB device opened\n", __FUNCTION__); + + if (hp5590_init_scanner (dn, &info, hp_scanner_type) != 0) + return SANE_STATUS_IO_ERROR; + + DBG (1, "%s: found HP%s scanner at '%s'\n", + __FUNCTION__, info->model, devname); + + DBG (DBG_verbose, "%s: Reading max scan count\n", __FUNCTION__); + if (hp5590_read_max_scan_count (dn, &max_count) != 0) + return SANE_STATUS_IO_ERROR; + DBG (DBG_verbose, "%s: Max Scanning count %u\n", __FUNCTION__, max_count); + + DBG (DBG_verbose, "%s: Reading scan count\n", __FUNCTION__); + if (hp5590_read_scan_count (dn, &count) != 0) + return SANE_STATUS_IO_ERROR; + DBG (DBG_verbose, "%s: Scanning count %u\n", __FUNCTION__, count); + + ret = hp5590_read_part_number (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_stop_scan (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + scanner = malloc (sizeof(struct hp5590_scanner)); + if (!scanner) + return SANE_STATUS_NO_MEM; + memset (scanner, 0, sizeof(struct hp5590_scanner)); + + scanner->sane.model = info->model; + scanner->sane.vendor = "HP"; + scanner->sane.type = info->kind; + scanner->sane.name = devname; + scanner->dn = dn; + scanner->info = info; + scanner->bulk_read_state = NULL; + + if (!scanners_list) + scanners_list = scanner; + else + { + for (ptr = scanners_list; ptr->next; ptr = ptr->next); + ptr->next = scanner; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +attach_hp5550 (SANE_String_Const devname) +{ + return attach_usb_device (devname, SCANNER_HP5550); +} + +/******************************************************************************/ +static SANE_Status +attach_hp5590 (SANE_String_Const devname) +{ + return attach_usb_device (devname, SCANNER_HP5590); +} + +/******************************************************************************/ +static SANE_Status +attach_hp7650 (SANE_String_Const devname) +{ + return attach_usb_device (devname, SCANNER_HP7650); +} + +/******************************************************************************/ +SANE_Status +sane_init (SANE_Int * version_code, SANE_Auth_Callback __sane_unused__ authorize) +{ + SANE_Status ret; + SANE_Word vendor_id, product_id; + + DBG_INIT(); + + DBG (1, "SANE backed for HP 5550/5590/7650 %u.%u.%u\n", V_MAJOR, V_MINOR, BUILD); + DBG (1, "(c) Ilia Sotnikov \n"); + + if (version_code) + *version_code = SANE_VERSION_CODE(V_MAJOR, V_MINOR, BUILD); + + sanei_usb_init(); + + sanei_usb_set_timeout (USB_TIMEOUT); + + scanners_list = NULL; + + ret = hp5590_vendor_product_id (SCANNER_HP5550, &vendor_id, &product_id); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp5550); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_vendor_product_id (SCANNER_HP5590, &vendor_id, &product_id); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp5590); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_vendor_product_id (SCANNER_HP7650, &vendor_id, &product_id); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp7650); + if (ret != SANE_STATUS_GOOD) + return ret; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +void sane_exit (void) +{ + struct hp5590_scanner *ptr; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + for (ptr = scanners_list; ptr; ptr = ptr->next) + { + hp5590_assert_void_return (ptr->opts != NULL); + free (ptr->opts); + free (ptr); + } +} + +/******************************************************************************/ +SANE_Status +sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) +{ + struct hp5590_scanner *ptr; + unsigned int found, i; + + DBG (DBG_proc, "%s, local only: %u\n", __FUNCTION__, local_only); + + if (!device_list) + return SANE_STATUS_INVAL; + + for (found = 0, ptr = scanners_list; ptr; found++, ptr = ptr->next); + DBG (1, "Found %u devices\n", found); + + found++; + *device_list = malloc (found * sizeof (SANE_Device)); + if (!*device_list) + return SANE_STATUS_NO_MEM; + memset (*device_list, 0, found * sizeof(SANE_Device)); + + for (i = 0, ptr = scanners_list; ptr; i++, ptr = ptr->next) + { + (*device_list)[i] = &(ptr->sane); + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +SANE_Status +sane_open (SANE_String_Const devicename, SANE_Handle * handle) +{ + struct hp5590_scanner *ptr; + SANE_Option_Descriptor *opts; + unsigned int available_sources; + SANE_String_Const *sources_list; + unsigned int source_idx; + + DBG (DBG_proc, "%s: device name: %s\n", __FUNCTION__, devicename); + + if (!handle) + return SANE_STATUS_INVAL; + + for (ptr = scanners_list; + ptr && strcmp (ptr->sane.name, devicename) != 0; + ptr = ptr->next); + + if (!ptr) + return SANE_STATUS_INVAL; + + ptr->tl_x = 0; + ptr->tl_y = 0; + ptr->br_x = ptr->info->max_size_x; + ptr->br_y = ptr->info->max_size_y; + ptr->dpi = res_list[1]; + ptr->depth = DEPTH_BW; + ptr->source = SOURCE_FLATBED; + ptr->extend_lamp_timeout = SANE_FALSE; + ptr->wait_for_button = SANE_FALSE; + ptr->preview = SANE_FALSE; + ptr->quality = 4; + ptr->image_size = 0; + ptr->scanning = SANE_FALSE; + + *handle = ptr; + + opts = malloc (sizeof (SANE_Option_Descriptor) * HP5590_OPT_LAST); + if (!opts) + return SANE_STATUS_NO_MEM; + + opts[HP5590_OPT_NUM].name = SANE_NAME_NUM_OPTIONS; + opts[HP5590_OPT_NUM].title = SANE_TITLE_NUM_OPTIONS; + opts[HP5590_OPT_NUM].desc = SANE_DESC_NUM_OPTIONS; + opts[HP5590_OPT_NUM].type = SANE_TYPE_INT; + opts[HP5590_OPT_NUM].unit = SANE_UNIT_NONE; + opts[HP5590_OPT_NUM].size = sizeof(SANE_Word); + opts[HP5590_OPT_NUM].cap = SANE_CAP_INACTIVE | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_NUM].constraint_type = SANE_CONSTRAINT_NONE; + opts[HP5590_OPT_NUM].constraint.string_list = NULL; + + range_x.min = SANE_FIX(0); + range_x.max = SANE_FIX(ptr->info->max_size_x * 25.4); + range_x.quant = SANE_FIX(0.1); + range_y.min = SANE_FIX(0); + range_y.max = SANE_FIX(ptr->info->max_size_y * 25.4); + range_y.quant = SANE_FIX(0.1); + + range_qual.min = SANE_FIX(4); + range_qual.max = SANE_FIX(16); + range_qual.quant = SANE_FIX(1); + + opts[HP5590_OPT_TL_X].name = SANE_NAME_SCAN_TL_X; + opts[HP5590_OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; + opts[HP5590_OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; + opts[HP5590_OPT_TL_X].type = SANE_TYPE_FIXED; + opts[HP5590_OPT_TL_X].unit = SANE_UNIT_MM; + opts[HP5590_OPT_TL_X].size = sizeof(SANE_Fixed); + opts[HP5590_OPT_TL_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; + opts[HP5590_OPT_TL_X].constraint.range = &range_x; + + opts[HP5590_OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; + opts[HP5590_OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; + opts[HP5590_OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; + opts[HP5590_OPT_TL_Y].type = SANE_TYPE_FIXED; + opts[HP5590_OPT_TL_Y].unit = SANE_UNIT_MM; + opts[HP5590_OPT_TL_Y].size = sizeof(SANE_Fixed); + opts[HP5590_OPT_TL_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; + opts[HP5590_OPT_TL_Y].constraint.range = &range_y; + + opts[HP5590_OPT_BR_X].name = SANE_NAME_SCAN_BR_X; + opts[HP5590_OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; + opts[HP5590_OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; + opts[HP5590_OPT_BR_X].type = SANE_TYPE_FIXED; + opts[HP5590_OPT_BR_X].unit = SANE_UNIT_MM; + opts[HP5590_OPT_BR_X].size = sizeof(SANE_Fixed); + opts[HP5590_OPT_BR_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; + opts[HP5590_OPT_BR_X].constraint.range = &range_x; + + opts[HP5590_OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; + opts[HP5590_OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; + opts[HP5590_OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; + opts[HP5590_OPT_BR_Y].type = SANE_TYPE_FIXED; + opts[HP5590_OPT_BR_Y].unit = SANE_UNIT_MM; + opts[HP5590_OPT_BR_Y].size = sizeof(SANE_Fixed); + opts[HP5590_OPT_BR_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; + opts[HP5590_OPT_BR_Y].constraint.range = &range_y; + + opts[HP5590_OPT_MODE].name = SANE_NAME_SCAN_MODE; + opts[HP5590_OPT_MODE].title = SANE_TITLE_SCAN_MODE; + opts[HP5590_OPT_MODE].desc = SANE_DESC_SCAN_MODE; + opts[HP5590_OPT_MODE].type = SANE_TYPE_STRING; + opts[HP5590_OPT_MODE].unit = SANE_UNIT_NONE; + opts[HP5590_OPT_MODE].size = MAX_SCAN_MODE_VALUE_LEN; + opts[HP5590_OPT_MODE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + opts[HP5590_OPT_MODE].constraint.string_list = mode_list; + + available_sources = 1; /* Flatbed is always available */ + if (ptr->info->features & FEATURE_ADF) + available_sources += 2; + if (ptr->info->features & FEATURE_TMA) + available_sources += 2; + available_sources++; /* Count terminating NULL */ + sources_list = malloc (available_sources * sizeof (SANE_String_Const)); + if (!sources_list) + return SANE_STATUS_NO_MEM; + source_idx = 0; + sources_list[source_idx++] = SANE_VALUE_SCAN_SOURCE_FLATBED; + if (ptr->info->features & FEATURE_ADF) + { + sources_list[source_idx++] = SANE_VALUE_SCAN_SOURCE_ADF; + sources_list[source_idx++] = SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX; + } + if (ptr->info->features & FEATURE_TMA) + { + sources_list[source_idx++] = SANE_VALUE_SCAN_SOURCE_TMA_SLIDES; + sources_list[source_idx++] = SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES; + } + sources_list[source_idx] = NULL; + + opts[HP5590_OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE; + opts[HP5590_OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE; + opts[HP5590_OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE; + opts[HP5590_OPT_SOURCE].type = SANE_TYPE_STRING; + opts[HP5590_OPT_SOURCE].unit = SANE_UNIT_NONE; + opts[HP5590_OPT_SOURCE].size = MAX_SCAN_SOURCE_VALUE_LEN; + opts[HP5590_OPT_SOURCE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + opts[HP5590_OPT_SOURCE].constraint.string_list = sources_list; + + opts[HP5590_OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION; + opts[HP5590_OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION; + opts[HP5590_OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION; + opts[HP5590_OPT_RESOLUTION].type = SANE_TYPE_INT; + opts[HP5590_OPT_RESOLUTION].unit = SANE_UNIT_DPI; + opts[HP5590_OPT_RESOLUTION].size = sizeof(SANE_Int); + opts[HP5590_OPT_RESOLUTION].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; + opts[HP5590_OPT_RESOLUTION].constraint.word_list = res_list; + + opts[HP5590_OPT_LAMP_TIMEOUT].name = SANE_NAME_LAMP_TIMEOUT; + opts[HP5590_OPT_LAMP_TIMEOUT].title = SANE_TITLE_LAMP_TIMEOUT; + opts[HP5590_OPT_LAMP_TIMEOUT].desc = SANE_DESC_LAMP_TIMEOUT; + opts[HP5590_OPT_LAMP_TIMEOUT].type = SANE_TYPE_BOOL; + opts[HP5590_OPT_LAMP_TIMEOUT].unit = SANE_UNIT_NONE; + opts[HP5590_OPT_LAMP_TIMEOUT].size = sizeof(SANE_Bool); + opts[HP5590_OPT_LAMP_TIMEOUT].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + opts[HP5590_OPT_LAMP_TIMEOUT].constraint_type = SANE_CONSTRAINT_NONE; + opts[HP5590_OPT_LAMP_TIMEOUT].constraint.string_list = NULL; + + opts[HP5590_OPT_WAIT_FOR_BUTTON].name = SANE_NAME_WAIT_FOR_BUTTON; + opts[HP5590_OPT_WAIT_FOR_BUTTON].title = SANE_TITLE_WAIT_FOR_BUTTON; + opts[HP5590_OPT_WAIT_FOR_BUTTON].desc = SANE_DESC_WAIT_FOR_BUTTON; + opts[HP5590_OPT_WAIT_FOR_BUTTON].type = SANE_TYPE_BOOL; + opts[HP5590_OPT_WAIT_FOR_BUTTON].unit = SANE_UNIT_NONE; + opts[HP5590_OPT_WAIT_FOR_BUTTON].size = sizeof(SANE_Bool); + opts[HP5590_OPT_WAIT_FOR_BUTTON].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_WAIT_FOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE; + opts[HP5590_OPT_WAIT_FOR_BUTTON].constraint.string_list = NULL; + + opts[HP5590_OPT_PREVIEW].name = SANE_NAME_PREVIEW; + opts[HP5590_OPT_PREVIEW].title = SANE_TITLE_PREVIEW; + opts[HP5590_OPT_PREVIEW].desc = SANE_DESC_PREVIEW; + opts[HP5590_OPT_PREVIEW].type = SANE_TYPE_BOOL; + opts[HP5590_OPT_PREVIEW].unit = SANE_UNIT_NONE; + opts[HP5590_OPT_PREVIEW].size = sizeof(SANE_Bool); + opts[HP5590_OPT_PREVIEW].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + opts[HP5590_OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE; + opts[HP5590_OPT_PREVIEW].constraint.string_list = NULL; + + ptr->opts = opts; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +void +sane_close (SANE_Handle handle) +{ + struct hp5590_scanner *scanner = handle; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + sanei_usb_close (scanner->dn); + scanner->dn = -1; +} + +/******************************************************************************/ +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) +{ + struct hp5590_scanner *scanner = handle; + + DBG (DBG_proc, "%s, option: %u\n", __FUNCTION__, option); + + return &scanner->opts[option]; +} + +/******************************************************************************/ +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int option, + SANE_Action action, void *value, + SANE_Int * info) +{ + struct hp5590_scanner *scanner = handle; + + if (!value) + return SANE_STATUS_INVAL; + + if (!handle) + return SANE_STATUS_INVAL; + + if (action == SANE_ACTION_GET_VALUE) + { + if (option == HP5590_OPT_NUM) + { + DBG(3, "%s: get total number of options - %u\n", __FUNCTION__, HP5590_OPT_LAST); + *((SANE_Int *) value) = HP5590_OPT_LAST; + return SANE_STATUS_GOOD; + } + + DBG (DBG_proc, "%s: get option '%s' value\n", __FUNCTION__, scanner->opts[option].name); + + if (option == HP5590_OPT_BR_X) + { + *(SANE_Fixed *) value = SANE_FIX (scanner->br_x * 25.4); + } + + if (option == HP5590_OPT_BR_Y) + { + *(SANE_Fixed *) value = SANE_FIX (scanner->br_y * 25.4); + } + + if (option == HP5590_OPT_TL_X) + { + *(SANE_Fixed *) value = SANE_FIX ((scanner->tl_x) * 25.4); + } + + if (option == HP5590_OPT_TL_Y) + { + *(SANE_Fixed *) value = SANE_FIX (scanner->tl_y * 25.4); + } + + if (option == HP5590_OPT_MODE) + { + switch (scanner->depth) { + case DEPTH_BW: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_MODE_LINEART, strlen (SANE_VALUE_SCAN_MODE_LINEART)); + break; + case DEPTH_GRAY: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_MODE_GRAY, strlen (SANE_VALUE_SCAN_MODE_GRAY)); + break; + case DEPTH_COLOR_24: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_MODE_COLOR_24, strlen (SANE_VALUE_SCAN_MODE_COLOR_24)); + break; + case DEPTH_COLOR_48: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_MODE_COLOR_48, strlen (SANE_VALUE_SCAN_MODE_COLOR_48)); + break; + default: + return SANE_STATUS_INVAL; + } + } + + if (option == HP5590_OPT_SOURCE) + { + switch (scanner->source) { + case SOURCE_FLATBED: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_SOURCE_FLATBED, strlen (SANE_VALUE_SCAN_SOURCE_FLATBED)); + break; + case SOURCE_ADF: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_SOURCE_ADF, strlen (SANE_VALUE_SCAN_SOURCE_ADF)); + break; + case SOURCE_ADF_DUPLEX: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX, strlen (SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX)); + break; + case SOURCE_TMA_SLIDES: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_SOURCE_TMA_SLIDES, strlen (SANE_VALUE_SCAN_SOURCE_TMA_SLIDES)); + break; + case SOURCE_TMA_NEGATIVES: + memset (value , 0, scanner->opts[option].size); + memcpy (value, SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES, strlen (SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES)); + break; + case SOURCE_NONE: + default: + return SANE_STATUS_INVAL; + } + } + + if (option == HP5590_OPT_RESOLUTION) + { + *(SANE_Int *) value = scanner->dpi; + } + + if (option == HP5590_OPT_LAMP_TIMEOUT) + { + *(SANE_Bool *) value = scanner->extend_lamp_timeout; + } + + if (option == HP5590_OPT_WAIT_FOR_BUTTON) + { + *(SANE_Bool *) value = scanner->wait_for_button; + } + + if (option == HP5590_OPT_PREVIEW) + { + *(SANE_Bool *) value = scanner->preview; + } + } + + if (action == SANE_ACTION_SET_VALUE) + { + if (option == HP5590_OPT_NUM) + return SANE_STATUS_INVAL; + + if (option == HP5590_OPT_BR_X) + { + float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4; + if (val <= scanner->tl_x) + return SANE_STATUS_GOOD; + scanner->br_x = val; + if (info) + *info = SANE_INFO_RELOAD_PARAMS; + } + + if (option == HP5590_OPT_BR_Y) + { + float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4; + if (val <= scanner->tl_y) + return SANE_STATUS_GOOD; + scanner->br_y = val; + if (info) + *info = SANE_INFO_RELOAD_PARAMS; + } + + if (option == HP5590_OPT_TL_X) + { + float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4; + if (val >= scanner->br_x) + return SANE_STATUS_GOOD; + scanner->tl_x = val; + if (info) + *info = SANE_INFO_RELOAD_PARAMS; + } + + if (option == HP5590_OPT_TL_Y) + { + float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4; + if (val >= scanner->br_y) + return SANE_STATUS_GOOD; + scanner->tl_y = val; + if (info) + *info = SANE_INFO_RELOAD_PARAMS; + } + + if (option == HP5590_OPT_MODE) + { + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_LINEART) == 0) + { + scanner->depth = DEPTH_BW; + } + + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_GRAY) == 0) + { + scanner->depth = DEPTH_GRAY; + } + + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_COLOR_24) == 0) + { + scanner->depth = DEPTH_COLOR_24; + } + + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_COLOR_48) == 0) + { + scanner->depth = DEPTH_COLOR_48; + } + if (info) + *info = SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + } + + if (option == HP5590_OPT_SOURCE) + { + range_y.max = SANE_FIX(scanner->info->max_size_y * 25.4); + + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_FLATBED) == 0) + { + scanner->source = SOURCE_FLATBED; + range_x.max = SANE_FIX(scanner->info->max_size_x * 25.4); + range_y.max = SANE_FIX(scanner->info->max_size_y * 25.4); + scanner->br_x = scanner->info->max_size_x; + scanner->br_y = scanner->info->max_size_y; + } + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_ADF) == 0) + { + scanner->source = SOURCE_ADF; + range_x.max = SANE_FIX(scanner->info->max_size_x * 25.4); + range_y.max = SANE_FIX(scanner->info->max_size_y * 25.4); + scanner->br_x = scanner->info->max_size_x; + scanner->br_y = scanner->info->max_size_y; + } + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX) == 0) + { + scanner->source = SOURCE_ADF_DUPLEX; + range_x.max = SANE_FIX(scanner->info->max_size_x * 25.4); + range_y.max = SANE_FIX(scanner->info->max_size_y * 25.4 * 2); + scanner->br_y = scanner->info->max_size_y * 2; + scanner->br_x = scanner->info->max_size_x; + } + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_TMA_SLIDES) == 0) + { + scanner->source = SOURCE_TMA_SLIDES; + range_x.max = SANE_FIX(TMA_MAX_X_INCHES * 25.4); + range_y.max = SANE_FIX(TMA_MAX_Y_INCHES * 25.4); + scanner->br_x = TMA_MAX_X_INCHES * 25.4; + scanner->br_y = TMA_MAX_Y_INCHES * 25.4; + } + if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES) == 0) + { + scanner->source = SOURCE_TMA_NEGATIVES; + range_x.max = SANE_FIX(TMA_MAX_X_INCHES * 25.4); + range_y.max = SANE_FIX(TMA_MAX_Y_INCHES * 25.4); + scanner->br_x = TMA_MAX_X_INCHES * 25.4; + scanner->br_y = TMA_MAX_Y_INCHES * 25.4; + } + if (info) + *info = SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + } + + if (option == HP5590_OPT_RESOLUTION) + { + scanner->dpi = *(SANE_Int *) value; + if (info) + *info = SANE_INFO_RELOAD_PARAMS; + } + + if (option == HP5590_OPT_LAMP_TIMEOUT) + { + scanner->extend_lamp_timeout = *(SANE_Bool *) value; + } + + if (option == HP5590_OPT_WAIT_FOR_BUTTON) + { + scanner->wait_for_button = *(SANE_Bool *) value; + } + + if (option == HP5590_OPT_PREVIEW) + { + scanner->preview = *(SANE_Bool *) value; + } + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +SANE_Status sane_get_parameters (SANE_Handle handle, + SANE_Parameters * params) +{ + struct hp5590_scanner *scanner = handle; + SANE_Status ret; + unsigned int pixel_bits; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + if (!params) + return SANE_STATUS_INVAL; + + if (!handle) + return SANE_STATUS_INVAL; + + ret = calc_image_params (scanner, + (unsigned int *) &pixel_bits, + (unsigned int *) ¶ms->pixels_per_line, + (unsigned int *) ¶ms->bytes_per_line, + (unsigned int *) ¶ms->lines, NULL); + if (ret != SANE_STATUS_GOOD) + return ret; + + switch (scanner->depth) { + case DEPTH_BW: + params->depth = pixel_bits; + params->format = SANE_FRAME_GRAY; + params->last_frame = SANE_TRUE; + break; + case DEPTH_GRAY: + params->depth = pixel_bits; + params->format = SANE_FRAME_GRAY; + params->last_frame = SANE_TRUE; + break; + case DEPTH_COLOR_24: + params->depth = pixel_bits / 3; + params->last_frame = SANE_TRUE; + params->format = SANE_FRAME_RGB; + break; + case DEPTH_COLOR_48: + params->depth = pixel_bits / 3; + params->last_frame = SANE_TRUE; + params->format = SANE_FRAME_RGB; + break; + default: + DBG(0, "%s: Unknown depth\n", __FUNCTION__); + return SANE_STATUS_INVAL; + } + + + DBG (DBG_proc, "format: %u, last_frame: %u, bytes_per_line: %u, " + "pixels_per_line: %u, lines: %u, depth: %u\n", + params->format, params->last_frame, + params->bytes_per_line, params->pixels_per_line, + params->lines, params->depth); + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +SANE_Status +sane_start (SANE_Handle handle) +{ + struct hp5590_scanner *scanner = handle; + SANE_Status ret; + unsigned int bytes_per_line; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + if (!scanner) + return SANE_STATUS_INVAL; + + if ( scanner->scanning == SANE_TRUE + && ( scanner->source == SOURCE_ADF + || scanner->source == SOURCE_ADF_DUPLEX)) + { + DBG (DBG_verbose, "%s: Scanner is scanning, check if more data is available\n", + __FUNCTION__); + ret = hp5590_is_data_available (scanner->dn); + if (ret == SANE_STATUS_GOOD) + { + DBG (DBG_verbose, "%s: More data is available\n", __FUNCTION__); + scanner->transferred_image_size = scanner->image_size; + return SANE_STATUS_GOOD; + } + + if (ret != SANE_STATUS_NO_DOCS) + return ret; + } + + sane_cancel (handle); + + if (scanner->wait_for_button) + { + enum button_status status; + for (;;) + { + ret = hp5590_read_buttons (scanner->dn, &status); + if (ret != SANE_STATUS_GOOD) + return ret; + + if (status == BUTTON_CANCEL) + return SANE_STATUS_CANCELLED; + + if (status != BUTTON_NONE && status != BUTTON_POWER) + break; + sleep (1); + } + } + + DBG (DBG_verbose, "Init scanner\n"); + ret = hp5590_init_scanner (scanner->dn, NULL, SCANNER_NONE); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_power_status (scanner->dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + DBG (DBG_verbose, "Wakeup\n"); + ret = hp5590_select_source_and_wakeup (scanner->dn, scanner->source, + scanner->extend_lamp_timeout); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_set_scan_params (scanner->dn, scanner->info, + scanner->tl_x * scanner->dpi, + scanner->tl_y * scanner->dpi, + (scanner->br_x - scanner->tl_x) * scanner->dpi, + (scanner->br_y - scanner->tl_y) * scanner->dpi, + scanner->dpi, + scanner->depth, scanner->preview ? MODE_PREVIEW : MODE_NORMAL, + scanner->source); + if (ret != SANE_STATUS_GOOD) + { + hp5590_reset_scan_head (scanner->dn); + return ret; + } + + ret = calc_image_params (scanner, NULL, NULL, + &bytes_per_line, NULL, + &scanner->image_size); + if (ret != SANE_STATUS_GOOD) + { + hp5590_reset_scan_head (scanner->dn); + return ret; + } + + scanner->transferred_image_size = scanner->image_size; + + if ( scanner->depth == DEPTH_COLOR_24 + || scanner->depth == DEPTH_COLOR_48) + { + DBG (1, "Color 24/48 bits: checking if image size is correctly " + "aligned on number of colors\n"); + if (bytes_per_line % 3) + { + DBG (DBG_err, "Color 24/48 bits: image size doesn't lined up on number of colors (3) " + "(image size: %u, bytes per line %u)\n", + scanner->image_size, bytes_per_line); + hp5590_reset_scan_head (scanner->dn); + return SANE_STATUS_INVAL; + } + DBG (1, "Color 24/48 bits: image size is correctly aligned on number of colors " + "(image size: %u, bytes per line %u)\n", + scanner->image_size, bytes_per_line); + + DBG (1, "Color 24/48 bits: checking if image size is correctly " + "aligned on bytes per line\n"); + if (scanner->image_size % bytes_per_line) + { + DBG (DBG_err, "Color 24/48 bits: image size doesn't lined up on bytes per line " + "(image size: %u, bytes per line %u)\n", + scanner->image_size, bytes_per_line); + hp5590_reset_scan_head (scanner->dn); + return SANE_STATUS_INVAL; + } + DBG (1, "Color 24/48 bits: image size correctly aligned on bytes per line " + "(images size: %u, bytes per line: %u)\n", + scanner->image_size, bytes_per_line); + } + + DBG (DBG_verbose, "Final image size: %u\n", scanner->image_size); + + DBG (DBG_verbose, "Reverse calibration maps\n"); + ret = hp5590_send_reverse_calibration_map (scanner->dn); + if (ret != SANE_STATUS_GOOD) + { + hp5590_reset_scan_head (scanner->dn); + return ret; + } + + DBG (DBG_verbose, "Forward calibration maps\n"); + ret = hp5590_send_forward_calibration_maps (scanner->dn); + if (ret != SANE_STATUS_GOOD) + { + hp5590_reset_scan_head (scanner->dn); + return ret; + } + + scanner->scanning = SANE_TRUE; + + DBG (DBG_verbose, "Starting scan\n"); + ret = hp5590_start_scan (scanner->dn); + /* Check for paper jam */ + if ( ret == SANE_STATUS_DEVICE_BUSY + && ( scanner->source == SOURCE_ADF + || scanner->source == SOURCE_ADF_DUPLEX)) + return SANE_STATUS_JAMMED; + + if (ret != SANE_STATUS_GOOD) + { + hp5590_reset_scan_head (scanner->dn); + return ret; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +convert_lineart (struct hp5590_scanner *scanner, SANE_Byte *data, SANE_Int size) +{ + SANE_Int i; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_assert (scanner != NULL); + hp5590_assert (data != NULL); + + /* Invert lineart */ + if (scanner->depth == DEPTH_BW) + { + for (i = 0; i < size; i++) + data[i] ^= 0xff; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +convert_to_rgb (struct hp5590_scanner *scanner, SANE_Byte *data, SANE_Int size) +{ + unsigned int pixels_per_line; + unsigned int bytes_per_color; + unsigned int bytes_per_line; + unsigned int lines; + unsigned int i, j; + unsigned char *buf; + unsigned char *ptr; + SANE_Status ret; + + hp5590_assert (scanner != NULL); + hp5590_assert (data != NULL); + + if ( scanner->depth == DEPTH_BW + || scanner->depth == DEPTH_GRAY) + return SANE_STATUS_GOOD; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + +#ifndef HAS_WORKING_COLOR_48 + if (scanner->depth == DEPTH_COLOR_48) + return SANE_STATUS_UNSUPPORTED; +#endif + + ret = calc_image_params (scanner, + NULL, + &pixels_per_line, &bytes_per_line, + NULL, NULL); + if (ret != SANE_STATUS_GOOD) + return ret; + + lines = size / bytes_per_line; + bytes_per_color = bytes_per_line / 3; + + DBG (DBG_verbose, "Length : %u\n", size); + + DBG (DBG_verbose, "Converting row RGB to normal RGB\n"); + + DBG (DBG_verbose, "Bytes per line %u\n", bytes_per_line); + DBG (DBG_verbose, "Bytes per color %u\n", bytes_per_color); + DBG (DBG_verbose, "Lines %u\n", lines); + + buf = malloc (bytes_per_line); + if (!buf) + return SANE_STATUS_NO_MEM; + + ptr = data; + for (j = 0; j < lines; ptr += bytes_per_line, j++) + { + memset (buf, 0, bytes_per_line); + for (i = 0; i < pixels_per_line; i++) + { + if (scanner->depth == DEPTH_COLOR_24) + { + /* R */ + buf[i*3] = ptr[i]; + /* G */ + buf[i*3+1] = ptr[i+bytes_per_color]; + /* B */ + buf[i*3+2] = ptr[i+bytes_per_color*2]; + } + else + { + /* R */ + buf[i*6] = ptr[2*i+1]; + buf[i*6+1] = ptr[2*i]; + /* G */ + buf[i*6+2] = ptr[2*i+bytes_per_color+1]; + buf[i*6+3] = ptr[2*i+bytes_per_color]; + /* B */ + buf[i*6+4] = ptr[2*i+bytes_per_color*2+1]; + buf[i*6+5] = ptr[2*i+bytes_per_color*2]; + } + } + memcpy (ptr, buf, bytes_per_line); + } + free (buf); + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte * data, + SANE_Int max_length, SANE_Int * length) +{ + struct hp5590_scanner *scanner = handle; + SANE_Status ret; + + DBG (DBG_proc, "%s, length %u, left %u\n", + __FUNCTION__, + max_length, + scanner->transferred_image_size); + + if (!length) + { + scanner->scanning = SANE_FALSE; + return SANE_STATUS_INVAL; + } + + if (scanner->transferred_image_size == 0) + { + *length = 0; + DBG (DBG_verbose, "Setting scan count\n"); + + ret = hp5590_inc_scan_count (scanner->dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* Dont free bulk read state, some bytes could be left + * for the next images from ADF + */ + return SANE_STATUS_EOF; + } + + if (!scanner->bulk_read_state) + { + ret = hp5590_low_init_bulk_read_state (&scanner->bulk_read_state); + if (ret != SANE_STATUS_GOOD) + { + scanner->scanning = SANE_FALSE; + return ret; + } + } + + *length = max_length; + if (*length > scanner->transferred_image_size) + *length = scanner->transferred_image_size; + + if ( scanner->depth == DEPTH_COLOR_24 + || scanner->depth == DEPTH_COLOR_48) + { + unsigned int bytes_per_line; + ret = calc_image_params (scanner, + NULL, NULL, + &bytes_per_line, + NULL, NULL); + if (ret != SANE_STATUS_GOOD) + return ret; + + *length -= *length % bytes_per_line; + DBG (2, "Aligning requested size to bytes per line " + "(requested: %u, aligned: %u)\n", + max_length, *length); + } + + ret = hp5590_read (scanner->dn, data, *length, scanner->bulk_read_state); + if (ret != SANE_STATUS_GOOD) + { + scanner->scanning = SANE_FALSE; + return ret; + } + + scanner->transferred_image_size -= *length; + + ret = convert_to_rgb (scanner, data, *length); + if (ret != SANE_STATUS_GOOD) + { + scanner->scanning = SANE_FALSE; + return ret; + } + + ret = convert_lineart (scanner, data, *length); + if (ret != SANE_STATUS_GOOD) + return ret; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +void +sane_cancel (SANE_Handle handle) +{ + struct hp5590_scanner *scanner = handle; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + scanner->scanning = SANE_FALSE; + + if (scanner->dn < 0) + return; + + hp5590_low_free_bulk_read_state (&scanner->bulk_read_state); + + ret = hp5590_stop_scan (scanner->dn); + if (ret != SANE_STATUS_GOOD) + return; +} + +/******************************************************************************/ + +SANE_Status +sane_set_io_mode (SANE_Handle __sane_unused__ handle, + SANE_Bool __sane_unused__ non_blocking) +{ + DBG (DBG_proc, "%s\n", __FUNCTION__); + + return SANE_STATUS_UNSUPPORTED; +} + +/******************************************************************************/ +SANE_Status +sane_get_select_fd (SANE_Handle __sane_unused__ handle, + SANE_Int __sane_unused__ * fd) +{ + DBG (DBG_proc, "%s\n", __FUNCTION__); + + return SANE_STATUS_UNSUPPORTED; +} + +/* vim: sw=2 ts=8 + */ diff --git a/backend/hp5590_cmds.c b/backend/hp5590_cmds.c new file mode 100644 index 000000000..f76be1e4e --- /dev/null +++ b/backend/hp5590_cmds.c @@ -0,0 +1,2159 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 2007 Ilia Sotnikov + 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 is part of a SANE backend for HP 5550/5590/7650 Scanners +*/ + +#include "sane/config.h" + +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#include + +#include "sane/sanei_debug.h" +#include "hp5590_low.h" +#include "hp5590_cmds.h" + +struct hp5590_model +{ + enum hp_scanner_types scanner_type; + unsigned int usb_vendor_id; + unsigned int usb_product_id; + const char *vendor_id; + const char *model; + const char *kind; +}; + +static const struct hp5590_model +hp5590_models[] = { + { + SCANNER_HP5550, + 0x03f0, 0x1205, "SILITEKIPenguin", + "5550", "Workgroup scanner" + }, + { + SCANNER_HP5590, + 0x03f0, 0x1705, "SILITEKIPenguin", + "5590", "Workgroup scanner" + }, + { + SCANNER_HP7650, + 0x03f0, 0x1805, "SILITEKIArnold", + "7650", "Document scanner" + } +}; + +/* Debug levels */ +#define DBG_err 0 +#define DBG_proc 10 +#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(u_int16_t) +#define FORWARD_MAP_LEN 128 * 1024 / sizeof(u_int16_t) + +/* Button flags */ +/* From left to rigth */ +/* 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 + +struct init_resp +{ + u_int8_t flags; /* bit 0 - TMA, bit 1 - ADF, bit 3 - LCD present */ + u_int8_t id[15]; /* SILITEKPenguin */ + u_int8_t pad1[9]; /* 00 00 00 00 00 00 00 00 00 */ + u_int8_t version[5]; /* 0.0.67 */ + u_int16_t max_dpi_x; /* 09 60 = 2400 */ + u_int16_t max_dpi_y; /* 09 60 = 2400 */ + u_int16_t max_pixels_x; /* 4F B0 = 20400 (20400 / 2400 = 8.5") */ + u_int16_t max_pixels_y; /* 6D E0 = 28128 (28128 / 2400 = 11.72") */ + u_int8_t pad2[8]; /* 00 00 00 00 00 00 00 00 */ + u_int16_t motor_param_normal; /* 00 64 = 100 */ + u_int16_t motor_param_max; /* 03 E8 = 1000 */ +} __attribute__ ((packed)); + +struct power_resp +{ + u_int8_t flags; + u_int16_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 +{ + u_int8_t source; /* + * TMA Negatives : 11 = 17 + * TMA Slides : 12 = 18 + * ADF : 14 = 20 + * Flatbed : 18 = 24 + * ADF Duplex : 54 = 84 + */ + + u_int16_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 + */ + + u_int16_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 + */ + + u_int16_t top_x; /* + * pixels * (Base DPI / current DPI) + * 00 00, 01 6e = 366 (x = 425 - 302 = 123) + * 04 b0 = 1200 (x = 425 - 24 = 401) + */ + + u_int16_t top_y; /* + * pixels * (Base DPI / current DPI) + * 00 00, 06 99 = 1689 (y = 585 - 21 = 564) + */ + + u_int16_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 + */ + + u_int16_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 + */ + + u_int16_t unk1; /* 00 80 */ + + u_int16_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 + */ + + u_int8_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 + */ + + u_int16_t flags; /* + * 50/75/100/150/200/300 : e8 40 = 59456 + * 400/600/1200 : c8 40 = 51264 + */ + + u_int16_t motor_param1; /* + * 00 64 = 100 + */ + u_int16_t motor_param2; /* + * 00 64 = 100 - ADF, Flatbed, TMA slides + * 00 c8 = 200 - TMA Negatives + */ + u_int16_t motor_param3; /* + * 00 64 = 100 - ADF, Flatbed, TMA slides + * 01 90 = 400 - TMA negatives + */ + u_int32_t pad1; /* 00 00 00 00 */ + u_int16_t pad2; /* 00 00 */ + u_int8_t mode; /* 00 - normal scan, 04 - preview scan */ + u_int16_t pad3; /* 00 00 */ + + u_int16_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 +{ + u_int8_t signature; /* c0 */ + u_int8_t pad1; /* 00 */ + u_int32_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 + */ + u_int16_t pad2; /* 00 00 */ + u_int16_t line_width; + u_int16_t real_size_y; + u_int32_t pad3; /* 00 00 00 00 */ +} __attribute__ ((packed)); + +struct lamp_state +{ + u_int8_t unk1; /* 02 */ + u_int8_t flag; /* 01 on start, 02 - TMA, 03 - all other */ + u_int16_t turnoff_time; /* 0a 0a, 03 36, 0f 36 */ +} __attribute__ ((packed)); + +struct color_map +{ + u_int8_t color1[6]; /* 00 00 00 00 01 00 */ + u_int8_t color2[6]; /* 00 00 00 00 01 00 */ + u_int8_t color3[6]; /* 00 00 00 00 01 00 */ +} __attribute__ ((packed)); + +struct reg_03 +{ + u_int8_t unk1; /* 0x0b - ADF ready, 0x03 - not */ + u_int8_t unk2; /* 0x80 */ + u_int8_t adf_flags; /* 0x01 - ADF ready when selected, 0x02 - not */ +} __attribute__ ((packed)); + +/******************************************************************************/ +static SANE_Status +hp5590_vendor_product_id (enum hp_scanner_types scanner_type, + SANE_Word * vendor_id, + SANE_Word * product_id) +{ + unsigned int i; + + hp5590_cmds_assert (vendor_id != NULL); + hp5590_cmds_assert (product_id != NULL); + + for (i = 0; i < sizeof (hp5590_models) / sizeof (struct hp5590_model); i++) + { + if (hp5590_models[i].scanner_type == scanner_type) + { + *vendor_id = hp5590_models[i].usb_vendor_id; + *product_id = hp5590_models[i].usb_product_id; + return SANE_STATUS_GOOD; + } + } + + return SANE_STATUS_INVAL; +} + +/******************************************************************************/ +static SANE_Status +hp5590_init_scanner (SANE_Int dn, + 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", __FUNCTION__); + + hp5590_cmds_assert (sizeof (init_resp) == 50); + + /* Init scanner */ + ret = hp5590_cmd (dn, 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", + __FUNCTION__, + 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); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: scanner reports non-zero status: %s\n", + __FUNCTION__, sane_strstatus (ret)); + return ret; + } + DBG (DBG_cmds, "%s: scanner status OK\n", __FUNCTION__); + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_read_eeprom (SANE_Int dn, + unsigned int addr, + unsigned char *data, unsigned int size) +{ + u_int8_t eeprom_addr = addr; + SANE_Status ret; + + hp5590_cmds_assert (data != NULL); + hp5590_cmds_assert (sizeof (eeprom_addr) == 1); + + DBG (DBG_proc, "%s\n", __FUNCTION__); + DBG (DBG_proc, "Reading EEPROM: addr %04x, size %u\n", addr, size); + + ret = hp5590_cmd (dn, 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, + 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, + unsigned int addr, + unsigned char *data, unsigned int size) +{ + u_int8_t eeprom_addr = addr; + SANE_Status ret; + + hp5590_cmds_assert (data != NULL); + hp5590_cmds_assert (sizeof (eeprom_addr) == 1); + + DBG (DBG_proc, "%s\n", __FUNCTION__); + DBG (DBG_proc, "Writing EEPROM: addr %04x, size: %u\n", addr, size); + + ret = hp5590_cmd (dn, 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, + 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, unsigned int *count) +{ + u_int32_t scan_count; + SANE_Status ret; + + hp5590_cmds_assert (count != NULL); + hp5590_cmds_assert (sizeof (scan_count) == 4); + + DBG (DBG_proc, "%s\n", __FUNCTION__); + DBG (DBG_proc, "Reading scan count\n"); + + ret = hp5590_read_eeprom (dn, 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) +{ + u_int32_t scan_count; + unsigned int count; + unsigned int new_count; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + hp5590_cmds_assert (sizeof (scan_count) == 4); + + ret = hp5590_read_scan_count (dn, &count); + if (ret != SANE_STATUS_GOOD) + return ret; + + scan_count = ++count; + + ret = hp5590_write_eeprom (dn, 0x00, + (unsigned char *) &scan_count, + sizeof (scan_count)); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* Verify its setting */ + ret = hp5590_read_scan_count (dn, &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, unsigned int *max_count) +{ + u_int8_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", __FUNCTION__); + DBG (DBG_proc, "Reading max scan count\n"); + + ret = hp5590_read_eeprom (dn, + 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_Status +hp5590_read_eeprom_all_cmd (SANE_Int dn) +{ + u_int8_t eeprom[255]; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + ret = hp5590_read_eeprom (dn, 0x00, + (unsigned char *) eeprom, + sizeof (eeprom)); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* FIXME: Debug output */ + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_read_part_number (SANE_Int dn) +{ + unsigned int part_number_len = PART_NUMBER_LEN; + unsigned char part_number[PART_NUMBER_LEN + 1]; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + memset (part_number, 0, sizeof (part_number)); + ret = hp5590_read_eeprom (dn, + 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) +{ + u_int8_t data_status; + SANE_Status ret; + SANE_Bool data_available; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (data_status) == 1); + data_available = SANE_FALSE; + + ret = hp5590_cmd (dn, 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", __FUNCTION__, data_status); + + if (data_status == 0x40) + data_available = SANE_TRUE; + + DBG (DBG_cmds, "%s: Data is %s\n", + __FUNCTION__, + 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) +{ + u_int8_t reg_011b = 0x40; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (reg_011b) == 1); + + ret = hp5590_cmd (dn, CMD_VERIFY, + CMD_STOP_SCAN, + (unsigned char *) ®_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 hp5590_lamp_state state) +{ + struct lamp_state lamp_state; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + 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", __FUNCTION__); + } + + 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", __FUNCTION__); + } + + 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", __FUNCTION__); + } + + 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", __FUNCTION__); + } + + ret = hp5590_cmd (dn, 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, NULL, SCANNER_NONE); + if (ret != SANE_STATUS_GOOD) + return ret; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_power_status (SANE_Int dn) +{ + struct power_resp power_resp; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (power_resp) == 3); + + ret = hp5590_cmd (dn, 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, LAMP_STATE_TURNON); + if (ret != SANE_STATUS_GOOD) + return ret; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_read_error_code (SANE_Int dn, unsigned int *adf_flags) +{ + struct reg_03 reg_03; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (reg_03) == 3); + hp5590_cmds_assert (adf_flags != NULL); + + memset (®_03, 0, sizeof (reg_03)); + *adf_flags = 0; + ret = hp5590_cmd (dn, CMD_IN, + CMD_MISC_STATUS, + (unsigned char *) ®_03, + sizeof (reg_03), CORE_NONE); + + if (ret != SANE_STATUS_GOOD) + return ret; + + DBG (DBG_cmds, "%s: adf_flags: %04x\n", __FUNCTION__, reg_03.adf_flags); + DBG (DBG_cmds, "%s: unk1 : %04x\n", __FUNCTION__, reg_03.unk1); + DBG (DBG_cmds, "%s: unk2 : %04x\n", __FUNCTION__, reg_03.unk2); + + *adf_flags = reg_03.adf_flags; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_reset_scan_head (SANE_Int dn) +{ + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + ret = hp5590_turnon_lamp (dn, LAMP_STATE_TURNOFF); + if (ret != SANE_STATUS_GOOD) + return ret; + + usleep (100 * 1000); + + ret = hp5590_turnon_lamp (dn, 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 scan_sources source, + SANE_Bool extend_lamp_timeout) +{ + u_int8_t reg_d6 = 0x04; + SANE_Status ret; + unsigned int adf_flags; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (reg_d6) == 1); + + if (source == SOURCE_TMA_SLIDES || source == SOURCE_TMA_NEGATIVES) + { + ret = hp5590_turnon_lamp (dn, LAMP_STATE_TURNOFF); + if (ret != SANE_STATUS_GOOD) + return ret; + } + else + { + ret = hp5590_turnon_lamp (dn, + 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, CMD_VERIFY, + CMD_SELECT_SOURCE, + (unsigned char *) ®_d6, + sizeof (reg_d6), CORE_NONE); + + if (ret != SANE_STATUS_GOOD && ret != SANE_STATUS_DEVICE_BUSY) + return ret; + + ret = hp5590_read_error_code (dn, &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) +{ + u_int8_t reg_00 = 0x01; + SANE_Status ret; + unsigned int adf_flags; + unsigned int waiting; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + hp5590_cmds_assert (sizeof (reg_00) == 1); + + for (waiting = 0; waiting < WAKEUP_TIMEOUT; waiting++, sleep (1)) + { + ret = hp5590_cmd (dn, CMD_VERIFY, + CMD_LOCK_UNLOCK, + (unsigned char *) ®_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, &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, + struct scanner_info *scanner_info, + unsigned int base_dpi) +{ + u_int16_t _base_dpi; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + 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, 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, unsigned int base_dpi) +{ + struct color_map color_map; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + 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 + { + 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; + } + + ret = hp5590_cmd (dn, 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", __FUNCTION__); + + 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", __FUNCTION__); + + 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", __FUNCTION__); + + 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, 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", __FUNCTION__); + + 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); + } + + /* 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) exceedes 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) exceedes 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 smal, 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, 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) +{ + struct image_params image_params; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (image_params) == 16); + + memset (&image_params, 0, sizeof (image_params)); + + ret = hp5590_cmd (dn, 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, 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", __FUNCTION__); + + hp5590_cmds_assert (scanner_info != NULL); + hp5590_cmds_assert (dpi != 0); + + /* Lock scanner */ + ret = hp5590_lock_unlock_scanner (dn); + 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); + return ret; + } + + DBG (DBG_cmds, "Set base DPI: %u\n", base_dpi); + ret = hp5590_set_base_dpi (dn, scanner_info, base_dpi); + if (ret != SANE_STATUS_GOOD) + { + /* Unlock scanner */ + hp5590_lock_unlock_scanner (dn); + return ret; + } + + /* Set color map */ + ret = hp5590_set_color_map (dn, base_dpi); + if (ret != SANE_STATUS_GOOD) + { + /* Unlock scanner */ + hp5590_lock_unlock_scanner (dn); + return ret; + } + + ret = hp5590_set_scan_area (dn, 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); + return ret; + } + + ret = hp5590_read_image_params (dn); + if (ret != SANE_STATUS_GOOD) + { + /* Unlock scanner */ + hp5590_lock_unlock_scanner (dn); + return ret; + } + + /* Unlock scanner */ + ret = hp5590_lock_unlock_scanner (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_send_reverse_calibration_map (SANE_Int dn) +{ + unsigned int reverse_map_size = REVERSE_MAP_LEN; + u_int16_t reverse_map[REVERSE_MAP_LEN]; + unsigned int i; + u_int16_t val; + unsigned int len; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + 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++) + { + reverse_map[i] = htons (0xffff); + } + + DBG (DBG_proc, "Done preparing reverse calibration map\n"); + + ret = hp5590_bulk_write (dn, 0x2b, + (unsigned char *) reverse_map, + reverse_map_size * sizeof (u_int16_t)); + if (ret != SANE_STATUS_GOOD) + return ret; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_send_forward_calibration_maps (SANE_Int dn) +{ + unsigned int forward_map_size = FORWARD_MAP_LEN; + u_int16_t forward_map[FORWARD_MAP_LEN]; + SANE_Status ret; + unsigned int i; + u_int16_t val; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + 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"); + + ret = hp5590_bulk_write (dn, 0x012a, + (unsigned char *) forward_map, + forward_map_size * sizeof (u_int16_t)); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_bulk_write (dn, 0x022a, + (unsigned char *) forward_map, + forward_map_size * sizeof (u_int16_t)); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = hp5590_bulk_write (dn, 0x032a, + (unsigned char *) forward_map, + forward_map_size * sizeof (u_int16_t)); + if (ret != SANE_STATUS_GOOD) + return ret; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_read (SANE_Int dn, unsigned char *bytes, unsigned int size, + void *state) +{ + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (bytes != NULL); + hp5590_cmds_assert (state != NULL); + + ret = hp5590_bulk_read (dn, bytes, size, state); + if (ret != SANE_STATUS_GOOD) + return ret; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************/ +static SANE_Status +hp5590_start_scan (SANE_Int dn) +{ + u_int8_t reg_051b = 0x40; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (sizeof (reg_051b) == 1); + + ret = hp5590_cmd (dn, CMD_VERIFY, + CMD_START_SCAN, + (unsigned char *) ®_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 button_status * status) +{ + u_int16_t button_status; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + hp5590_cmds_assert (status != NULL); + hp5590_cmds_assert (sizeof (button_status) == 2); + + ret = hp5590_cmd (dn, 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; +} + +/* 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 + */ diff --git a/backend/hp5590_cmds.h b/backend/hp5590_cmds.h new file mode 100644 index 000000000..5fcb68b91 --- /dev/null +++ b/backend/hp5590_cmds.h @@ -0,0 +1,168 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 2007 Ilia Sotnikov + 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 is part of a SANE backend for HP 5550/5590/7650 Scanners +*/ + +#ifndef HP5590_H +#define HP5590_H + +#include "hp5590_low.h" + +#define TMA_MAX_X_INCHES 1.69 +#define TMA_MAX_Y_INCHES 6 + +enum hp_scanner_types +{ + SCANNER_NONE = 0, + SCANNER_HP5550, + SCANNER_HP5590, + SCANNER_HP7650 +}; + +enum scan_sources +{ + SOURCE_NONE = 1, + SOURCE_FLATBED, + SOURCE_ADF, + SOURCE_ADF_DUPLEX, + SOURCE_TMA_NEGATIVES, + SOURCE_TMA_SLIDES +}; + +enum scan_modes +{ + MODE_NORMAL = 1, + MODE_PREVIEW +}; + +enum color_depths +{ + DEPTH_BW = 1, + DEPTH_GRAY, + DEPTH_COLOR_24, + DEPTH_COLOR_48 +}; + +enum button_status +{ + BUTTON_NONE = 1, + BUTTON_POWER, + BUTTON_SCAN, + BUTTON_COLLECT, + BUTTON_FILE, + BUTTON_EMAIL, + BUTTON_COPY, + BUTTON_UP, + BUTTON_DOWN, + BUTTON_MODE, + BUTTON_CANCEL +}; + +enum hp5590_lamp_state +{ + LAMP_STATE_TURNOFF = 1, + LAMP_STATE_TURNON, + LAMP_STATE_SET_TURNOFF_TIME, + LAMP_STATE_SET_TURNOFF_TIME_LONG +}; + +#define FEATURE_NONE 0 +#define FEATURE_ADF 1 << 0 +#define FEATURE_TMA 1 << 1 +#define FEATURE_LCD 1 << 2 + +struct scanner_info +{ + const char *model; + const char *kind; + unsigned int features; + const char *fw_version; + unsigned int max_dpi_x; + unsigned int max_dpi_y; + unsigned int max_pixels_x; + unsigned int max_pixels_y; + float max_size_x; + float max_size_y; + unsigned int max_motor_param; + unsigned int normal_motor_param; +}; + +static SANE_Status hp5590_vendor_product_id (enum hp_scanner_types scanner_type, + SANE_Word * vendor_id, + SANE_Word * product_id); +static SANE_Status hp5590_init_scanner (SANE_Int dn, + struct scanner_info **info, + enum hp_scanner_types scanner_type); +static SANE_Status hp5590_power_status (SANE_Int dn); +static SANE_Status hp5590_read_max_scan_count (SANE_Int dn, + unsigned int *max_count); +static SANE_Status hp5590_select_source_and_wakeup (SANE_Int dn, + enum scan_sources source, + SANE_Bool extend_lamp_timeout); +static SANE_Status hp5590_stop_scan (SANE_Int dn); +static SANE_Status hp5590_read_scan_count (SANE_Int dn, + unsigned int *count); +static SANE_Status hp5590_set_scan_params (SANE_Int dn, + 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); +static SANE_Status hp5590_send_forward_calibration_maps (SANE_Int dn); +static SANE_Status hp5590_send_reverse_calibration_map (SANE_Int dn); +static SANE_Status hp5590_inc_scan_count (SANE_Int dn); +static SANE_Status hp5590_start_scan (SANE_Int dn); +static SANE_Status hp5590_read (SANE_Int dn, + unsigned char *bytes, + unsigned int size, void *state); +static SANE_Status hp5590_read_buttons (SANE_Int dn, + enum button_status *status); +static SANE_Status hp5590_read_part_number (SANE_Int dn); +static SANE_Status hp5590_calc_pixel_bits (unsigned int dpi, + enum color_depths color_depth, + unsigned int *pixel_bits); +static SANE_Status hp5590_is_data_available (SANE_Int dn); +static SANE_Status hp5590_reset_scan_head (SANE_Int dn); +#endif /* HP5590_H */ +/* vim: sw=2 ts=8 + */ diff --git a/backend/hp5590_low.c b/backend/hp5590_low.c new file mode 100644 index 000000000..69ef6c81b --- /dev/null +++ b/backend/hp5590_low.c @@ -0,0 +1,948 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 2007 Ilia Sotnikov + 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 is part of a SANE backend for HP 5550/5590/7650 Scanners +*/ + +#include "sane/config.h" + +#include +#include +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ + +#include "sane/sanei_debug.h" +#include "sane/sanei_usb.h" +#include "hp5590_low.h" + +/* For USB_* constants */ +#ifdef HAVE_USB_H +# include +#endif /* HAVE_USB_H */ + +/* Debug levels */ +#define DBG_err 0 +#define DBG_proc 10 +#define DBG_usb 50 + +/* Custom assert() macro */ +#define hp5590_low_assert(exp) if(!(exp)) { \ + DBG (DBG_err, "Assertion '%s' failed at %s:%u\n", #exp, __FILE__, __LINE__);\ + return SANE_STATUS_INVAL; \ +} + +/* Structure describing bulk transfer size */ +struct bulk_size +{ + u_int16_t size; + u_int8_t unused; +} __attribute__ ((packed)); + +/* Structure describing bulk URB */ +/* FIXME: Verify according to USB standard */ +struct usb_bulk_setup +{ + u_int8_t bRequestType; + u_int8_t bRequest; + u_int8_t bEndpoint; + u_int16_t unknown; + u_int16_t wLength; + u_int8_t pad; +} __attribute__ ((packed)); + +/* CORE status flag - ready or not */ +#define CORE_FLAG_NOT_READY 1 << 1 + +/* Bulk transfers are done in pages, below their respective sizes */ +#define BULK_WRITE_PAGE_SIZE 0x0f000 +#define BULK_READ_PAGE_SIZE 0x10000 +#define ALLOCATE_BULK_READ_PAGES 16 /* 16 * 65536 = 1Mb */ + +/* Structure describing bulk read state, because bulk reads will be done in + * pages, but function caller uses its own buffer, whose size is certainly + * different. Also, each bulk read page is ACK'ed by special command + * so total pages received should be tracked as well + */ +struct bulk_read_state +{ + unsigned char *buffer; + unsigned int buffer_size; + unsigned int bytes_available; + unsigned char *buffer_out_ptr; + unsigned char *buffer_in_ptr; + unsigned int total_pages; + unsigned char *buffer_end_ptr; + unsigned int initialized; +}; + +/******************************************************************************* + * USB-in-USB: get acknowledge for last USB-in-USB operation + * + * Parameters + * dn - sanei_usb device descriptor + * + * Returns + * SANE_STATUS_GOOD - if correct acknowledge was received + * SANE_STATUS_DEVICE_BUSY - otherwise + */ +static SANE_Status +hp5590_get_ack (SANE_Int dn) +{ + u_int8_t status; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + /* Check if USB-in-USB operation was accepted */ + ret = sanei_usb_control_msg (dn, USB_DIR_IN | USB_TYPE_VENDOR, + 0x0c, 0x8e, 0x20, + sizeof (status), &status); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error getting acknowledge\n", + __FUNCTION__); + return ret; + } + + DBG (DBG_usb, "%s: USB-in-USB: accepted\n", __FUNCTION__); + + /* Check if we received correct acknowledgement */ + if (status != 0x01) + { + DBG (DBG_err, "%s: USB-in-USB: not accepted (status %u)\n", + __FUNCTION__, status); + return SANE_STATUS_DEVICE_BUSY; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************* + * USB-in-USB: get device status + * + * Parameters + * dn - sanei_usb device descriptor + * + * Returns + * SANE_STATUS_GOOD - if correct status was received + * SANE_STATUS_DEVICE_BUSY - otherwise + */ +static SANE_Status +hp5590_get_status (SANE_Int dn) +{ + u_int8_t status; + SANE_Status ret; + + DBG (DBG_proc, "%s\n", __FUNCTION__); + + ret = sanei_usb_control_msg (dn, USB_DIR_IN | USB_TYPE_VENDOR, + 0x0c, 0x8e, 0x20, + sizeof (status), &status); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error getting device status\n", + __FUNCTION__); + return ret; + } + + /* Check if we received correct status */ + if (status != 0x00) + { + DBG (DBG_err, "%s: USB-in-USB: got non-zero device status (status %u)\n", + __FUNCTION__, status); + return SANE_STATUS_DEVICE_BUSY; + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************* + * USB-in-USB: sends control message for IN or OUT operation + * + * Parameters + * dn - sanei_usb device descriptor + * requesttype, request, value, index - their meaninings are similar to + * sanei_control_msg() + * bytes - pointer to data buffer + * size - size of data + * core_flags - + * CORE_NONE - no CORE operation will be performed + * CORE_DATA - operation on CORE data will be performed + * CORE_BULK_IN - preparation for bulk IN transfer (not used yet) + * CORE_BULK_OUT - preparation for bulk OUT trasfer + * + * Returns + * SANE_STATUS_GOOD - control message was sent w/o any errors + * all other SANE_Status values - otherwise + */ +static SANE_Status +hp5590_control_msg (SANE_Int dn, + int requesttype, int request, + int value, int index, unsigned char *bytes, + int size, int core_flags) +{ + struct usb_ctrl_setup ctrl; + SANE_Status ret; + unsigned int len; + unsigned char *ptr; + u_int8_t ack; + u_int8_t response; + unsigned int needed_response; + + DBG (DBG_proc, "%s: USB-in-USB: core data: %s\n", + __FUNCTION__, core_flags & CORE_DATA ? "yes" : "no"); + + hp5590_low_assert (bytes != NULL); + + /* IN (read) operation will be performed */ + if (requesttype & USB_DIR_IN) + { + /* Prepare USB-in-USB control message */ + memset (&ctrl, 0, sizeof (ctrl)); + ctrl.bRequestType = 0xc0; + ctrl.bRequest = request; + ctrl.wValue = htons (value); + ctrl.wIndex = htons (index); + ctrl.wLength = size; + + DBG (DBG_usb, "%s: USB-in-USB: sending control msg\n", __FUNCTION__); + /* Send USB-in-USB control message */ + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x04, 0x8f, 0x00, + sizeof (ctrl), (unsigned char *) &ctrl); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error sending control message\n", + __FUNCTION__); + return ret; + } + + DBG (DBG_usb, "%s: USB-in-USB: checking acknowledge for control message\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + len = size; + ptr = bytes; + /* Data is read in 8 byte portions */ + while (len) + { + unsigned int next_packet_size; + next_packet_size = 8; + if (len < 8) + next_packet_size = len; + + /* Read USB-in-USB data */ + ret = sanei_usb_control_msg (dn, USB_DIR_IN | USB_TYPE_VENDOR, + core_flags & CORE_DATA ? 0x0c : 0x04, + 0x90, 0x00, next_packet_size, ptr); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error reading data\n", __FUNCTION__); + return ret; + } + + ptr += next_packet_size; + len -= next_packet_size; + } + + /* Confirm data reception */ + ack = 0; + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x0c, 0x8f, 0x00, + sizeof (ack), &ack); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error confirming data reception\n", + __FUNCTION__); + return -1; + } + + DBG (DBG_usb, "%s: USB-in-USB: checking if confirmation was acknowledged\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + } + + /* OUT (write) operation will be performed */ + if (!(requesttype & USB_DIR_IN)) + { + /* Prepare USB-in-USB control message */ + memset (&ctrl, 0, sizeof (ctrl)); + ctrl.bRequestType = 0x40; + ctrl.bRequest = request; + ctrl.wValue = htons (value); + ctrl.wIndex = htons (index); + ctrl.wLength = size; + + DBG (DBG_usb, "%s: USB-in-USB: sending control msg\n", __FUNCTION__); + /* Send USB-in-USB control message */ + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x04, 0x8f, 0x00, + sizeof (ctrl), (unsigned char *) &ctrl); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error sending control message\n", + __FUNCTION__); + return ret; + } + + DBG (DBG_usb, "%s: USB-in-USB: checking acknowledge for control message\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + len = size; + ptr = bytes; + /* Data is sent in 8 byte portions */ + while (len) + { + unsigned int next_packet_size; + next_packet_size = 8; + if (len < 8) + next_packet_size = len; + + /* Send USB-in-USB data */ + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + core_flags & CORE_DATA ? 0x04 : 0x0c, + 0x8f, 0x00, next_packet_size, ptr); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error sending data\n", __FUNCTION__); + return ret; + } + + /* CORE data is acknowledged packet by packet */ + if (core_flags & CORE_DATA) + { + DBG (DBG_usb, "%s: USB-in-USB: checking if data was accepted\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + } + + ptr += next_packet_size; + len -= next_packet_size; + } + + /* Normal (non-CORE) data is acknowledged after its full transmission */ + if (!(core_flags & CORE_DATA)) + { + DBG (3, "%s: USB-in-USB: checking if data was accepted\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + } + + /* Getting response after data transmission */ + DBG (DBG_usb, "%s: USB-in-USB: getting response\n", __FUNCTION__); + ret = sanei_usb_control_msg (dn, USB_DIR_IN | USB_TYPE_VENDOR, + 0x0c, 0x90, 0x00, + sizeof (response), &response); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error getting response\n", __FUNCTION__); + return ret; + } + + /* Necessary response after normal (non-CORE) data is 0x00, + * after bulk OUT preparation - 0x24 + */ + needed_response = core_flags & CORE_BULK_OUT ? 0x24 : 0x00; + if (response == needed_response) + DBG (DBG_usb, "%s: USB-in-USB: got correct response\n", + __FUNCTION__); + + if (response != needed_response) + { + DBG (DBG_err, + "%s: USB-in-USB: invalid response received " + "(needed %04x, got %04x)\n", + __FUNCTION__, needed_response, response); + return SANE_STATUS_IO_ERROR; + } + + /* Send bulk OUT flags is bulk OUT preparation is performed */ + if (core_flags & CORE_BULK_OUT) + { + u_int8_t bulk_flags = 0x24; + DBG (DBG_usb, "%s: USB-in-USB: sending bulk flags\n", + __FUNCTION__); + + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x0c, 0x83, 0x00, + sizeof (bulk_flags), &bulk_flags); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error sending bulk flags\n", + __FUNCTION__); + return ret; + } + + /* Check acknowledge for bulk flags transmission */ + DBG (DBG_usb, "%s: USB-in-USB: checking confirmation for bulk flags\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + } + } + + return SANE_STATUS_GOOD; +} + +/******************************************************************************* + * USB-in-USB: verifies last command + * + * Parameters + * dn - sanei_usb device descriptor + * cmd - command to verify + * + * Returns + * SANE_STATUS_GOOD - command verified successfully and CORE is ready + * SANE_STATUS_IO_ERROR - command verification failed + * SANE_STATUS_DEVICE_BUSY - command verified successfully but CORE isn't ready + * all other SANE_Status values - otherwise + */ +static SANE_Status +hp5590_verify_last_cmd (SANE_Int dn, unsigned int cmd) +{ + u_int16_t verify_cmd; + unsigned int last_cmd; + unsigned int core_status; + SANE_Status ret; + + DBG (3, "%s: USB-in-USB: command verification requested\n", + __FUNCTION__); + + /* Read last command along with CORE status */ + ret = hp5590_control_msg (dn, USB_DIR_IN, + 0x04, 0xc5, 0x00, + (unsigned char *) &verify_cmd, + sizeof (verify_cmd), CORE_NONE); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* Last command - minor byte */ + last_cmd = verify_cmd & 0xff; + /* CORE status - major byte */ + core_status = (verify_cmd & 0xff00) >> 8; + + /* Verify last command */ + DBG (DBG_usb, "%s: USB-in-USB: command verification %04x, " + "last command: %04x, core status: %04x\n", + __FUNCTION__, verify_cmd, last_cmd, core_status); + if ((cmd & 0x00ff) != last_cmd) + { + DBG (DBG_err, "%s: USB-in-USB: command verification failed: " + "expected 0x%04x, got 0x%04x\n", + __FUNCTION__, cmd, last_cmd); + return SANE_STATUS_IO_ERROR; + } + + DBG (DBG_usb, "%s: USB-in-USB: command verified successfully\n", + __FUNCTION__); + + /* Return value depends on CORE status */ + return core_status & CORE_FLAG_NOT_READY ? + SANE_STATUS_DEVICE_BUSY : SANE_STATUS_GOOD; +} + +/******************************************************************************* + * USB-in-USB: send command (convenience wrapper around hp5590_control_msg()) + * + * Parameters + * dn - sanei_usb device descriptor + * requesttype, request, value, index - their meaninings are similar to + * sanei_control_msg() + * bytes - pointer to data buffer + * size - size of data + * core_flags - + * CORE_NONE - no CORE operation will be performed + * CORE_DATA - operation on CORE data will be performed + * CORE_BULK_IN - preparation for bulk IN transfer (not used yet) + * CORE_BULK_OUT - preparation for bulk OUT trasfer + * + * Returns + * SANE_STATUS_GOOD - command was sent (and possible verified) w/o any errors + * all other SANE_Status values - otherwise + */ +static SANE_Status +hp5590_cmd (SANE_Int dn, unsigned int flags, + unsigned int cmd, unsigned char *data, unsigned int size, + unsigned int core_flags) +{ + SANE_Status ret; + + DBG (3, "%s: USB-in-USB: command : %04x\n", __FUNCTION__, cmd); + + ret = hp5590_control_msg (dn, + flags & CMD_IN ? USB_DIR_IN : USB_DIR_OUT, + 0x04, cmd, 0x00, data, size, core_flags); + if (ret != SANE_STATUS_GOOD) + return ret; + + ret = SANE_STATUS_GOOD; + /* Verify last command if requested */ + if (flags & CMD_VERIFY) + { + ret = hp5590_verify_last_cmd (dn, cmd); + } + + return ret; +} + +/******************************************************************************* + * USB-in-USB: initialized bulk read state + * + * Parameters + * state - pointer to a pointer for initialized state + * + * Returns + * SANE_STATUS_GOOD - if state was initialized successfully + * SANE_STATUS_NO_MEM - memory allocation failed + */ +static SANE_Status +hp5590_low_init_bulk_read_state (void **state) +{ + struct bulk_read_state *bulk_read_state; + + DBG (3, "%s: USB-in-USB: initializing bulk read state\n", __FUNCTION__); + + hp5590_low_assert (state != NULL); + + bulk_read_state = malloc (sizeof (struct bulk_read_state)); + if (!bulk_read_state) + return SANE_STATUS_NO_MEM; + memset (bulk_read_state, 0, sizeof (struct bulk_read_state)); + + bulk_read_state->buffer = malloc (ALLOCATE_BULK_READ_PAGES + * BULK_READ_PAGE_SIZE); + if (!bulk_read_state->buffer) + { + DBG (DBG_err, "%s: Memory allocation failed for %u bytes\n", + __FUNCTION__, ALLOCATE_BULK_READ_PAGES * BULK_READ_PAGE_SIZE); + return SANE_STATUS_NO_MEM; + } + bulk_read_state->buffer_size = ALLOCATE_BULK_READ_PAGES + * BULK_READ_PAGE_SIZE; + bulk_read_state->bytes_available = 0; + bulk_read_state->buffer_out_ptr = bulk_read_state->buffer; + bulk_read_state->buffer_in_ptr = bulk_read_state->buffer; + bulk_read_state->total_pages = 0; + bulk_read_state->buffer_end_ptr = bulk_read_state->buffer + + bulk_read_state->buffer_size; + bulk_read_state->initialized = 1; + + *state = bulk_read_state; + return SANE_STATUS_GOOD; +} + +/******************************************************************************* + * USB-in-USB: free bulk read state + * + * Parameters + * state - pointer to a pointer to bulk read state + * + * Returns + * SANE_STATUS_GOOD - bulk read state freed successfully + */ +static SANE_Status +hp5590_low_free_bulk_read_state (void **state) +{ + struct bulk_read_state *bulk_read_state; + + DBG (3, "%s\n", __FUNCTION__); + + hp5590_low_assert (state != NULL); + /* Just return if NULL bulk read state was given */ + if (*state == NULL) + return SANE_STATUS_GOOD; + + bulk_read_state = *state; + + DBG (3, "%s: USB-in-USB: freeing bulk read state\n", __FUNCTION__); + + free (bulk_read_state->buffer); + bulk_read_state->buffer = NULL; + free (bulk_read_state); + *state = NULL; + + return SANE_STATUS_GOOD; +} + +/* FIXME: perhaps needs to be converted to use hp5590_control_msg() */ +/******************************************************************************* + * USB-in-USB: bulk read + * + * Parameters + * dn - sanei_usb device descriptor + * bytes - pointer to data buffer + * size - size of data to read + * state - pointer to initialized bulk read state structure + */ +static SANE_Status +hp5590_bulk_read (SANE_Int dn, unsigned char *bytes, unsigned int size, + void *state) +{ + struct usb_bulk_setup ctrl; + SANE_Status ret; + unsigned int next_pages; + u_int8_t bulk_flags; + unsigned int next_portion; + struct bulk_read_state *bulk_read_state; + unsigned int bytes_until_buffer_end; + + DBG (3, "%s\n", __FUNCTION__); + + hp5590_low_assert (state != NULL); + hp5590_low_assert (bytes != NULL); + + bulk_read_state = state; + if (bulk_read_state->initialized == 0) + { + DBG (DBG_err, "%s: USB-in-USB: bulk read state not initialized\n", + __FUNCTION__); + return SANE_STATUS_INVAL; + } + + memset (bytes, 0, size); + + /* Check if requested data would fit into the buffer */ + if (size > bulk_read_state->buffer_size) + { + DBG (DBG_err, "Data requested won't fit in the bulk read buffer " + "(requested: %u, buffer size: %u\n", size, + bulk_read_state->buffer_size); + return SANE_STATUS_NO_MEM; + } + + /* Read data until requested size of data will be received */ + while (bulk_read_state->bytes_available < size) + { + DBG (DBG_usb, "%s: USB-in-USB: not enough data in buffer available " + "(available: %u, requested: %u)\n", + __FUNCTION__, bulk_read_state->bytes_available, size); + + /* IMPORTANT! 'next_pages' means 'request and receive next_pages pages in + * one bulk transfer request '. Windows driver uses 4 pages between each + * request. The more pages are received between requests the less the + * scanner does scan head re-positioning thus improving scanning speed. + * On the other hand, scanner expects that all of the requested pages + * will be received immediately after the request. In case when a + * frontend will have a delay between reads we will get bulk transfer + * timeout sooner or later. + * Having next_pages = 1 is the most safe case. + */ + next_pages = 1; + /* Count all received pages to calculate when we will need to send + * another bulk request + */ + bulk_read_state->total_pages++; + DBG (DBG_usb, "%s: USB-in-USB: total pages done: %u\n", + __FUNCTION__, bulk_read_state->total_pages); + + /* Send another bulk request for 'next_pages' before first + * page or next necessary one + */ + if ( bulk_read_state->total_pages == 1 + || bulk_read_state->total_pages % next_pages == 0) + { + /* Send bulk flags */ + DBG (DBG_usb, "%s: USB-in-USB: sending USB-in-USB bulk flags\n", + __FUNCTION__); + bulk_flags = 0x24; + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x0c, 0x83, 0x00, + sizeof (bulk_flags), &bulk_flags); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error sending bulk flags\n", + __FUNCTION__); + return ret; + } + + /* Check acknowledge for bulk flags just sent */ + DBG (DBG_usb, "%s: USB-in-USB: checking confirmation for bulk flags\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* Prepare bulk read request */ + memset (&ctrl, 0, sizeof (ctrl)); + ctrl.bRequestType = 0x00; + ctrl.bEndpoint = 0x82; + ctrl.wLength = htons (next_pages); + + /* Send bulk read request */ + DBG (DBG_usb, "%s: USB-in-USB: sending control msg for bulk\n", + __FUNCTION__); + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x04, 0x82, 0x00, + sizeof (ctrl), + (unsigned char *) &ctrl); + if (ret != SANE_STATUS_GOOD) + { + DBG (DBG_err, "%s: USB-in-USB: error sending control msg\n", + __FUNCTION__); + return ret; + } + + /* Check acknowledge for bulk read request */ + DBG (DBG_usb, "%s: USB-in-USB: checking if control msg was accepted\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + } + + next_portion = BULK_READ_PAGE_SIZE; + /* Check if next page will fit into the buffer */ + if (bulk_read_state->buffer_size + - bulk_read_state->bytes_available < next_portion) + { + DBG (DBG_err, "%s: USB-in-USB: buffer too small\n", __FUNCTION__); + return SANE_STATUS_NO_MEM; + } + + /* Bulk read next page */ + DBG (DBG_usb, "%s: USB-in-USB: bulk reading %u bytes\n", + __FUNCTION__, next_portion); + ret = sanei_usb_read_bulk (dn, + bulk_read_state->buffer_in_ptr, + &next_portion); + if (ret != SANE_STATUS_GOOD) + { + if (ret == SANE_STATUS_EOF) + return ret; + DBG (DBG_err, "%s: USB-in-USB: error during bulk read: %s\n", + __FUNCTION__, sane_strstatus (ret)); + return ret; + } + + /* Check if we received the same amount of data as requsted */ + if (next_portion != BULK_READ_PAGE_SIZE) + { + DBG (DBG_err, "%s: USB-in-USB: incomplete bulk read " + "(requested %u bytes, got %u bytes)\n", + __FUNCTION__, BULK_READ_PAGE_SIZE, next_portion); + return SANE_STATUS_IO_ERROR; + } + + /* Move pointers to the next position */ + bulk_read_state->buffer_in_ptr += next_portion; + + /* Check for the end of the buffer */ + if (bulk_read_state->buffer_in_ptr > bulk_read_state->buffer_end_ptr) + { + DBG (DBG_err, + "%s: USB-in-USB: attempted to access over the end of buffer " + "(in_ptr: %p, end_ptr: %p, ptr: %p, buffer size: %u\n", + __FUNCTION__, bulk_read_state->buffer_in_ptr, + bulk_read_state->buffer_end_ptr, bulk_read_state->buffer, + bulk_read_state->buffer_size); + return SANE_STATUS_NO_MEM; + } + + /* Check for buffer pointer wrapping */ + if (bulk_read_state->buffer_in_ptr == bulk_read_state->buffer_end_ptr) + { + DBG (DBG_usb, "%s: USB-in-USB: buffer wrapped while writing\n", + __FUNCTION__); + bulk_read_state->buffer_in_ptr = bulk_read_state->buffer; + } + + /* Count the amount of data we read */ + bulk_read_state->bytes_available += next_portion; + } + + /* Transfer requested amount of data to the caller */ + DBG (DBG_usb, "%s: USB-in-USB: data in bulk buffer is available " + "(requested %u bytes, available %u bytes)\n", + __FUNCTION__, size, bulk_read_state->bytes_available); + + /* Check for buffer pointer wrapping */ + bytes_until_buffer_end = bulk_read_state->buffer_end_ptr + - bulk_read_state->buffer_out_ptr; + if (bytes_until_buffer_end <= size) + { + /* First buffer part */ + DBG (DBG_usb, "%s: USB-in-USB: reached bulk read buffer end\n", __FUNCTION__); + memcpy (bytes, bulk_read_state->buffer_out_ptr, bytes_until_buffer_end); + bulk_read_state->buffer_out_ptr = bulk_read_state->buffer; + /* And second part (if any) */ + if (bytes_until_buffer_end < size) + { + DBG (DBG_usb, "%s: USB-in-USB: giving 2nd buffer part\n", __FUNCTION__); + memcpy (bytes + bytes_until_buffer_end, + bulk_read_state->buffer_out_ptr, + size - bytes_until_buffer_end); + bulk_read_state->buffer_out_ptr += size - bytes_until_buffer_end; + } + } + else + { + /* The data is in one buffer part (w/o wrapping) */ + memcpy (bytes, bulk_read_state->buffer_out_ptr, size); + bulk_read_state->buffer_out_ptr += size; + if (bulk_read_state->buffer_out_ptr == bulk_read_state->buffer_end_ptr) + { + DBG (DBG_usb, "%s: USB-in-USB: buffer wrapped while reading\n", + __FUNCTION__); + bulk_read_state->buffer_out_ptr = bulk_read_state->buffer; + } + } + + /* Count the amount of data transferred to the caller */ + bulk_read_state->bytes_available -= size; + + return SANE_STATUS_GOOD; +} + +/******************************************************************************* + * USB-in-USB: bulk write + * + * Parameters + * dn - sanei_usb device descriptor + * cmd - command for bulk write operation + * bytes - pointer to data buffer + * size - size of data + * + * Returns + * SANE_STATUS_GOOD - all data transferred successfully + * all other SANE_Status value - otherwise + */ +static SANE_Status +hp5590_bulk_write (SANE_Int dn, int cmd, unsigned char *bytes, + unsigned int size) +{ + struct usb_bulk_setup ctrl; + SANE_Status ret; + struct bulk_size bulk_size; + + unsigned int len; + unsigned char *ptr; + unsigned int next_portion; + + DBG (3, "%s: USB-in-USB: command: %04x, size %u\n", __FUNCTION__, cmd, + size); + + hp5590_low_assert (bytes != NULL); + + /* Prepare bulk write request */ + memset (&bulk_size, 0, sizeof (bulk_size)); + /* Counted in page size */ + bulk_size.size = size / BULK_WRITE_PAGE_SIZE; + + /* Send bulk write request */ + DBG (3, "%s: USB-in-USB: total %u pages (each of %u bytes)\n", + __FUNCTION__, bulk_size.size, BULK_WRITE_PAGE_SIZE); + ret = hp5590_control_msg (dn, USB_DIR_OUT, + 0x04, cmd, 0, + (unsigned char *) &bulk_size, sizeof (bulk_size), + CORE_DATA | CORE_BULK_OUT); + if (ret != SANE_STATUS_GOOD) + return ret; + + len = size; + ptr = bytes; + + /* Send all data in pages */ + while (len) + { + next_portion = BULK_WRITE_PAGE_SIZE; + if (len < next_portion) + next_portion = len; + + DBG (3, "%s: USB-in-USB: next portion %u bytes\n", + __FUNCTION__, next_portion); + + /* Prepare bulk write request */ + memset (&ctrl, 0, sizeof (ctrl)); + ctrl.bRequestType = 0x01; + ctrl.bEndpoint = 0x82; + ctrl.wLength = htons (next_portion); + + /* Send bulk write request */ + ret = sanei_usb_control_msg (dn, USB_DIR_OUT | USB_TYPE_VENDOR, + 0x04, 0x82, 0, + sizeof (ctrl), (unsigned char *) &ctrl); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* Check acknowledge for bulk write request */ + DBG (DBG_usb, "%s: USB-in-USB: checking if command was accepted\n", + __FUNCTION__); + ret = hp5590_get_ack (dn); + if (ret != SANE_STATUS_GOOD) + return ret; + + /* Write bulk data */ + DBG (3, "%s: USB-in-USB: bulk writing %u bytes\n", + __FUNCTION__, next_portion); + ret = sanei_usb_write_bulk (dn, ptr, &next_portion); + if (ret != SANE_STATUS_GOOD) + { + /* Treast EOF as successful result */ + if (ret == SANE_STATUS_EOF) + break; + DBG (DBG_err, "%s: USB-in-USB: error during bulk write: %s\n", + __FUNCTION__, sane_strstatus (ret)); + return ret; + } + + /* Move to the next page */ + len -= next_portion; + ptr += next_portion; + } + + /* Verify bulk command */ + return hp5590_verify_last_cmd (dn, cmd); +} +/* vim: sw=2 ts=8 + */ diff --git a/backend/hp5590_low.h b/backend/hp5590_low.h new file mode 100644 index 000000000..ee9fc4c53 --- /dev/null +++ b/backend/hp5590_low.h @@ -0,0 +1,77 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 2007 Ilia Sotnikov + 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 is part of a SANE backend for HP 5550/5590/7650 Scanners +*/ + +#ifndef HP5590_LOW_H +#define HP5590_LOW_H + +#include "sane/sane.h" + +/* Flags for hp5590_cmd() */ +#define CMD_IN 1 << 0 /* Indicates IN direction, otherwise - OUT */ +#define CMD_VERIFY 1 << 1 /* Requests last command verification */ + +/* Core flags for hp5590_cmd() - they indicate so called CORE commands */ +#define CORE_NONE 0 /* No CORE operation */ +#define CORE_DATA 1 << 0 /* Operate on CORE data */ +#define CORE_BULK_IN 1 << 1 /* CORE bulk operation - prepare for bulk IN + * transfer (not used yet) + */ +#define CORE_BULK_OUT 1 << 2 /* CORE bulk operation - prepare for bulk OUT + * transfer + */ +static SANE_Status hp5590_cmd (SANE_Int dn, + unsigned int flags, + unsigned int cmd, unsigned char *data, + unsigned int size, unsigned int core_flags); +static SANE_Status hp5590_bulk_read (SANE_Int dn, + unsigned char *bytes, + unsigned int size, void *state); +static SANE_Status hp5590_bulk_write (SANE_Int dn, int cmd, + unsigned char *bytes, + unsigned int size); +static SANE_Status hp5590_get_status (SANE_Int dn); +static SANE_Status hp5590_low_init_bulk_read_state (void **state); +static SANE_Status hp5590_low_free_bulk_read_state (void **state); +#endif /* HP5590_LOW_H */ +/* vim: sw=2 ts=8 + */ diff --git a/doc/sane-hp5590.man b/doc/sane-hp5590.man new file mode 100644 index 000000000..9623d35ab --- /dev/null +++ b/doc/sane-hp5590.man @@ -0,0 +1,62 @@ +.TH sane-hp5590 5 "9 Apr 2007" "@PACKAGEVERSION@" "SANE Scanner Access Now Easy" +.IX sane-hp5590 +.SH NAME +sane-hp5590 \- SANE backend for Hewlett-Packard 5550/5590/7650 Workgroup/Document scanners +.SH DESCRIPTION +The +.B sane-hp5590 +library implements a SANE (Scanner Access Now Easy) backend that provides +access to the following Hewlett-Packard Workgroup/Document scanners: +.PP +.RS +ScanJet 5550 +.br +ScanJet 5590 +.br +ScanJet 7650 +.RE +.PP +If you own a scanner other than the ones listed above that works with this +backend, please let us know this by sending the scanner's exact model name and +the USB vendor and device ids (e.g. from +.IR /proc/bus/usb/devices , +.I sane-find-scanner +or syslog) to us. Even if the scanner's name is only slightly different from +the models mentioned above, please let us know. +.SH CONFIGURATION +None required. +.SH FILES +.TP +.I @LIBDIR@/libsane-hp5590.a +The static library implementing this backend. +.TP +.I @LIBDIR@/libsane-hp5590.so +The shared library implementing this backend (present on systems that +support dynamic loading). +.SH ENVIRONMENT +.TP +.B SANE_DEBUG_HP5590 +If the library was compiled with debug support enabled, this +environment variable controls the debug level for this backend. Higher +debug levels increase the verbosity of the output. See used levels below. +.P +.RS +Level 10 - generic processing +.br +Level 20 - verbose backend messages +.br +Level 40 - HP5590 high-level commands +.br +Level 50 - HP5590 low-level (USB-in-USB) commands + +.P +Example: + +export SANE_DEBUG_HP5590=50 + +.SH "SEE ALSO" +.BR sane (7), +.BR sane-usb (5), + +.SH AUTHOR +Ilia Sotnikov