From 982bf43a644a6912c260578f53eb620bb7a1419a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Voltz?= Date: Mon, 15 Feb 2010 06:46:04 +0100 Subject: [PATCH] p5 (Parallel Port Primax PagePartner) backend files addition --- backend/p5.c | 2044 ++++++++++++++++++++++++++++++++++++++ backend/p5.conf.in | 10 + backend/p5.h | 206 ++++ backend/p5_device.c | 1596 +++++++++++++++++++++++++++++ backend/p5_device.h | 304 ++++++ doc/descriptions/p5.desc | 31 + doc/sane-p5.man | 160 +++ 7 files changed, 4351 insertions(+) create mode 100644 backend/p5.c create mode 100644 backend/p5.conf.in create mode 100644 backend/p5.h create mode 100644 backend/p5_device.c create mode 100644 backend/p5_device.h create mode 100644 doc/descriptions/p5.desc create mode 100644 doc/sane-p5.man diff --git a/backend/p5.c b/backend/p5.c new file mode 100644 index 000000000..76df263af --- /dev/null +++ b/backend/p5.c @@ -0,0 +1,2044 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2009 Stéphane Voltz + + 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. +*/ +/* -------------------------------------------------------------------------- + +*/ + +/* ------------------------------------------------------------------------- */ +/*! \mainpage Primax PagePartner Parallel Port scanner Index Page + * + * \section intro_sec Introduction + * + * This backend provides support for the Prima PagePartner sheet fed parallel + * port scanner. + * + * \section sane_api SANE API + * + * \subsection sane_flow sane flow + SANE FLOW + - sane_init() : initialize backend, attach scanners. + - sane_get_devices() : query list of scanner devices, backend must + probe for new devices. + - sane_open() : open a particular scanner device, adding a handle + to the opened device + - sane_set_io_mode() : set blocking mode + - sane_get_select_fd() : get scanner fd + - sane_get_option_descriptor() : get option information + - sane_control_option() : change option values + - sane_start() : start image acquisition + - sane_get_parameters() : returns actual scan parameters for the ongoing scan + - sane_read() : read image data + - sane_cancel() : cancel operation, end scan + - sane_close() : close opened scanner device, freeing scanner handle + - sane_exit() : terminate use of backend, freeing all resources for attached + devices when last frontend quits + */ + +/** + * the build number allow to know which version of the backend is running. + */ +#define BUILD 1 + +#include "p5.h" + +/** + * Import directly the low level part needed to + * operate scanner. The alternative is to prefix all public functions + * with sanei_p5_ ,and have all the functions prototyped in + * p5_device.h . + */ +#include "p5_device.c" + +/** + * number of time the backend has been loaded by sane_init. + */ +static int init_count = 0; + +/** + * NULL terminated list of opened frontend sessions. Sessions are + * inserted here on sane_open() and removed on sane_close(). + */ +static P5_Session *sessions = NULL; + +/** + * NULL terminated list of detected physical devices. + * The same device may be opened several time by different sessions. + * Entry are inserted here by the attach() function. + * */ +static P5_Device *devices = NULL; + +/** + * NULL terminated list of devices needed by sane_get_devices(), since + * the result returned must stay consistent until next call. + */ +static const SANE_Device **devlist = 0; + +/** + * list of possible color modes + */ +static SANE_String_Const mode_list[] = { + SANE_I18N (COLOR_MODE), + SANE_I18N (GRAY_MODE), + /* SANE_I18N (LINEART_MODE), not supported yet */ + 0 +}; + +static SANE_Range x_range = { + SANE_FIX (0.0), /* minimum */ + SANE_FIX (216.0), /* maximum */ + SANE_FIX (0.0) /* quantization */ +}; + +static SANE_Range y_range = { + SANE_FIX (0.0), /* minimum */ + SANE_FIX (299.0), /* maximum */ + SANE_FIX (0.0) /* no quantization */ +}; + +static const SANE_Range u8_range = { + 0, /* minimum */ + 255, /* maximum */ + 0 /* no quantization */ +}; + +static const SANE_Range threshold_percentage_range = { + SANE_FIX (0), /* minimum */ + SANE_FIX (100), /* maximum */ + SANE_FIX (1) /* quantization */ +}; + +/** + * finds the maximum string length in a string array. + */ +static size_t +max_string_size (const SANE_String_Const strings[]) +{ + size_t size, max_size = 0; + SANE_Int i; + + for (i = 0; strings[i]; ++i) + { + size = strlen (strings[i]) + 1; + if (size > max_size) + max_size = size; + } + return max_size; +} + +/**> placeholders for decoded configuration values */ +static P5_Config p5cfg; + + +/* ------------------------------------------------------------------------- */ + +/* + * SANE Interface + */ + + +/** + * Called by SANE initially. + * + * From the SANE spec: + * This function must be called before any other SANE function can be + * called. The behavior of a SANE backend is undefined if this + * function is not called first. The version code of the backend is + * returned in the value pointed to by version_code. If that pointer + * is NULL, no version code is returned. Argument authorize is either + * a pointer to a function that is invoked when the backend requires + * authentication for a specific resource or NULL if the frontend does + * not support authentication. + */ +SANE_Status +sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) +{ + SANE_Status status; + + authorize = authorize; /* get rid of compiler warning */ + + init_count++; + + /* init backend debug */ + DBG_INIT (); + DBG (DBG_info, "SANE P5 backend version %d.%d-%d\n", + SANE_CURRENT_MAJOR, V_MINOR, BUILD); + DBG (DBG_proc, "sane_init: start\n"); + DBG (DBG_trace, "sane_init: init_count=%d\n", init_count); + + if (version_code) + *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD); + + /* cold-plugging case : probe for already plugged devices */ + status = probe_p5_devices (); + + DBG (DBG_proc, "sane_init: exit\n"); + return status; +} + + +/** + * Called by SANE to find out about supported devices. + * + * From the SANE spec: + * This function can be used to query the list of devices that are + * available. If the function executes successfully, it stores a + * pointer to a NULL terminated array of pointers to SANE_Device + * structures in *device_list. The returned list is guaranteed to + * remain unchanged and valid until (a) another call to this function + * is performed or (b) a call to sane_exit() is performed. This + * function can be called repeatedly to detect when new devices become + * available. If argument local_only is true, only local devices are + * returned (devices directly attached to the machine that SANE is + * running on). If it is false, the device list includes all remote + * devices that are accessible to the SANE library. + * + * SANE does not require that this function is called before a + * sane_open() call is performed. A device name may be specified + * explicitly by a user which would make it unnecessary and + * undesirable to call this function first. + * @param device_list pointer where to store the device list + * @param local_only SANE_TRUE if only local devices are required. + * @return SANE_STATUS_GOOD when successfull + */ +SANE_Status +sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) +{ + int dev_num, devnr; + struct P5_Device *device; + SANE_Device *sane_device; + int i; + + DBG (DBG_proc, "sane_get_devices: start: local_only = %s\n", + local_only == SANE_TRUE ? "true" : "false"); + + /* free existing devlist first */ + if (devlist) + { + for (i = 0; devlist[i] != NULL; i++) + free ((SANE_Device *) devlist[i]); + free (devlist); + devlist = NULL; + } + + /** + * Since sane_get_devices() may be called repeatedly to detect new devices, + * the device detection must be run at each call. We are handling + * hot-plugging : we probe for devices plugged since sane_init() was called. + */ + probe_p5_devices (); + + /* if no devices detected, just return an empty list */ + if (devices == NULL) + { + devlist = malloc (sizeof (devlist[0])); + if (!devlist) + return SANE_STATUS_NO_MEM; + devlist[0] = NULL; + *device_list = devlist; + DBG (DBG_proc, "sane_get_devices: exit with no device\n"); + return SANE_STATUS_GOOD; + } + + /* count physical devices */ + devnr = 1; + device = devices; + while (device->next) + { + devnr++; + device = device->next; + } + + /* allocate room for the list, plus 1 for the NULL terminator */ + devlist = malloc ((devnr + 1) * sizeof (devlist[0])); + if (!devlist) + return SANE_STATUS_NO_MEM; + + *device_list = devlist; + + dev_num = 0; + device = devices; + + /* we build a list of SANE_Device from the list of attached devices */ + for (i = 0; i < devnr; i++) + { + /* add device according to local only flag */ + if ((local_only == SANE_TRUE && device->local == SANE_TRUE) + || local_only == SANE_FALSE) + { + /* allocate memory to add the device */ + sane_device = malloc (sizeof (*sane_device)); + if (!sane_device) + { + return SANE_STATUS_NO_MEM; + } + + /* copy data */ + sane_device->name = device->name; + sane_device->vendor = device->model->vendor; + sane_device->model = device->model->product; + sane_device->type = device->model->type; + devlist[dev_num] = sane_device; + + /* increment device counter */ + dev_num++; + } + + /* go to next detected device */ + device = device->next; + } + devlist[dev_num] = 0; + + *device_list = devlist; + + DBG (DBG_proc, "sane_get_devices: exit\n"); + + return SANE_STATUS_GOOD; +} + + +/** + * Called to establish connection with the session. This function will + * also establish meaningful defaults and initialize the options. + * + * From the SANE spec: + * This function is used to establish a connection to a particular + * device. The name of the device to be opened is passed in argument + * name. If the call completes successfully, a handle for the device + * is returned in *h. As a special case, specifying a zero-length + * string as the device requests opening the first available device + * (if there is such a device). Another special case is to only give + * the name of the backend as the device name, in this case the first + * available device will also be used. + * @param name name of the device to open + * @param handle opaque pointer where to store the pointer of + * the opened P5_Session + * @return SANE_STATUS_GOOD on success + */ +SANE_Status +sane_open (SANE_String_Const name, SANE_Handle * handle) +{ + struct P5_Session *session = NULL; + struct P5_Device *device = NULL; + + DBG (DBG_proc, "sane_open: start (devicename=%s)\n", name); + + /* check there is at least a device */ + if (devices == NULL) + { + DBG (DBG_proc, "sane_open: exit, no device to open!\n"); + return SANE_STATUS_INVAL; + } + + if (name[0] == 0 || strncmp (name, "p5", strlen ("p5")) == 0) + { + DBG (DBG_info, + "sane_open: no specific device requested, using default\n"); + if (devices) + { + device = devices; + DBG (DBG_info, "sane_open: device %s used as default device\n", + device->name); + } + } + else + { + DBG (DBG_info, "sane_open: device %s requested\n", name); + /* walk the device list until we find a matching name */ + device = devices; + while (device && strcmp (device->name, name) != 0) + { + DBG (DBG_trace, "sane_open: device %s doesn't match\n", + device->name); + device = device->next; + } + } + + /* check wether we have found a match or reach the end of the device list */ + if (!device) + { + DBG (DBG_info, "sane_open: no device found\n"); + return SANE_STATUS_INVAL; + } + + /* now we have a device, duplicate it and return it in handle */ + DBG (DBG_info, "sane_open: device %s found\n", name); + + /* device initialization */ + if (device->initialized == SANE_FALSE) + { + /** + * call to hardware initialization function here. + */ + device->fd = open_pp (device->name); + if (device->fd < 0) + { + DBG (DBG_error, "sane_open: failed to open '%s' device!\n", + device->name); + return SANE_STATUS_INVAL; + } + + /* now try to connect to scanner */ + if (connect (device->fd) != SANE_TRUE) + { + DBG (DBG_error, "sane_open: failed to connect!\n"); + close_pp (device->fd); + return SANE_STATUS_INVAL; + } + + /* load calibration data */ + restore_calibration (device); + + /* device link is OK now */ + device->initialized = SANE_TRUE; + } + device->buffer = NULL; + device->gain = NULL; + device->offset = NULL; + + /* prepare handle to return */ + session = (P5_Session *) malloc (sizeof (P5_Session)); + if (session == NULL) + { + DBG (DBG_proc, "sane_open: exit OOM\n"); + return SANE_STATUS_NO_MEM; + } + + /* initalize session */ + session->dev = device; + session->scanning = SANE_FALSE; + session->non_blocking = SANE_FALSE; + + /* initialize SANE options for this session */ + init_options (session); + + /* add the handle to the linked list of sessions */ + session->next = sessions; + sessions = session; + + /* store result */ + *handle = session; + + /* exit success */ + DBG (DBG_proc, "sane_open: exit\n"); + return SANE_STATUS_GOOD; +} + + +/** + * Set non blocking mode. In this mode, read return immediatly when + * no data is available whithin sane_read(), instead of polling the scanner. + */ +SANE_Status +sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) +{ + P5_Session *session = (P5_Session *) handle; + + DBG (DBG_proc, "sane_set_io_mode: start\n"); + if (session->scanning != SANE_TRUE) + { + DBG (DBG_error, "sane_set_io_mode: called out of a scan\n"); + return SANE_STATUS_INVAL; + } + session->non_blocking = non_blocking; + DBG (DBG_info, "sane_set_io_mode: I/O mode set to %sblocking.\n", + non_blocking ? "non " : " "); + DBG (DBG_proc, "sane_set_io_mode: exit\n"); + return SANE_STATUS_GOOD; +} + + +/** + * An advanced method we don't support but have to define. At SANE API + * level this function is meant to provide a file descriptor on which the + * frontend can do select()/poll() to wait for data. + */ +SANE_Status +sane_get_select_fd (SANE_Handle handle, SANE_Int * fdp) +{ + /* make compiler happy ... */ + handle = handle; + fdp = fdp; + + DBG (DBG_proc, "sane_get_select_fd: start\n"); + DBG (DBG_warn, "sane_get_select_fd: unsupported ...\n"); + DBG (DBG_proc, "sane_get_select_fd: exit\n"); + return SANE_STATUS_UNSUPPORTED; +} + + +/** + * Returns the options we know. + * + * From the SANE spec: + * This function is used to access option descriptors. The function + * returns the option descriptor for option number n of the device + * represented by handle h. Option number 0 is guaranteed to be a + * valid option. Its value is an integer that specifies the number of + * options that are available for device handle h (the count includes + * option 0). If n is not a valid option index, the function returns + * NULL. The returned option descriptor is guaranteed to remain valid + * (and at the returned address) until the device is closed. + */ +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) +{ + struct P5_Session *session = handle; + + DBG (DBG_proc, "sane_get_option_descriptor: start\n"); + + if ((unsigned) option >= NUM_OPTIONS) + return NULL; + + DBG (DBG_info, "sane_get_option_descriptor: \"%s\"\n", + session->options[option].descriptor.name); + + DBG (DBG_proc, "sane_get_option_descriptor: exit\n"); + return &(session->options[option].descriptor); +} + +/** + * sets automatic value for an option , called by sane_control_option after + * all checks have been done */ +static SANE_Status +set_automatic_value (P5_Session * s, int option, SANE_Int * myinfo) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Int i, min; + SANE_Word *dpi_list; + + switch (option) + { + case OPT_TL_X: + s->options[OPT_TL_X].value.w = x_range.min; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + case OPT_TL_Y: + s->options[OPT_TL_Y].value.w = y_range.min; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + case OPT_BR_X: + s->options[OPT_BR_X].value.w = x_range.max; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + case OPT_BR_Y: + s->options[OPT_BR_Y].value.w = y_range.max; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + case OPT_RESOLUTION: + /* we set up to the lowest available dpi value */ + dpi_list = + (SANE_Word *) s->options[OPT_RESOLUTION].descriptor.constraint. + word_list; + min = 65536; + for (i = 1; i < dpi_list[0]; i++) + { + if (dpi_list[i] < min) + min = dpi_list[i]; + } + s->options[OPT_RESOLUTION].value.w = min; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + case OPT_PREVIEW: + s->options[OPT_PREVIEW].value.w = SANE_FALSE; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + case OPT_MODE: + if (s->options[OPT_MODE].value.s) + free (s->options[OPT_MODE].value.s); + s->options[OPT_MODE].value.s = strdup (mode_list[0]); + *myinfo |= SANE_INFO_RELOAD_OPTIONS; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + default: + DBG (DBG_warn, "set_automatic_value: can't set unknown option %d\n", + option); + } + + return status; +} + +/** + * sets an option , called by sane_control_option after all + * checks have been done */ +static SANE_Status +set_option_value (P5_Session * s, int option, void *val, SANE_Int * myinfo) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Word tmpw; + + switch (option) + { + case OPT_TL_X: + case OPT_BR_X: + case OPT_TL_Y: + case OPT_BR_Y: + s->options[option].value.w = *(SANE_Word *) val; + /* we ensure geometry is coherent */ + /* this happens when user drags TL corner right or below the BR point */ + if (s->options[OPT_BR_Y].value.w < s->options[OPT_TL_Y].value.w) + { + tmpw = s->options[OPT_BR_Y].value.w; + s->options[OPT_BR_Y].value.w = s->options[OPT_TL_Y].value.w; + s->options[OPT_TL_Y].value.w = tmpw; + } + if (s->options[OPT_BR_X].value.w < s->options[OPT_TL_X].value.w) + { + tmpw = s->options[OPT_BR_X].value.w; + s->options[OPT_BR_X].value.w = s->options[OPT_TL_X].value.w; + s->options[OPT_TL_X].value.w = tmpw; + } + + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + + case OPT_RESOLUTION: + case OPT_PREVIEW: + s->options[option].value.w = *(SANE_Word *) val; + *myinfo |= SANE_INFO_RELOAD_PARAMS; + break; + + case OPT_MODE: + if (s->options[option].value.s) + free (s->options[option].value.s); + s->options[option].value.s = strdup (val); + *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + break; + + case OPT_CALIBRATE: + status = sheetfed_calibration (s->dev); + *myinfo |= SANE_INFO_RELOAD_OPTIONS; + break; + + case OPT_CLEAR_CALIBRATION: + cleanup_calibration (s->dev); + *myinfo |= SANE_INFO_RELOAD_OPTIONS; + break; + + default: + DBG (DBG_warn, "set_option_value: can't set unknown option %d\n", + option); + } + return status; +} + +/** + * gets an option , called by sane_control_option after all checks + * have been done */ +static SANE_Status +get_option_value (P5_Session * s, int option, void *val) +{ + SANE_Status status; + + switch (option) + { + /* word or word equivalent options: */ + case OPT_NUM_OPTS: + case OPT_RESOLUTION: + case OPT_PREVIEW: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + *(SANE_Word *) val = s->options[option].value.w; + break; + + /* string options: */ + case OPT_MODE: + strcpy (val, s->options[option].value.s); + break; + + /* sensor options */ + case OPT_PAGE_LOADED_SW: + status = test_document (s->dev->fd); + if (status == SANE_STATUS_GOOD) + s->options[option].value.b = SANE_TRUE; + else + s->options[option].value.b = SANE_FALSE; + *(SANE_Bool *) val = s->options[option].value.b; + break; + + case OPT_NEED_CALIBRATION_SW: + *(SANE_Bool *) val = !s->dev->calibrated; + break; + + + /* unhandled options */ + default: + DBG (DBG_warn, "get_option_value: can't get unknown option %d\n", + option); + } + + return SANE_STATUS_GOOD; +} + +/** + * Gets or sets an option value. + * + * From the SANE spec: + * This function is used to set or inquire the current value of option + * number n of the device represented by handle h. The manner in which + * the option is controlled is specified by parameter action. The + * possible values of this parameter are described in more detail + * below. The value of the option is passed through argument val. It + * is a pointer to the memory that holds the option value. The memory + * area pointed to by v must be big enough to hold the entire option + * value (determined by member size in the corresponding option + * descriptor). + * + * The only exception to this rule is that when setting the value of a + * string option, the string pointed to by argument v may be shorter + * since the backend will stop reading the option value upon + * encountering the first NUL terminator in the string. If argument i + * is not NULL, the value of *i will be set to provide details on how + * well the request has been met. + * action is SANE_ACTION_GET_VALUE, SANE_ACTION_SET_VALUE or SANE_ACTION_SET_AUTO + */ +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int option, + SANE_Action action, void *val, SANE_Int * info) +{ + P5_Session *s = handle; + SANE_Status status; + SANE_Word cap; + SANE_Int myinfo = 0; + + DBG (DBG_io2, + "sane_control_option: start: action = %s, option = %s (%d)\n", + (action == SANE_ACTION_GET_VALUE) ? "get" : (action == + SANE_ACTION_SET_VALUE) ? + "set" : (action == SANE_ACTION_SET_AUTO) ? "set_auto" : "unknown", + s->options[option].descriptor.name, option); + + if (info) + *info = 0; + + /* do checks before trying to apply action */ + + if (s->scanning) + { + DBG (DBG_warn, "sane_control_option: don't call this function while " + "scanning (option = %s (%d))\n", + s->options[option].descriptor.name, option); + return SANE_STATUS_DEVICE_BUSY; + } + + /* option must be within existing range */ + if (option >= NUM_OPTIONS || option < 0) + { + DBG (DBG_warn, + "sane_control_option: option %d >= NUM_OPTIONS || option < 0\n", + option); + return SANE_STATUS_INVAL; + } + + /* don't access an inactive option */ + cap = s->options[option].descriptor.cap; + if (!SANE_OPTION_IS_ACTIVE (cap)) + { + DBG (DBG_warn, "sane_control_option: option %d is inactive\n", option); + return SANE_STATUS_INVAL; + } + + /* now checks have been done, apply action */ + switch (action) + { + case SANE_ACTION_GET_VALUE: + status = get_option_value (s, option, val); + break; + + case SANE_ACTION_SET_VALUE: + if (!SANE_OPTION_IS_SETTABLE (cap)) + { + DBG (DBG_warn, "sane_control_option: option %d is not settable\n", + option); + return SANE_STATUS_INVAL; + } + + status = + sanei_constrain_value (&s->options[option].descriptor, val, &myinfo); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_warn, + "sane_control_option: sanei_constrain_value returned %s\n", + sane_strstatus (status)); + return status; + } + + /* return immediatly if no change */ + if (s->options[option].descriptor.type == SANE_TYPE_INT + && *(SANE_Word *) val == s->options[option].value.w) + { + status = SANE_STATUS_GOOD; + } + else + { /* apply change */ + status = set_option_value (s, option, val, &myinfo); + } + break; + + case SANE_ACTION_SET_AUTO: + /* sets automatic values */ + if (!(cap & SANE_CAP_AUTOMATIC)) + { + DBG (DBG_warn, + "sane_control_option: option %d is not autosettable\n", + option); + return SANE_STATUS_INVAL; + } + + status = set_automatic_value (s, option, &myinfo); + break; + + default: + DBG (DBG_error, "sane_control_option: invalid action %d\n", action); + status = SANE_STATUS_INVAL; + break; + } + + if (info) + *info = myinfo; + + DBG (DBG_io2, "sane_control_option: exit\n"); + return status; +} + +/** + * Called by SANE when a page acquisition operation is to be started. + * @param handle opaque handle to a frontend session + * @return SANE_STATUS_GOOD on success, SANE_STATUS_BUSY if the device is + * in use by another session or SANE_STATUS_WARMING_UP if the device is + * warming up. In this case the fronted as to call sane_start again until + * warming up is done. Any other values returned are error status. + */ +SANE_Status +sane_start (SANE_Handle handle) +{ + struct P5_Session *session = handle; + int status = SANE_STATUS_GOOD; + P5_Device *dev = session->dev; + + DBG (DBG_proc, "sane_start: start\n"); + + /* if already scanning, tell we're busy */ + if (session->scanning == SANE_TRUE) + { + DBG (DBG_info, "sane_start: device is already scanning\n"); + return SANE_STATUS_DEVICE_BUSY; + } + + /* check that the device has been initialized */ + if (dev->initialized == SANE_FALSE) + { + DBG (DBG_error, "sane_start: device is not initialized\n"); + return SANE_STATUS_INVAL; + } + + /* check if there is a document */ + status = test_document (dev->fd); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "sane_start: device is already scanning\n"); + return status; + } + + /* we compute all the scan parameters so that */ + /* we will be able to set up the registers correctly */ + compute_parameters (session); + + /* move to scan area if needed */ + if (dev->ystart > 0) + { + status = move (dev); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "sane_start: failed to move to scan area\n"); + return SANE_STATUS_INVAL; + } + } + + /* send scan command */ + status = start_scan (dev, dev->mode, dev->ydpi, dev->xstart, dev->pixels); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "sane_start: failed to start scan\n"); + return SANE_STATUS_INVAL; + } + + /* allocates work buffer */ + if (dev->buffer != NULL) + { + free (dev->buffer); + } + + dev->position = 0; + dev->top = 0; + /* compute amount of lines needed for lds correction */ + dev->bottom = dev->bytes_per_line * 2 * dev->lds; + /* computes buffer size, 66 color lines plus eventual amount needed for lds */ + dev->size = dev->pixels * 3 * 66 + dev->bottom; + dev->buffer = (u_int8_t *) malloc (dev->size); + if (dev->buffer == NULL) + { + DBG (DBG_error, "sane_start: failed to allocate %d bytes\n", dev->size); + sane_cancel (handle); + return SANE_STATUS_NO_MEM; + } + + /* return now the scan has been initiated */ + session->scanning = SANE_TRUE; + session->sent = 0; + + DBG (DBG_io, "sane_start: to_send=%d\n", session->to_send); + DBG (DBG_io, "sane_start: size=%d\n", dev->size); + DBG (DBG_io, "sane_start: top=%d\n", dev->top); + DBG (DBG_io, "sane_start: bottom=%d\n", dev->bottom); + DBG (DBG_io, "sane_start: position=%d\n", dev->position); + + DBG (DBG_proc, "sane_start: exit\n"); + return status; +} + +/** @brief compute scan parameters + * This function computes two set of parameters. The one for the SANE's standard + * and the other for the hardware. Among these parameters are the bit depth, total + * number of lines, total number of columns, extra line to read for data reordering... + * @param session fronted session to compute final scan parameters + * @return SANE_STATUS_GOOD on success + */ +static SANE_Status +compute_parameters (P5_Session * session) +{ + P5_Device *dev = session->dev; + SANE_Int dpi; /* dpi for scan */ + SANE_String mode; + SANE_Status status = SANE_STATUS_GOOD; + + int tl_x, tl_y, br_x, br_y; + + mode = session->options[OPT_MODE].value.s; + dpi = session->options[OPT_RESOLUTION].value.w; + + /* scan coordinates */ + tl_x = SANE_UNFIX (session->options[OPT_TL_X].value.w); + tl_y = SANE_UNFIX (session->options[OPT_TL_Y].value.w); + br_x = SANE_UNFIX (session->options[OPT_BR_X].value.w); + br_y = SANE_UNFIX (session->options[OPT_BR_Y].value.w); + + /* only single pass scanning supported */ + session->params.last_frame = SANE_TRUE; + + /* gray modes */ + if (strcmp (mode, GRAY_MODE) == 0) + { + session->params.format = SANE_FRAME_GRAY; + dev->mode = MODE_GRAY; + dev->lds = 0; + } + else if (strcmp (mode, LINEART_MODE) == 0) + { + session->params.format = SANE_FRAME_GRAY; + dev->mode = MODE_LINEART; + dev->lds = 0; + } + else + { + /* Color */ + session->params.format = SANE_FRAME_RGB; + dev->mode = MODE_COLOR; + dev->lds = (dev->model->lds * dpi) / dev->model->max_ydpi; + } + + /* SANE level values */ + session->params.lines = ((br_y - tl_y) * dpi) / MM_PER_INCH; + if (session->params.lines == 0) + session->params.lines = 1; + session->params.pixels_per_line = ((br_x - tl_x) * dpi) / MM_PER_INCH; + if (session->params.pixels_per_line == 0) + session->params.pixels_per_line = 1; + + DBG (DBG_data, "compute_parameters: pixels_per_line =%d\n", + session->params.pixels_per_line); + + if (strcmp (mode, LINEART_MODE) == 0) + { + session->params.depth = 1; + /* in lineart, having pixels multiple of 8 avoids a costly test */ + /* at each bit to see we must go to the next byte */ + /* TODO : implement this requirement in sane_control_option */ + session->params.pixels_per_line = + ((session->params.pixels_per_line + 7) / 8) * 8; + } + else + session->params.depth = 8; + + /* width needs to be even */ + if (session->params.pixels_per_line & 1) + session->params.pixels_per_line++; + + /* Hardware settings : they can differ from the ones at SANE level */ + /* for instance the effective DPI used by a sensor may be higher */ + /* than the one needed for the SANE scan parameters */ + dev->lines = session->params.lines; + dev->pixels = session->params.pixels_per_line; + + /* motor and sensor DPI */ + dev->xdpi = dpi; + dev->ydpi = dpi; + + /* handle bounds of motor's dpi range */ + if (dev->ydpi > dev->model->max_ydpi) + { + dev->ydpi = dev->model->max_ydpi; + dev->lines = (dev->lines * dev->model->max_ydpi) / dpi; + if (dev->lines == 0) + dev->lines = 1; + + /* round number of lines */ + session->params.lines = + (session->params.lines / dev->lines) * dev->lines; + if (session->params.lines == 0) + session->params.lines = 1; + } + if (dev->ydpi < dev->model->min_ydpi) + { + dev->ydpi = dev->model->min_ydpi; + dev->lines = (dev->lines * dev->model->min_ydpi) / dpi; + } + + /* hardware values */ + dev->xstart = + ((SANE_UNFIX (dev->model->x_offset) + tl_x) * dpi) / MM_PER_INCH; + dev->ystart = + ((SANE_UNFIX (dev->model->y_offset) + tl_y) * dev->ydpi) / MM_PER_INCH; + + /* take lds correction into account when moving to scan area */ + if (dev->ystart > 2 * dev->lds) + dev->ystart -= 2 * dev->lds; + + + /* computes bytes per line */ + session->params.bytes_per_line = session->params.pixels_per_line; + dev->bytes_per_line = dev->pixels; + if (session->params.format == SANE_FRAME_RGB) + { + dev->bytes_per_line *= 3; + } + + /* in lineart mode we adjust bytes_per_line needed by frontend */ + /* we do that here because we needed sent/to_send to be as if */ + /* there was no lineart */ + if (session->params.depth == 1) + { + session->params.bytes_per_line = + (session->params.bytes_per_line + 7) / 8; + } + + session->params.bytes_per_line = dev->bytes_per_line; + session->to_send = session->params.bytes_per_line * session->params.lines; + session->params.bytes_per_line = dev->bytes_per_line; + + DBG (DBG_data, "compute_parameters: bytes_per_line =%d\n", + session->params.bytes_per_line); + DBG (DBG_data, "compute_parameters: depth =%d\n", + session->params.depth); + DBG (DBG_data, "compute_parameters: lines =%d\n", + session->params.lines); + DBG (DBG_data, "compute_parameters: image size =%d\n", + session->to_send); + + DBG (DBG_data, "compute_parameters: xstart =%d\n", dev->xstart); + DBG (DBG_data, "compute_parameters: ystart =%d\n", dev->ystart); + DBG (DBG_data, "compute_parameters: dev lines =%d\n", dev->lines); + DBG (DBG_data, "compute_parameters: dev bytes per line=%d\n", + dev->bytes_per_line); + DBG (DBG_data, "compute_parameters: dev pixels =%d\n", dev->pixels); + DBG (DBG_data, "compute_parameters: lds =%d\n", dev->lds); + + return status; +} + + +/** + * Called by SANE to retrieve information about the type of data + * that the current scan will return. + * + * From the SANE spec: + * This function is used to obtain the current scan parameters. The + * returned parameters are guaranteed to be accurate between the time + * a scan has been started (sane_start() has been called) and the + * completion of that request. Outside of that window, the returned + * values are best-effort estimates of what the parameters will be + * when sane_start() gets invoked. + * + * Calling this function before a scan has actually started allows, + * for example, to get an estimate of how big the scanned image will + * be. The parameters passed to this function are the handle of the + * device for which the parameters should be obtained and a pointer + * to a parameter structure. + */ +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters * params) +{ + SANE_Status status; + struct P5_Session *session = (struct P5_Session *) handle; + + DBG (DBG_proc, "sane_get_parameters: start\n"); + + /* call parameters computing function */ + status = compute_parameters (session); + if (status == SANE_STATUS_GOOD && params) + *params = session->params; + + DBG (DBG_proc, "sane_get_parameters: exit\n"); + return status; +} + + +/** + * Called by SANE to read data. + * + * From the SANE spec: + * This function is used to read image data from the device + * represented by handle h. Argument buf is a pointer to a memory + * area that is at least maxlen bytes long. The number of bytes + * returned is stored in *len. A backend must set this to zero when + * the call fails (i.e., when a status other than SANE_STATUS_GOOD is + * returned). + * + * When the call succeeds, the number of bytes returned can be + * anywhere in the range from 0 to maxlen bytes. + * + * Returned data is read from working buffer. + */ +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte * buf, + SANE_Int max_len, SANE_Int * len) +{ + struct P5_Session *session = (struct P5_Session *) handle; + struct P5_Device *dev = session->dev; + SANE_Status status = SANE_STATUS_GOOD; + int count; + int size, lines; + SANE_Bool x2; + SANE_Int i; + + DBG (DBG_proc, "sane_read: start\n"); + DBG (DBG_io, "sane_read: up to %d bytes required by frontend\n", max_len); + + /* some sanity checks first to protect from would be buggy frontends */ + if (!session) + { + DBG (DBG_error, "sane_read: handle is null!\n"); + return SANE_STATUS_INVAL; + } + + if (!buf) + { + DBG (DBG_error, "sane_read: buf is null!\n"); + return SANE_STATUS_INVAL; + } + + if (!len) + { + DBG (DBG_error, "sane_read: len is null!\n"); + return SANE_STATUS_INVAL; + } + + /* no data read yet */ + *len = 0; + + /* check if session is scanning */ + if (!session->scanning) + { + DBG (DBG_warn, + "sane_read: scan was cancelled, is over or has not been initiated yet\n"); + return SANE_STATUS_CANCELLED; + } + + /* check for EOF, must be done before any physical read */ + if (session->sent >= session->to_send) + { + DBG (DBG_io, "sane_read: end of scan reached\n"); + return SANE_STATUS_EOF; + } + + /* if working buffer is empty, we do a physical data read */ + if (dev->top <= dev->bottom) + { + DBG (DBG_io, "sane_read: physical data read\n"); + /* check is there is data available. In case of non-blocking mode we return + * as soon it is detected there is no data yet. Reads must by done line by + * line, so we read only when count is bigger than bytes per line + * */ + count = available_bytes (dev->fd); + DBG (DBG_io, "sane_read: count=%d bytes\n", count); + if (count < dev->bytes_per_line && session->non_blocking == SANE_TRUE) + { + DBG (DBG_io, "sane_read: scanner hasn't enough data available\n"); + DBG (DBG_proc, "sane_read: exit\n"); + return SANE_STATUS_GOOD; + } + + /* now we can wait for data here */ + while (count < dev->bytes_per_line) + { + /* test if document left the feeder, so we have to terminate the scan */ + status = test_document (dev->fd); + if (status == SANE_STATUS_NO_DOCS) + { + session->to_send = session->sent; + return SANE_STATUS_EOF; + } + + /* don't call scanner too often */ + usleep (10000); + count = available_bytes (dev->fd); + } + + /** compute size of physical data to read + * on first read, position will be 0, while it will be 'bottom' + * for the subsequent reads. + * We try to read a complete buffer */ + size = dev->size - dev->position; + + if (session->to_send - session->sent < size) + { + /* not enough data left, so read remainder of scan */ + size = session->to_send - session->sent; + } + + /* 600 dpi is 300x600 physical, and 400 is 200x400 */ + if (dev->ydpi > dev->model->max_xdpi) + { + x2 = SANE_TRUE; + } + else + { + x2 = SANE_FALSE; + } + lines = read_line (dev, + dev->buffer + dev->position, + dev->bytes_per_line, + size / dev->bytes_per_line, + SANE_TRUE, x2, dev->mode, dev->calibrated); + + /* handle document end detection TODO try to recover the partial + * buffer already read before EOD */ + if (lines == -1) + { + DBG (DBG_io, "sane_read: error reading line\n"); + return SANE_STATUS_IO_ERROR; + } + + /* gather lines until we have more than needed for lds */ + dev->position += lines * dev->bytes_per_line; + dev->top = dev->position; + if (dev->position > dev->bottom) + { + dev->position = dev->bottom; + } + DBG (DBG_io, "sane_read: size =%d\n", dev->size); + DBG (DBG_io, "sane_read: bottom =%d\n", dev->bottom); + DBG (DBG_io, "sane_read: position=%d\n", dev->position); + DBG (DBG_io, "sane_read: top =%d\n", dev->top); + } /* end of physical data reading */ + + /* logical data reading */ + /* check if there data available in working buffer */ + if (dev->position < dev->top && dev->position >= dev->bottom) + { + DBG (DBG_io, "sane_read: logical data read\n"); + /* we have more data in internal buffer than asked , + * then send only max data */ + size = dev->top - dev->position; + if (max_len < size) + { + *len = max_len; + } + else + /* if we don't have enough, send all what we have */ + { + *len = dev->top - dev->position; + } + + /* data copy */ + if (dev->lds == 0) + { + memcpy (buf, dev->buffer + dev->position, *len); + } + else + { + /* compute count of bytes for lds */ + count = dev->lds * dev->bytes_per_line; + + /* adjust for lds as we copy data to frontend */ + for (i = 0; i < *len; i++) + { + switch ((dev->position + i) % 3) + { + /* red */ + case 0: + buf[i] = dev->buffer[dev->position + i - 2 * count]; + break; + /* green */ + case 1: + buf[i] = dev->buffer[dev->position + i - count]; + break; + /* blue */ + default: + buf[i] = dev->buffer[dev->position + i]; + break; + } + } + } + dev->position += *len; + + /* update byte accounting */ + session->sent += *len; + DBG (DBG_io, "sane_read: sent %d bytes from buffer to frontend\n", + *len); + return SANE_STATUS_GOOD; + } + + /* check if we exhausted working buffer */ + if (dev->position >= dev->top && dev->position >= dev->bottom) + { + /* copy extra lines needed for lds in next buffer */ + if (dev->position > dev->bottom && dev->lds > 0) + { + memcpy (dev->buffer, + dev->buffer + dev->position - dev->bottom, dev->bottom); + } + + /* restart buffer */ + dev->position = dev->bottom; + dev->top = 0; + } + + DBG (DBG_io, "sane_read: size =%d\n", dev->size); + DBG (DBG_io, "sane_read: bottom =%d\n", dev->bottom); + DBG (DBG_io, "sane_read: position=%d\n", dev->position); + DBG (DBG_io, "sane_read: top =%d\n", dev->top); + + DBG (DBG_proc, "sane_read: exit\n"); + return status; +} + + +/** + * Cancels a scan. + * + * From the SANE spec: + * This function is used to immediately or as quickly as possible + * cancel the currently pending operation of the device represented by + * handle h. This function can be called at any time (as long as + * handle h is a valid handle) but usually affects long-running + * operations only (such as image is acquisition). It is safe to call + * this function asynchronously (e.g., from within a signal handler). + * It is important to note that completion of this operaton does not + * imply that the currently pending operation has been cancelled. It + * only guarantees that cancellation has been initiated. Cancellation + * completes only when the cancelled call returns (typically with a + * status value of SANE_STATUS_CANCELLED). Since the SANE API does + * not require any other operations to be re-entrant, this implies + * that a frontend must not call any other operation until the + * cancelled operation has returned. + */ +void +sane_cancel (SANE_Handle handle) +{ + P5_Session *session = handle; + + DBG (DBG_proc, "sane_cancel: start\n"); + + /* if scanning, abort and park head */ + if (session->scanning == SANE_TRUE) + { + /* detects if we are called after the scan is finished, + * or if the scan is aborted */ + if (session->sent < session->to_send) + { + DBG (DBG_info, "sane_cancel: aborting scan.\n"); + /* device hasn't finished scan, we are aborting it + * and we may have to do something specific for it here */ + } + else + { + DBG (DBG_info, "sane_cancel: cleaning up after scan.\n"); + } + session->scanning = SANE_FALSE; + } + eject (session->dev->fd); + + DBG (DBG_proc, "sane_cancel: exit\n"); +} + + +/** + * Ends use of the session. + * + * From the SANE spec: + * This function terminates the association between the device handle + * passed in argument h and the device it represents. If the device is + * presently active, a call to sane_cancel() is performed first. After + * this function returns, handle h must not be used anymore. + * + * Handle resources are free'd before disposing the handle. But devices + * resources must not be mdofied, since it could be used or reused until + * sane_exit() is called. + */ +void +sane_close (SANE_Handle handle) +{ + P5_Session *prev, *session; + + DBG (DBG_proc, "sane_close: start\n"); + + /* remove handle from list of open handles: */ + prev = NULL; + for (session = sessions; session; session = session->next) + { + if (session == handle) + break; + prev = session; + } + if (!session) + { + DBG (DBG_error0, "close: invalid handle %p\n", handle); + return; /* oops, not a handle we know about */ + } + + /* cancel any active scan */ + if (session->scanning == SANE_TRUE) + { + sane_cancel (handle); + } + + if (prev) + prev->next = session->next; + else + sessions = session->next; + + /* close low level device */ + if (session->dev->initialized == SANE_TRUE) + { + if (session->dev->calibrated == SANE_TRUE) + { + save_calibration (session->dev); + } + disconnect (session->dev->fd); + close_pp (session->dev->fd); + session->dev->fd = -1; + session->dev->initialized = SANE_FALSE; + + /* free device data */ + if (session->dev->buffer != NULL) + { + free (session->dev->buffer); + } + if (session->dev->buffer != NULL) + { + free (session->dev->gain); + free (session->dev->offset); + } + if (session->dev->calibrated == SANE_TRUE) + { + cleanup_calibration (session->dev); + } + } + + /* free per session data */ + free (session->options[OPT_MODE].value.s); + free (session->options[OPT_RESOLUTION].descriptor.constraint.word_list); + + free (session); + + DBG (DBG_proc, "sane_close: exit\n"); +} + + +/** + * Terminates the backend. + * + * From the SANE spec: + * This function must be called to terminate use of a backend. The + * function will first close all device handles that still might be + * open (it is recommended to close device handles explicitly through + * a call to sane_close(), but backends are required to release all + * resources upon a call to this function). After this function + * returns, no function other than sane_init() may be called + * (regardless of the status value returned by sane_exit(). Neglecting + * to call this function may result in some resources not being + * released properly. + */ +void +sane_exit (void) +{ + struct P5_Session *session, *next; + struct P5_Device *dev, *nextdev; + int i; + + DBG (DBG_proc, "sane_exit: start\n"); + init_count--; + + if (init_count > 0) + { + DBG (DBG_info, + "sane_exit: still %d fronteds to leave before effective exit.\n", + init_count); + return; + } + + /* free session structs */ + for (session = sessions; session; session = next) + { + next = session->next; + sane_close ((SANE_Handle *) session); + free (session); + } + sessions = NULL; + + /* free devices structs */ + for (dev = devices; dev; dev = nextdev) + { + nextdev = dev->next; + free (dev->name); + free (dev); + } + devices = NULL; + + /* now list of devices */ + if (devlist) + { + i = 0; + while ((SANE_Device *) devlist[i]) + { + free ((SANE_Device *) devlist[i]); + i++; + } + free (devlist); + devlist = NULL; + } + + DBG (DBG_proc, "sane_exit: exit\n"); +} + + +/** @brief probe for all supported devices + * This functions tries to probe if any of the supported devices of + * the backend is present. Each detected device will be added to the + * 'devices' list + */ +static SANE_Status +probe_p5_devices (void) +{ + /**> configuration structure used during attach */ + SANEI_Config config; + /**> list of configuration options */ + SANE_Option_Descriptor *cfg_options[NUM_CFG_OPTIONS]; + /**> placeholders pointers for option values */ + void *values[NUM_CFG_OPTIONS]; + int i; + SANE_Status status; + + DBG (DBG_proc, "probe_p5_devices: start\n"); + + /* initialize configuration options */ + cfg_options[CFG_MODEL_NAME] = + (SANE_Option_Descriptor *) malloc (sizeof (SANE_Option_Descriptor)); + cfg_options[CFG_MODEL_NAME]->name = "modelname"; + cfg_options[CFG_MODEL_NAME]->desc = "user provided scanner's model name"; + cfg_options[CFG_MODEL_NAME]->type = SANE_TYPE_INT; + cfg_options[CFG_MODEL_NAME]->unit = SANE_UNIT_NONE; + cfg_options[CFG_MODEL_NAME]->size = sizeof (SANE_Word); + cfg_options[CFG_MODEL_NAME]->cap = SANE_CAP_SOFT_SELECT; + cfg_options[CFG_MODEL_NAME]->constraint_type = SANE_CONSTRAINT_NONE; + values[CFG_MODEL_NAME] = &p5cfg.modelname; + + /* set configuration options structure */ + config.descriptors = cfg_options; + config.values = values; + config.count = NUM_CFG_OPTIONS; + + /* generic configure and attach function */ + status = sanei_configure_attach (P5_CONFIG_FILE, &config, config_attach); + /* free allocated options */ + for (i = 0; i < NUM_CFG_OPTIONS; i++) + { + free (cfg_options[i]); + } + + DBG (DBG_proc, "probe_p5_devices: end\n"); + return status; +} + +/** This function is called by sanei_configure_attach to try + * to attach the backend to a device specified by the configuration file. + * + * @param config configuration structure filled with values read + * from configuration file + * @param devname name of the device to try to attach to, it is + * the unprocessed line of the configuration file + * + * @return status SANE_STATUS_GOOD if no errors (even if no matching + * devices found) + * SANE_STATUS_INVAL in case of error + */ +static SANE_Status +config_attach (SANEI_Config * config, const char *devname) +{ + /* currently, the config is a global variable so config is useless here */ + /* the correct thing would be to have a generic sanei_attach_matching_devices + * using an attach function with a config parameter */ + config = config; + + /* the devname has been processed and is ready to be used + * directly. The config struct contains all the configuration data for + * the corresponding device. Since there is no ressources common to each + * backends regarding parallel port, we can directly call the attach + * function. */ + attach_p5 (devname, config); + + return SANE_STATUS_GOOD; +} + +/** @brief try to attach to a device by its name + * The attach tries to open the given device and match it + * with devices handled by the backend. The configuration parameter + * contains the values of the already parsed configuration options + * from the conf file. + * @param config configuration structure filled with values read + * from configuration file + * @param devicename name of the device to try to attach to, it is + * the unprocessed line of the configuration file + * + * @return status SANE_STATUS_GOOD if no errors (even if no matching + * devices found) + * SANE_STATUS_NOM_MEM if there isn't enough memory to allocate the + * device structure + * SANE_STATUS_UNSUPPORTED if the device if unknown by the backend + * SANE_STATUS_INVAL in case of other error + */ +static SANE_Status +attach_p5 (const char *devicename, SANEI_Config * config) +{ + struct P5_Device *device; + struct P5_Model *model; + + DBG (DBG_proc, "attach(%s): start\n", devicename); + + /* search if we already have it attached */ + for (device = devices; device; device = device->next) + { + if (strcmp (device->name, devicename) == 0) + { + DBG (DBG_info, "attach: device already attached\n"); + DBG (DBG_proc, "attach: exit\n"); + return SANE_STATUS_GOOD; + } + } + + /** + * do physical probe of the device here. In case the device is recognized, + * we allocate a device struct and give it options and model. + * Else we return SANE_STATUS_UNSUPPORTED. + */ + model = probe (devicename); + if (model == NULL) + { + DBG (DBG_info, + "attach: device %s is not managed by the backend\n", devicename); + DBG (DBG_proc, "attach: exit\n"); + return SANE_STATUS_UNSUPPORTED; + } + + /* allocate device struct */ + device = malloc (sizeof (*device)); + if (device == NULL) + { + return SANE_STATUS_NO_MEM; + DBG (DBG_proc, "attach: exit\n"); + } + memset (device, 0, sizeof (*device)); + device->model = model; + + /* name of the device */ + device->name = strdup (devicename); + + DBG (DBG_info, "attach: found %s %s %s at %s\n", + device->model->vendor, device->model->product, device->model->type, + device->name); + + /* we insert new device at start of the chained list */ + /* head of the list becomes the next, and start is replaced */ + /* with the new session struct */ + device->next = devices; + devices = device; + + /* intialization is done at sane_open */ + device->initialized = SANE_FALSE; + device->calibrated = SANE_FALSE; + + DBG (DBG_proc, "attach: exit\n"); + return SANE_STATUS_GOOD; +} + + +/** @brief set initial value for the scanning options + * for each sessions, control options are initalized based on the capability + * of the model of the physical device. + * @param session scanner session to initialize options + * @return SANE_STATUS_GOOD on success + */ +static SANE_Status +init_options (struct P5_Session *session) +{ + SANE_Int option, i, min, idx; + SANE_Word *dpi_list; + P5_Model *model = session->dev->model; + + DBG (DBG_proc, "init_options: start\n"); + + /* we first initialize each options with a default value */ + memset (session->options, 0, sizeof (session->options[OPT_NUM_OPTS])); + for (option = 0; option < NUM_OPTIONS; option++) + { + session->options[option].descriptor.size = sizeof (SANE_Word); + session->options[option].descriptor.cap = + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + + /* we set up all the options listed in the P5_Option enum */ + + /* last option / end of list marker */ + session->options[OPT_NUM_OPTS].descriptor.name = SANE_NAME_NUM_OPTIONS; + session->options[OPT_NUM_OPTS].descriptor.title = SANE_TITLE_NUM_OPTIONS; + session->options[OPT_NUM_OPTS].descriptor.desc = SANE_DESC_NUM_OPTIONS; + session->options[OPT_NUM_OPTS].descriptor.type = SANE_TYPE_INT; + session->options[OPT_NUM_OPTS].descriptor.cap = SANE_CAP_SOFT_DETECT; + session->options[OPT_NUM_OPTS].value.w = NUM_OPTIONS; + + /* "Standard" group: */ + session->options[OPT_STANDARD_GROUP].descriptor.title = SANE_TITLE_STANDARD; + session->options[OPT_STANDARD_GROUP].descriptor.name = SANE_NAME_STANDARD; + session->options[OPT_STANDARD_GROUP].descriptor.desc = SANE_DESC_STANDARD; + session->options[OPT_STANDARD_GROUP].descriptor.type = SANE_TYPE_GROUP; + session->options[OPT_STANDARD_GROUP].descriptor.size = 0; + session->options[OPT_STANDARD_GROUP].descriptor.cap = 0; + session->options[OPT_STANDARD_GROUP].descriptor.constraint_type = + SANE_CONSTRAINT_NONE; + + /* scan mode */ + session->options[OPT_MODE].descriptor.name = SANE_NAME_SCAN_MODE; + session->options[OPT_MODE].descriptor.title = SANE_TITLE_SCAN_MODE; + session->options[OPT_MODE].descriptor.desc = SANE_DESC_SCAN_MODE; + session->options[OPT_MODE].descriptor.type = SANE_TYPE_STRING; + session->options[OPT_MODE].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_MODE].descriptor.constraint_type = + SANE_CONSTRAINT_STRING_LIST; + session->options[OPT_MODE].descriptor.size = max_string_size (mode_list); + session->options[OPT_MODE].descriptor.constraint.string_list = mode_list; + session->options[OPT_MODE].value.s = strdup (mode_list[0]); + + /* preview */ + session->options[OPT_PREVIEW].descriptor.name = SANE_NAME_PREVIEW; + session->options[OPT_PREVIEW].descriptor.title = SANE_TITLE_PREVIEW; + session->options[OPT_PREVIEW].descriptor.desc = SANE_DESC_PREVIEW; + session->options[OPT_PREVIEW].descriptor.type = SANE_TYPE_BOOL; + session->options[OPT_PREVIEW].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_PREVIEW].descriptor.unit = SANE_UNIT_NONE; + session->options[OPT_PREVIEW].descriptor.constraint_type = + SANE_CONSTRAINT_NONE; + session->options[OPT_PREVIEW].value.w = SANE_FALSE; + + /** @brief build resolution list + * We merge xdpi and ydpi list to provide only one resolution option control. + * This is the most common case for backends and fronteds and give 'square' + * pixels. The SANE API allow to control x and y dpi independantly, but this is + * rarely done and may confuse both frontends and users. In case a dpi value exists + * for one but not for the other, the backend will have to crop data so that the + * frontend is unaffected. A common case is that motor resolution (ydpi) is higher + * than sensor resolution (xdpi), so scan lines must be scaled up to keep square + * pixel when doing sane_read(). + * TODO this deserves a dedicated function and some unit testing + */ + + /* find minimum first */ + min = 65535; + for (i = 0; i < MAX_RESOLUTIONS && model->xdpi_values[i] > 0; i++) + { + if (model->xdpi_values[i] < min) + min = model->xdpi_values[i]; + } + for (i = 0; i < MAX_RESOLUTIONS && model->ydpi_values[i] > 0; i++) + { + if (model->ydpi_values[i] < min) + min = model->ydpi_values[i]; + } + + dpi_list = malloc ((MAX_RESOLUTIONS * 2 + 1) * sizeof (SANE_Word)); + if (!dpi_list) + return SANE_STATUS_NO_MEM; + dpi_list[1] = min; + idx = 2; + + /* find any value greater than the last used min and + * less than the max value + */ + do + { + min = 65535; + for (i = 0; i < MAX_RESOLUTIONS && model->xdpi_values[i] > 0; i++) + { + if (model->xdpi_values[i] < min + && model->xdpi_values[i] > dpi_list[idx - 1]) + min = model->xdpi_values[i]; + } + for (i = 0; i < MAX_RESOLUTIONS && model->ydpi_values[i] > 0; i++) + { + if (model->ydpi_values[i] < min + && model->ydpi_values[i] > dpi_list[idx - 1]) + min = model->ydpi_values[i]; + } + if (min < 65535) + { + dpi_list[idx] = min; + idx++; + } + } + while (min != 65535); + dpi_list[idx] = 0; + /* the count of different resolution is put at the beginning */ + dpi_list[0] = idx - 1; + + session->options[OPT_RESOLUTION].descriptor.name = + SANE_NAME_SCAN_RESOLUTION; + session->options[OPT_RESOLUTION].descriptor.title = + SANE_TITLE_SCAN_RESOLUTION; + session->options[OPT_RESOLUTION].descriptor.desc = + SANE_DESC_SCAN_RESOLUTION; + session->options[OPT_RESOLUTION].descriptor.type = SANE_TYPE_INT; + session->options[OPT_RESOLUTION].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_RESOLUTION].descriptor.unit = SANE_UNIT_DPI; + session->options[OPT_RESOLUTION].descriptor.constraint_type = + SANE_CONSTRAINT_WORD_LIST; + session->options[OPT_RESOLUTION].descriptor.constraint.word_list = dpi_list; + + /* initial value is lowest available dpi */ + session->options[OPT_RESOLUTION].value.w = min; + + /* "Geometry" group: */ + session->options[OPT_GEOMETRY_GROUP].descriptor.title = SANE_TITLE_GEOMETRY; + session->options[OPT_GEOMETRY_GROUP].descriptor.name = SANE_NAME_GEOMETRY; + session->options[OPT_GEOMETRY_GROUP].descriptor.desc = SANE_DESC_GEOMETRY; + session->options[OPT_GEOMETRY_GROUP].descriptor.type = SANE_TYPE_GROUP; + session->options[OPT_GEOMETRY_GROUP].descriptor.cap = SANE_CAP_ADVANCED; + session->options[OPT_GEOMETRY_GROUP].descriptor.size = 0; + session->options[OPT_GEOMETRY_GROUP].descriptor.constraint_type = + SANE_CONSTRAINT_NONE; + + /* adapt the constraint range to the detected model */ + x_range.max = model->x_size; + y_range.max = model->y_size; + + /* top-left x */ + session->options[OPT_TL_X].descriptor.name = SANE_NAME_SCAN_TL_X; + session->options[OPT_TL_X].descriptor.title = SANE_TITLE_SCAN_TL_X; + session->options[OPT_TL_X].descriptor.desc = SANE_DESC_SCAN_TL_X; + session->options[OPT_TL_X].descriptor.type = SANE_TYPE_FIXED; + session->options[OPT_TL_X].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_TL_X].descriptor.unit = SANE_UNIT_MM; + session->options[OPT_TL_X].descriptor.constraint_type = + SANE_CONSTRAINT_RANGE; + session->options[OPT_TL_X].descriptor.constraint.range = &x_range; + session->options[OPT_TL_X].value.w = 0; + + /* top-left y */ + session->options[OPT_TL_Y].descriptor.name = SANE_NAME_SCAN_TL_Y; + session->options[OPT_TL_Y].descriptor.title = SANE_TITLE_SCAN_TL_Y; + session->options[OPT_TL_Y].descriptor.desc = SANE_DESC_SCAN_TL_Y; + session->options[OPT_TL_Y].descriptor.type = SANE_TYPE_FIXED; + session->options[OPT_TL_Y].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_TL_Y].descriptor.unit = SANE_UNIT_MM; + session->options[OPT_TL_Y].descriptor.constraint_type = + SANE_CONSTRAINT_RANGE; + session->options[OPT_TL_Y].descriptor.constraint.range = &y_range; + session->options[OPT_TL_Y].value.w = 0; + + /* bottom-right x */ + session->options[OPT_BR_X].descriptor.name = SANE_NAME_SCAN_BR_X; + session->options[OPT_BR_X].descriptor.title = SANE_TITLE_SCAN_BR_X; + session->options[OPT_BR_X].descriptor.desc = SANE_DESC_SCAN_BR_X; + session->options[OPT_BR_X].descriptor.type = SANE_TYPE_FIXED; + session->options[OPT_BR_X].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_BR_X].descriptor.unit = SANE_UNIT_MM; + session->options[OPT_BR_X].descriptor.constraint_type = + SANE_CONSTRAINT_RANGE; + session->options[OPT_BR_X].descriptor.constraint.range = &x_range; + session->options[OPT_BR_X].value.w = x_range.max; + + /* bottom-right y */ + session->options[OPT_BR_Y].descriptor.name = SANE_NAME_SCAN_BR_Y; + session->options[OPT_BR_Y].descriptor.title = SANE_TITLE_SCAN_BR_Y; + session->options[OPT_BR_Y].descriptor.desc = SANE_DESC_SCAN_BR_Y; + session->options[OPT_BR_Y].descriptor.type = SANE_TYPE_FIXED; + session->options[OPT_BR_Y].descriptor.cap |= SANE_CAP_AUTOMATIC; + session->options[OPT_BR_Y].descriptor.unit = SANE_UNIT_MM; + session->options[OPT_BR_Y].descriptor.constraint_type = + SANE_CONSTRAINT_RANGE; + session->options[OPT_BR_Y].descriptor.constraint.range = &y_range; + session->options[OPT_BR_Y].value.w = y_range.max; + + /* sensor group */ + session->options[OPT_SENSOR_GROUP].descriptor.name = SANE_NAME_SENSORS; + session->options[OPT_SENSOR_GROUP].descriptor.title = SANE_TITLE_SENSORS; + session->options[OPT_SENSOR_GROUP].descriptor.desc = SANE_DESC_SENSORS; + session->options[OPT_SENSOR_GROUP].descriptor.type = SANE_TYPE_GROUP; + session->options[OPT_SENSOR_GROUP].descriptor.constraint_type = + SANE_CONSTRAINT_NONE; + + /* page loaded sensor */ + session->options[OPT_PAGE_LOADED_SW].descriptor.name = + SANE_NAME_PAGE_LOADED; + session->options[OPT_PAGE_LOADED_SW].descriptor.title = + SANE_TITLE_PAGE_LOADED; + session->options[OPT_PAGE_LOADED_SW].descriptor.desc = + SANE_DESC_PAGE_LOADED; + session->options[OPT_PAGE_LOADED_SW].descriptor.type = SANE_TYPE_BOOL; + session->options[OPT_PAGE_LOADED_SW].descriptor.unit = SANE_UNIT_NONE; + session->options[OPT_PAGE_LOADED_SW].descriptor.cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + session->options[OPT_PAGE_LOADED_SW].value.b = 0; + + /* calibration needed */ + session->options[OPT_NEED_CALIBRATION_SW].descriptor.name = + "need-calibration"; + session->options[OPT_NEED_CALIBRATION_SW].descriptor.title = + SANE_I18N ("Need calibration"); + session->options[OPT_NEED_CALIBRATION_SW].descriptor.desc = + SANE_I18N ("The scanner needs calibration for the current settings"); + session->options[OPT_NEED_CALIBRATION_SW].descriptor.type = SANE_TYPE_BOOL; + session->options[OPT_NEED_CALIBRATION_SW].descriptor.unit = SANE_UNIT_NONE; + session->options[OPT_NEED_CALIBRATION_SW].descriptor.cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + session->options[OPT_NEED_CALIBRATION_SW].value.b = 0; + + /* button group */ + session->options[OPT_BUTTON_GROUP].descriptor.name = "Buttons"; + session->options[OPT_BUTTON_GROUP].descriptor.title = SANE_I18N ("Buttons"); + session->options[OPT_BUTTON_GROUP].descriptor.desc = SANE_I18N ("Buttons"); + session->options[OPT_BUTTON_GROUP].descriptor.type = SANE_TYPE_GROUP; + session->options[OPT_BUTTON_GROUP].descriptor.constraint_type = + SANE_CONSTRAINT_NONE; + + /* calibrate button */ + session->options[OPT_CALIBRATE].descriptor.name = "calibrate"; + session->options[OPT_CALIBRATE].descriptor.title = SANE_I18N ("Calibrate"); + session->options[OPT_CALIBRATE].descriptor.desc = + SANE_I18N ("Start calibration using special sheet"); + session->options[OPT_CALIBRATE].descriptor.type = SANE_TYPE_BUTTON; + session->options[OPT_CALIBRATE].descriptor.unit = SANE_UNIT_NONE; + session->options[OPT_CALIBRATE].descriptor.cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED | + SANE_CAP_AUTOMATIC; + session->options[OPT_CALIBRATE].value.b = 0; + + /* clear calibration cache button */ + session->options[OPT_CLEAR_CALIBRATION].descriptor.name = "clear"; + session->options[OPT_CLEAR_CALIBRATION].descriptor.title = + SANE_I18N ("Clear calibration"); + session->options[OPT_CLEAR_CALIBRATION].descriptor.desc = + SANE_I18N ("Clear calibration cache"); + session->options[OPT_CLEAR_CALIBRATION].descriptor.type = SANE_TYPE_BUTTON; + session->options[OPT_CLEAR_CALIBRATION].descriptor.unit = SANE_UNIT_NONE; + session->options[OPT_CLEAR_CALIBRATION].descriptor.cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED | + SANE_CAP_AUTOMATIC; + session->options[OPT_CLEAR_CALIBRATION].value.b = 0; + + /* until work on calibration isfinished */ + DISABLE (OPT_CALIBRATE); + DISABLE (OPT_CLEAR_CALIBRATION); + + DBG (DBG_proc, "init_options: exit\n"); + return SANE_STATUS_GOOD; +} + +/** @brief physical probe of a device + * This function probes for a scanning device using the given name. If the + * device is managed, a model structure describing the device will be returned. + * @param devicename low level device to access to probe hardware + * @return NULL is the device is unsupported, or a model struct describing the + * device. + */ +P5_Model * +probe (const char *devicename) +{ + int fd; + + /* open parallel port device */ + fd = open_pp (devicename); + if (fd < 0) + { + DBG (DBG_error, "probe: failed to open '%s' device!\n", devicename); + return NULL; + } + + /* now try to connect to scanner */ + if (connect (fd) != SANE_TRUE) + { + DBG (DBG_error, "probe: failed to connect!\n"); + close_pp (fd); + return NULL; + } + + /* set up for memory test */ + write_reg (fd, REG1, 0x00); + write_reg (fd, REG7, 0x00); + write_reg (fd, REG0, 0x00); + write_reg (fd, REG1, 0x00); + write_reg (fd, REGF, 0x80); + if (memtest (fd, 0x0100) != SANE_TRUE) + { + disconnect (fd); + close_pp (fd); + DBG (DBG_error, "probe: memory test failed!\n"); + return NULL; + } + else + { + DBG (DBG_info, "memtest() OK...\n"); + } + write_reg (fd, REG7, 0x00); + + /* check for document presence 0xC6: present, 0xC3 no document */ + test_document (fd); + + /* release device nd parport for next uses */ + disconnect (fd); + close_pp (fd); + + /* for there is only one supported model, so we use hardcoded values */ + DBG (DBG_proc, "probe: exit\n"); + return &pagepartner_model; +} + + +/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */ diff --git a/backend/p5.conf.in b/backend/p5.conf.in new file mode 100644 index 000000000..913085633 --- /dev/null +++ b/backend/p5.conf.in @@ -0,0 +1,10 @@ +# configuration file for the p5 backend. + +# configuration option to override detected model name +#option modelname "Prima PagePartner" + +# when the parser find this line, it detects it is not an option, +# then it calls the attach function with this value. +# Currently only user mode parallel port support is possible: +# auto, /dev/paraport* (ppdev device name) +auto diff --git a/backend/p5.h b/backend/p5.h new file mode 100644 index 000000000..0efa8303a --- /dev/null +++ b/backend/p5.h @@ -0,0 +1,206 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2009 stef.dev@free.fr + + 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. + +*/ + +/** @file p5.h + * @brief Declaration of high level structures used by the p5 backend. + * + * The structures and functions declared here are used to do the deal with + * the SANE API. + */ + + +#ifndef P5_H +#define P5_H + +#include "../include/sane/config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_LIBC_H +# include +#endif + +#include "../include/sane/sane.h" +#include "../include/sane/saneopts.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/sanei_backend.h" + +/**< macro to enable an option */ +#define ENABLE(OPTION) session->options[OPTION].cap &= ~SANE_CAP_INACTIVE + +/**< macro to disable an option */ +#define DISABLE(OPTION) session->options[OPTION].cap |= SANE_CAP_INACTIVE + +/** macro to test is an option is active */ +#define IS_ACTIVE(OPTION) (((s->opt[OPTION].cap) & SANE_CAP_INACTIVE) == 0) + +/**< name of the configuration file */ +#define P5_CONFIG_FILE "p5.conf" + +/**< macro to define texts that should translated */ +#ifndef SANE_I18N +#define SANE_I18N(text) text +#endif + +/** color mode names + */ +/* @{ */ +#define COLOR_MODE "Color" +#define GRAY_MODE "Gray" +#define LINEART_MODE "Lineart" +/* @} */ + +#include "p5_device.h" + +/** + * List of all SANE options available for the frontend. Given a specific + * device, some options may be set to inactive when the scanner model is + * detected. The default values and the ranges they belong maybe also model + * dependent. + */ +enum P5_Options +{ + OPT_NUM_OPTS = 0, /** first enum which must be zero */ + /** @name standard options group + */ + /* @{ */ + OPT_STANDARD_GROUP, + OPT_MODE, /** set the mode: color, grey levels or lineart */ + OPT_PREVIEW, /** set up for preview */ + OPT_RESOLUTION, /** set scan's resolution */ + /* @} */ + + /** @name geometry group + * geometry related options + */ + /* @{ */ + OPT_GEOMETRY_GROUP, /** group of options defining the position and size of the scanned area */ + OPT_TL_X, /** top-left x of the scanned area*/ + OPT_TL_Y, /** top-left y of the scanned area*/ + OPT_BR_X, /** bottom-right x of the scanned area*/ + OPT_BR_Y, /** bottom-right y of the scanned area*/ + /* @} */ + + /** @name sensor group + * detectors group + */ + /* @{ */ + OPT_SENSOR_GROUP, + OPT_PAGE_LOADED_SW, + OPT_NEED_CALIBRATION_SW, + /* @} */ + + /** @name button group + * buttons group + */ + /* @{ */ + OPT_BUTTON_GROUP, + OPT_CALIBRATE, + OPT_CLEAR_CALIBRATION, + /* @} */ + + /** @name option list terminator + * must come last so it can be used for array and list size + */ + NUM_OPTIONS +}; + +/** + * Contains one SANE option description and its value. + */ +typedef struct P5_Option +{ + SANE_Option_Descriptor descriptor; /** option description */ + Option_Value value; /** option value */ +} P5_Option; + +/** + * Frontend session. This struct holds informations usefull for + * the functions defined in SANE's standard. Informations closer + * to the hardware are in the P5_Device structure. There is + * as many session structure than frontends using the backend. + */ +typedef struct P5_Session +{ + /** + * Point to the next session in a linked list + */ + struct P5_Session *next; + + /** + * low-level device object used by the session + */ + P5_Device *dev; + + /** + * array of possible options and their values for the backend + */ + P5_Option options[NUM_OPTIONS]; + + /** + * SANE_True if a scan is in progress, ie sane_start has been called. + * Stay SANE_True until sane_cancel() is called. + */ + SANE_Bool scanning; + + /** @brief non blocking flag + * SANE_TRUE if sane_read are non-blocking, ie returns immediatly if there + * is no data available from the scanning device. Modified by sane_set_io_mode() + */ + SANE_Bool non_blocking; + + /** + * SANE Parameters describes what the next or current scan will be + * according to the current values of the options + */ + SANE_Parameters params; + + /** + * bytes to send to frontend for the scan + */ + SANE_Int to_send; + + /** + * bytes currently sent to frontend during the scan + */ + SANE_Int sent; + +} P5_Session; + + +static SANE_Status probe_p5_devices (void); +static P5_Model *probe (const char *devicename); +static SANE_Status config_attach (SANEI_Config * config, const char *devname); +static SANE_Status attach_p5 (const char *name, SANEI_Config * config); +static SANE_Status init_options (struct P5_Session *session); +static SANE_Status compute_parameters (struct P5_Session *session); + +#endif /* not P5_H */ diff --git a/backend/p5_device.c b/backend/p5_device.c new file mode 100644 index 000000000..3b734c3a7 --- /dev/null +++ b/backend/p5_device.c @@ -0,0 +1,1596 @@ +/** + * Description of the Primax PagePartner model + */ +static P5_Model pagepartner_model = { + "Primax PagePartner", + "Primax", + "PagePartner", + SANE_I18N ("sheetfed scanner"), + + {300, 200, 150, 100, 0}, + /* 500 seems also possible */ + {600, 400, 300, 200, 150, 100, 0}, + + 300, + 600, + 100, + 100, + 16, + + SANE_FIX (0.0), + SANE_FIX (0.0), + SANE_FIX (215.9), + SANE_FIX (300.0), +}; + +static char * +addr_name (u_int16_t addr) +{ + switch (addr) + { + case DATA: + return "DATA"; + break; + case STATUS: + return "STATUS"; + break; + case CONTROL: + return "CONTROL"; + break; + case EPPADR: + return "EPPADR"; + break; + case EPPDATA: + return "EPPDATA"; + break; + default: + return "*ERROR*"; + } +} + +/** @brief low level hardware access functions + * @{ + */ + +static u_int8_t +inb (int fd, u_int16_t addr) +{ +#ifdef HAVE_LINUX_PPDEV_H + u_int8_t val = 0xff; + int rc, mode = 0xff; + + switch (addr) + { + case DATA: + rc = ioctl (fd, PPRDATA, &val); + break; + case STATUS: + rc = ioctl (fd, PPRSTATUS, &val); + break; + case CONTROL: + rc = ioctl (fd, PPRCONTROL, &val); + break; + case EPPDATA: + mode = 1; /* data_reverse */ + rc = ioctl (fd, PPDATADIR, &mode); + mode = IEEE1284_MODE_EPP | IEEE1284_DATA; + rc = ioctl (fd, PPSETMODE, &mode); +#ifdef PPSETFLAGS + mode = PP_FASTREAD; + rc = ioctl (fd, PPSETFLAGS, &mode); +#endif + rc = read (fd, &val, 1); + break; + default: + DBG (DBG_error, "inb(%s) escaped ppdev\n", addr_name (addr)); + return 0xFF; + } + if (rc < 0) + { + DBG (DBG_error, "ppdev ioctl returned <%s>\n", strerror (errno)); + } + return val; +#else + return 0; +#endif +} + +static void +outb (int fd, u_int16_t addr, u_int8_t value) +{ +#ifdef HAVE_LINUX_PPDEV_H + int rc = 0, mode = 0xff; + + switch (addr) + { + case DATA: + rc = ioctl (fd, PPWDATA, &value); + break; + case CONTROL: + mode = value & 0x20; + rc = ioctl (fd, PPDATADIR, &mode); + if (!rc) + { + value = value & 0xDF; + rc = ioctl (fd, PPWCONTROL, &value); + } + break; + case EPPDATA: + mode = 0; /* data forward */ + rc = ioctl (fd, PPDATADIR, &mode); + mode = IEEE1284_MODE_EPP | IEEE1284_DATA; + rc = ioctl (fd, PPSETMODE, &mode); + rc = write (fd, &value, 1); + break; + case EPPADR: + mode = 0; /* data forward */ + rc = ioctl (fd, PPDATADIR, &mode); + mode = IEEE1284_MODE_EPP | IEEE1284_ADDR; + rc = ioctl (fd, PPSETMODE, &mode); + rc = write (fd, &value, 1); + break; + default: + DBG (DBG_error, "outb(%s,0x%02x) escaped ppdev\n", addr_name (addr), + value); + break; + } + if (rc < 0) + { + DBG (DBG_error, "ppdev ioctl returned <%s>\n", strerror (errno)); + } +#endif /* HAVE_LINUX_PPDEV_H */ +} + +static void +write_reg (int fd, u_int8_t index, u_int8_t value) +{ + u_int8_t idx; + + /* both nibbles hold the same value */ + idx = index & 0x0F; + DBG (DBG_io2, "write_reg(REG%X,0x%x)\n", idx, value); + idx = idx << 4 | idx; + outb (fd, EPPADR, idx); + outb (fd, EPPDATA, value); +} + +static u_int8_t +read_reg (int fd, u_int8_t index) +{ + u_int8_t idx; + + /* both nibbles hold the same value */ + idx = index & 0x0F; + idx = idx << 4 | idx; + outb (fd, EPPADR, idx); + return inb (fd, EPPDATA); +} + +#ifdef HAVE_LINUX_PPDEV_H +static int +read_data (int fd, u_int8_t * data, int length) +{ + int mode, rc, nb; + unsigned char bval; + + bval = REG8; + mode = IEEE1284_MODE_EPP | IEEE1284_ADDR; + rc = ioctl (fd, PPSETMODE, &mode); + rc = write (fd, &bval, 1); + + mode = 1; /* data_reverse */ + rc = ioctl (fd, PPDATADIR, &mode); +#ifdef PPSETFLAGS + mode = PP_FASTREAD; + rc = ioctl (fd, PPSETFLAGS, &mode); +#endif + mode = IEEE1284_MODE_EPP | IEEE1284_DATA; + rc = ioctl (fd, PPSETMODE, &mode); + nb = 0; + while (nb < length) + { + rc = read (fd, data + nb, length - nb); + if (rc < 0) + { + DBG (DBG_error, "memtest: error reading data back!\n"); + return 0; + } + else + { + nb += rc; + } + } + + return 1; +} + +static void +index_write_data (int fd, u_int8_t index, u_int8_t * data, int length) +{ + int mode, rc; + unsigned char bval; + + bval = index; + mode = IEEE1284_MODE_EPP | IEEE1284_ADDR; + rc = ioctl (fd, PPSETMODE, &mode); + rc = write (fd, &bval, 1); + + mode = IEEE1284_MODE_EPP | IEEE1284_DATA; + rc = ioctl (fd, PPSETMODE, &mode); + mode = 0; /* data forward */ + rc = ioctl (fd, PPDATADIR, &mode); + rc = write (fd, data, length); + return; +} + +static void +write_data (int fd, u_int8_t * data, int length) +{ + index_write_data (fd, REG8, data, length); +} + +static void +write_reg2 (int fd, u_int8_t index, u_int16_t value) +{ + u_int8_t data2[2]; + + data2[0] = value & 0xff; + data2[1] = value >> 8; + index_write_data (fd, index, data2, 2); +} +#else + +static int +read_data (int fd, u_int8_t * data, int length) +{ + return -1; +} + +static void +index_write_data (int fd, u_int8_t index, u_int8_t * data, int length) +{ +} + +static void +write_data (int fd, u_int8_t * data, int length) +{ +} + +static void +write_reg2 (int fd, u_int8_t index, u_int16_t value) +{ +} +#endif + +/** + * @} + */ + + +/** @brief This function checks a memory buffer. + * This function writes at the given memory address then read it back + * to check the scanner is correctly working. + * @param fd file descriptor used to access hardware + * @param addr address where to write and read + * @return SANE_TRUE on succes, SANE_FALSE otherwise + */ +static int +memtest (int fd, u_int16_t addr) +{ + u_int8_t sent[256]; + u_int8_t back[256]; + int i; + + write_reg2 (fd, REG1, addr); + for (i = 0; i < 256; i++) + { + sent[i] = (u_int8_t) i; + back[i] = 0; + } + write_data (fd, sent, 256); + read_data (fd, back, 256); + + /* check if data read back is the same that the one sent */ + for (i = 0; i < 256; i++) + { + if (back[i] != sent[i]) + { + return SANE_FALSE; + } + } + + return SANE_TRUE; +} + + +#define INB(k,y,z) val=inb(k,y); if(val!=z) { DBG(DBG_error,"expected 0x%02x, got 0x%02x\n",z, val); return SANE_FALSE; } + +/** @brief connect to scanner + * This function sends the connect sequence for the scanner. + * @param fd filedescriptor of the parallel port communication channel + * @return SANE_TRUE in case of success, SANE_FALSE otherwise + */ +static int +connect (int fd) +{ + u_int8_t val; + + inb (fd, CONTROL); + outb (fd, CONTROL, 0x04); + outb (fd, DATA, 0x02); + INB (fd, DATA, 0x02); + outb (fd, DATA, 0x03); + INB (fd, DATA, 0x03); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + outb (fd, DATA, 0x03); + outb (fd, DATA, 0x83); + INB (fd, DATA, 0x83); + outb (fd, DATA, 0x82); + INB (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + outb (fd, DATA, 0x02); + outb (fd, DATA, 0x82); + outb (fd, DATA, 0xFF); + DBG (DBG_info, "connect() OK...\n"); + return SANE_TRUE; +} + +static int +disconnect (int fd) +{ + u_int8_t val; + + outb (fd, CONTROL, 0x04); + outb (fd, DATA, 0x00); + INB (fd, DATA, 0x00); + outb (fd, DATA, 0x01); + INB (fd, DATA, 0x01); + outb (fd, DATA, 0x01); + outb (fd, DATA, 0x81); + outb (fd, DATA, 0x01); + outb (fd, DATA, 0x81); + INB (fd, DATA, 0x81); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x81); + INB (fd, DATA, 0x81); + outb (fd, DATA, 0x01); + outb (fd, DATA, 0x81); + outb (fd, DATA, 0x01); + outb (fd, DATA, 0x81); + INB (fd, DATA, 0x81); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + INB (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + outb (fd, DATA, 0x00); + outb (fd, DATA, 0x80); + inb (fd, CONTROL); + outb (fd, CONTROL, 0x0C); + + return SANE_STATUS_GOOD; +} + +static void +setadresses (int fd, u_int16_t start, u_int16_t end) +{ + write_reg (fd, REG3, start & 0xff); + write_reg (fd, REG4, start >> 8); + write_reg (fd, REG5, end & 0xff); + write_reg (fd, REG6, end >> 8); + DBG (DBG_io, "setadresses(0x%x,0x%x); OK...\n", start, end); +} + +#ifdef HAVE_LINUX_PPDEV_H +/** @brief open parallel port device + * opens parallel port's low level device in EPP mode + * @param devicename nam of the real device or the special value 'auto' + * @return file descriptor in cas of successn -1 otherwise + */ +static int +open_pp (char *devicename) +{ + int fd, rc, mode = 0; + char *name; + + DBG (DBG_proc, "open_pp: start, devicename=%s\n", devicename); + /* TODO improve auto device finding */ + if (strncmp (devicename, "auto", 4) == 0) + { + name = "/dev/parport0"; + } + else + { + name = devicename; + } + + /* open device */ + fd = open (name, O_RDWR); + if (fd < 0) + { + switch (errno) + { + case ENOENT: +#ifdef ENIO + case ENXIO: +#endif +#ifdef ENODEV + case ENODEV: +#endif + DBG (DBG_error, "open_pp: no %s device ...\n", name); + break; + case EACCES: + DBG (DBG_error, + "open_pp: current user cannot use existing %s device ...\n", + name); + break; + default: + DBG (DBG_error, "open_pp: %s while opening %s\n", strerror (errno), + name); + } + return -1; + } + + /* claim device and set it to EPP */ + rc = ioctl (fd, PPCLAIM); + rc = ioctl (fd, PPGETMODES, &mode); + if (mode & PARPORT_MODE_PCSPP) + DBG (DBG_io, "PARPORT_MODE_PCSPP\n"); + if (mode & PARPORT_MODE_TRISTATE) + DBG (DBG_io, "PARPORT_MODE_TRISTATE\n"); + if (mode & PARPORT_MODE_EPP) + DBG (DBG_io, "PARPORT_MODE_EPP\n"); + if (mode & PARPORT_MODE_ECP) + DBG (DBG_io, "PARPORT_MODE_ECP\n"); + if (mode & PARPORT_MODE_COMPAT) + DBG (DBG_io, "PARPORT_MODE_COMPAT\n"); + if (mode & PARPORT_MODE_DMA) + DBG (DBG_io, "PARPORT_MODE_DMA\n"); + if (mode & PARPORT_MODE_EPP) + { + mode = IEEE1284_MODE_EPP; + } + else + { + /* + if (mode & PARPORT_MODE_ECP) + { + mode = IEEE1284_MODE_ECP; + } + else + */ + { + mode = -1; + } + } + if (mode == -1) + { + DBG (DBG_error, "open_pp: no EPP mode, giving up ...\n"); + rc = ioctl (fd, PPRELEASE); + close (fd); + return -1; + } + rc = ioctl (fd, PPNEGOT, &mode); + rc = ioctl (fd, PPSETMODE, &mode); + DBG (DBG_proc, "open_pp: exit\n"); + return fd; +} + +/** close low level device + * release and close low level hardware device + */ +static void +close_pp (int fd) +{ + int mode = IEEE1284_MODE_COMPAT; + + if (fd > 2) + { + ioctl (fd, PPNEGOT, &mode); + ioctl (fd, PPRELEASE); + close (fd); + } +} + +#else /* HAVE_LINUX_PPDEV_H */ + +static int +open_pp (char *devicename) +{ + return -1; +} + +static void +close_pp (int fd) +{ +} +#endif /* HAVE_LINUX_PPDEV_H */ + +/** @brief test if a document is inserted + * Test if a document is inserted by reading register E + * @param fd file descriptor to access scanner + * @return SANE_STATUS_NO_DOCS if no document or SANE_STATUS_GOOD + * if something is present. + */ +static SANE_Status +test_document (int fd) +{ + int detector; + + /* check for document presence 0xC6: present, 0xC3 no document */ + detector = read_reg (fd, REGE); + DBG (DBG_io, "test_document: detector=0x%02X\n", detector); + + /* document inserted */ + if (detector & 0x04) + return SANE_STATUS_GOOD; + + return SANE_STATUS_NO_DOCS; +} + +/** + * return the amount of scanned data available + * @param fd file descriptor to access scanner + * @return avaible byte number + */ +static int +available_bytes (int fd) +{ + int counter; + + /* read the number of 256 bytes block of scanned data */ + counter = read_reg (fd, REG9); + DBG (DBG_io, "available_bytes: available_bytes=0x%02X\n", counter); + return 256 * counter; +} + +static SANE_Status +build_correction (P5_Device * dev, unsigned int dpi, unsigned int mode, + unsigned int start, unsigned int width) +{ + unsigned int i, j, shift, step; + + DBG (DBG_proc, "build_correction: start=%d, width=%d\n", start, width); + DBG (DBG_trace, "build_correction: dpi=%d, mode=%d\n", dpi, mode); + + /* loop on calibration data to find the matching one */ + j = 0; + while (dev->calibration_data[j]->dpi != dpi) + { + j++; + if (j > MAX_RESOLUTIONS) + { + DBG (DBG_error, "build_correction: couldn't find calibration!\n"); + return SANE_STATUS_INVAL; + } + } + + if (dev->gain != NULL) + { + free (dev->gain); + dev->gain = NULL; + } + if (dev->offset != NULL) + { + free (dev->offset); + dev->offset = NULL; + } + dev->gain = (float *) malloc (width * sizeof (float)); + if (dev->gain == NULL) + { + DBG (DBG_error, + "build_correction: failed to allocate memory for gain!\n"); + return SANE_STATUS_NO_MEM; + } + dev->offset = (u_int8_t *) malloc (width); + if (dev->offset == NULL) + { + DBG (DBG_error, + "build_correction: failed to allocate memory for offset!\n"); + return SANE_STATUS_NO_MEM; + } + + /* compute starting point of calibration data to use */ + shift = start; + step = 1; + if (mode == MODE_GRAY) + { + /* we use green data */ + shift += 1; + step = 3; + } + for (i = 0; i < width; i += step) + { + if (dev->calibration_data[j]->white_data[shift + i] - + dev->calibration_data[0]->black_data[shift + i] > BLACK_LEVEL) + { + dev->gain[i] = + WHITE_TARGET / + ((float) + (dev->calibration_data[j]->white_data[shift + i] - + dev->calibration_data[j]->black_data[shift + i])); + dev->offset[i] = dev->calibration_data[j]->black_data[shift + i]; + } + else + { + dev->gain[i] = 1.0; + dev->offset[i] = 0; + } + } + return SANE_STATUS_GOOD; + DBG (DBG_proc, "build_correction: end\n"); +} + +/** @brief start up a real scan + * This function starts the scan with the given parameters. + * @param dev device describing hardware + * @param mode color, gray level or lineart. + * @param dpi desired scan resolution. + * @param startx coordinate of the first pixel to scan in + * scan's resolution coordinate + * @param width width of the scanned area + * scanner's physical scan aread. + * @return SANE_STATUS_GOOD if scan is successfully started + */ +static SANE_Status +start_scan (P5_Device * dev, int mode, unsigned int dpi, unsigned int startx, + unsigned int width) +{ + u_int8_t reg0; + u_int8_t reg2; + u_int8_t regF; + u_int16_t addr; + u_int16_t start, end; + unsigned int xdpi; + + DBG (DBG_proc, "start_scan: start \n"); + DBG (DBG_io, "start_scan: startx=%d, width=%d, dpi=%d\n", startx, width, + dpi); + + /** @brief register values + * - reg2 : reg2 seems related to x dpi and provides only 100, + * 150, 200 and 300 resolutions. + * - regF : lower nibble gives y dpi resolution ranging from 150 + * to 1200 dpi. + */ + xdpi = dpi; + switch (dpi) + { + case 100: + reg2 = 0x90; + regF = 0xA2; + break; + case 150: + reg2 = 0x10; + regF = 0xA4; + break; + case 200: + reg2 = 0x80; + regF = 0xA6; + break; + case 300: + reg2 = 0x00; + regF = 0xA8; + break; + case 400: + reg2 = 0x80; /* xdpi=200 */ + regF = 0xAA; + xdpi = 200; + break; + case 500: + reg2 = 0x00; + regF = 0xAC; + xdpi = 300; + break; + case 600: + reg2 = 0x00; + regF = 0xAE; + xdpi = 300; + break; + } + + switch (mode) + { + case MODE_COLOR: + reg0 = 0x00; + addr = 0x0100; + break; + case MODE_GRAY: + /* green channel only */ + reg0 = 0x20; + addr = 0x0100; + break; + case MODE_LINEART: + reg0 = 0x40; + addr = 0x0908; + break; + } + + write_reg (dev->fd, REG1, 0x01); + write_reg (dev->fd, REG7, 0x00); + write_reg (dev->fd, REG0, reg0); + write_reg (dev->fd, REG1, 0x00); + write_reg (dev->fd, REGF, regF); + /* the memory addr used to test need not to be related + * to resolution, 0x0100 could be always used */ + /* TODO get rid of it */ + memtest (dev->fd, addr); + + /* handle case where dpi>xdpi */ + start = startx; + if (dpi > xdpi) + { + width = (width * xdpi) / dpi; + start = (startx * xdpi) / dpi; + } + + /* compute and set start addr */ + if (mode == MODE_COLOR) + { + start = start * 3; + width = width * 3; + } + end = start + width + 1; + + /* build calibration data for the scan */ + if (dev->calibrated) + { + build_correction (dev, xdpi, mode, start, width); + } + + setadresses (dev->fd, start, end); + + write_reg (dev->fd, REG1, addr >> 8); + write_reg (dev->fd, REG2, reg2); + regF = (regF & 0x0F) | 0x80; + write_reg (dev->fd, REGF, regF); + write_reg (dev->fd, REG0, reg0); + if (mode == MODE_LINEART) + { + write_reg (dev->fd, 0x07, 0x04); + } + else + { + write_reg (dev->fd, 0x07, 0x00); + } + write_reg (dev->fd, REG1, addr >> 8); + write_reg2 (dev->fd, REG1, addr); + write_reg (dev->fd, REGF, regF | 0x01); + write_reg (dev->fd, REG0, reg0 | 0x0C); + if (mode == MODE_LINEART) + { + write_reg (dev->fd, REG1, 0x19); + } + else + { + write_reg (dev->fd, REG1, 0x11); + } + DBG (DBG_proc, "start_scan: exit\n"); + return SANE_STATUS_GOOD; +} + +/** read a line of scan data + * @param dev device to read + * @param data pointer where to store data + * @param length total bytes to read on one line + * @param ltr total number of lines to read + * @param retry signals that the function must read as much lines it can + * @param x2 tells that lines must be enlarged by a 2 factor + * @param mode COLOR_MODE if color mode + * @returns number of data lines read, -1 in case of error + */ +static int +read_line (P5_Device * dev, u_int8_t * data, size_t length, int ltr, + SANE_Bool retry, SANE_Bool x2, int mode, SANE_Bool correction) +{ + u_int8_t counter, read, cnt; + u_int8_t inbuffer[MAX_SENSOR_PIXELS * 2 * 3 + 2]; + unsigned int i, factor; + float val; + + DBG (DBG_proc, "read_line: trying to read %d lines of %d bytes\n", ltr, + length); + + counter = read_reg (dev->fd, REG9); + DBG (DBG_io, "read_line: %d bytes available\n", counter * 256); + read = 0; + if (x2 == SANE_FALSE) + { + factor = 1; + } + else + { + factor = 2; + } + + /* in retry mode we read until not enough data, but in no retry + * read only one line , counter give us 256 bytes block available + * and we want an number multiple of color channels */ + cnt = (255 + length / factor) / 256; + while ((counter > cnt && retry == 1) || (counter > cnt && read == 0)) + { + /* read data from scanner, first and last byte aren't picture data */ + read_data (dev->fd, inbuffer, length / factor + 2); + + /* image correction */ + if (correction == SANE_TRUE) + { + for (i = 0; i < length / factor; i++) + { + val = inbuffer[i + 1] - dev->offset[i]; + if (val > 0) + { + val = val * dev->gain[i]; + if (val < 255) + inbuffer[i + 1] = val; + else + inbuffer[i + 1] = 255; + } + else + { + inbuffer[i + 1] = 0; + } + } + } + + /* handle horizontal data doubling */ + if (x2 == SANE_FALSE) + { + memcpy (data + read * length, inbuffer + 1, length); + } + else + { + if (mode == MODE_COLOR) + { + for (i = 0; i < length / factor; i += 3) + { + data[read * length + i * factor] = inbuffer[i + 1]; + data[read * length + i * factor + 1] = inbuffer[i + 2]; + data[read * length + i * factor + 2] = inbuffer[i + 3]; + data[read * length + i * factor + 3] = inbuffer[i + 1]; + data[read * length + i * factor + 4] = inbuffer[i + 2]; + data[read * length + i * factor + 5] = inbuffer[i + 3]; + } + } + else + { + for (i = 0; i < length / factor; i++) + { + data[read * length + i * factor] = inbuffer[i + 1]; + data[read * length + i * factor + 1] = inbuffer[i + 1]; + } + } + } + read++; + if (retry == SANE_TRUE) + { + read_reg (dev->fd, REGF); + read_reg (dev->fd, REGA); + read_reg (dev->fd, REG9); + counter = read_reg (dev->fd, REG9); + read_reg (dev->fd, REGA); + if (read >= ltr) + { + DBG (DBG_io, "read_line returning %d lines\n", read); + return read; + } + counter = read_reg (dev->fd, REG9); + } + } + read_reg (dev->fd, REGF); + read_reg (dev->fd, REGA); + read_reg (dev->fd, REG9); + counter = read_reg (dev->fd, REG9); + read_reg (dev->fd, REGA); + DBG (DBG_io, "read_line returning %d lines\n", read); + return read; +} + + +static SANE_Status +eject (int fd) +{ + int detector; + + DBG (DBG_proc, "eject: start ...\n"); + + do + { + write_reg2 (fd, REG1, 0x1110); + detector = read_reg (fd, REGE); + detector = read_reg (fd, REGE); + } + while ((detector & 0x04) != 0); + write_reg (fd, REG0, 0x00); + write_reg (fd, REG1, 0x00); + write_reg (fd, REGF, 0x82); + write_reg (fd, REG7, 0x00); + + DBG (DBG_proc, "eject: end.\n"); + return SANE_STATUS_GOOD; +} + +/** @brief wait for document to be present in feeder + * Polls document sensor until something is present. Give up after 20 seconds + * @param fd file descriptor of the physical device + */ +/* static int +wait_document (int fd, u_int8_t detector) +{ + int count = 0; + u_int8_t val; + + write_reg (fd, REG1, 0x00); + write_reg (fd, REG7, 0x00); + detector = read_reg (fd, REGE); + while (detector == 0xc3 && count < 20) + { + sleep (1); + count++; + detector = read_reg (fd, REGE); + } + setadresses (fd, 0x002d, 0x09c7); + write_reg (fd, REG1, 0x00); + write_reg (fd, REG2, 0x90); + write_reg (fd, REGF, 0x82); + write_reg (fd, REG0, 0x00); + val = inb (fd, STATUS) & 0xf8; + if (val != 0xf8) + { + DBG (DBG_error, "wait_document: unexpected STATUS value 0x%02x instead of 0xf8", val); + } + if (count >= 20) + { + DBG (DBG_error, "wait_document: failed to detect document!\n"); + return 0; + } + return 1; +} */ + +/** @brief move at 150 dpi + * move the paper at 150 dpi motor speed by the amount specified + * @params dev pointer to the device structure + */ +static SANE_Status +move (P5_Device * dev) +{ + int skip, done, read, count; + SANE_Status status = SANE_STATUS_GOOD; + unsigned char buffer[256]; + + DBG (DBG_proc, "move: start\n"); + + /* compute number of lines to skip */ + skip = dev->ystart; + + /* works, but remains to be explained ... */ + if (dev->ydpi > 300) + skip = skip / 2; + + DBG (DBG_io, "move: skipping %d lines at %d dpi\n", skip, dev->ydpi); + + /* we do a real scan of small width, discarding data */ + done = 0; + status = start_scan (dev, MODE_GRAY, dev->ydpi, 0, 256); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "move: failed to start scan\n"); + return SANE_STATUS_INVAL; + } + + do + { + /* test if document left the feeder */ + status = test_document (dev->fd); + if (status == SANE_STATUS_NO_DOCS) + { + DBG (DBG_info, + "move: document was shorter than the required move\n"); + return SANE_STATUS_INVAL; + } + /* test if data is available */ + count = available_bytes (dev->fd); + if (count) + { + read = + read_line (dev, buffer, 256, 1, SANE_FALSE, SANE_FALSE, + MODE_GRAY, SANE_FALSE); + if (read == -1) + { + DBG (DBG_error, "move: failed to read data\n"); + return SANE_STATUS_INVAL; + } + done += read; + } + } + while (done < skip); + + /* reset scanner */ + write_reg2 (dev->fd, REG1, 0x1110); + count = read_reg (dev->fd, REGE); + count = read_reg (dev->fd, REGE); + write_reg (dev->fd, REG0, 0x00); + write_reg (dev->fd, REG1, 0x00); + write_reg (dev->fd, REGF, 0x82); + write_reg (dev->fd, REG7, 0x00); + + DBG (DBG_proc, "move: exit\n"); + return status; +} + +/** clean up calibration data + * @param dev device to clean up + */ +static void +cleanup_calibration (P5_Device * dev) +{ + int i; + + for (i = 0; i < MAX_RESOLUTIONS * 2; i++) + { + if (dev->calibration_data[i] != NULL) + { + free (dev->calibration_data[i]); + dev->calibration_data[i] = NULL; + } + } + dev->calibrated = SANE_FALSE; +} + +/** detect a black scan line + * parses the given buffer and retrun SANE_TRUE if the line is an + * acceptable black line for calibration + * @param buffer data line to parse + * @param pixels number of pixels + * @param mode MODE_COLOR or MODE_GRAY + * @returns SANE_TRUE if it is considered as a white line + */ +static SANE_Bool +is_black_line (u_int8_t * buffer, unsigned int pixels, int mode) +{ + unsigned int i, start, end, count, width; + + /* compute width in bytes */ + if (mode == MODE_COLOR) + { + width = pixels * 3; + } + else + { + width = pixels; + } + + /* we allow the calibration target to be narrower than full width, ie + * black margin at both ends of the line */ + start = (5 * width) / 100; + end = (95 * width) / 100; + count = 0; + + /* count number of black bytes */ + for (i = start; i < end; i++) + { + if (buffer[i] > BLACK_LEVEL) + { + count++; + } + } + + /* we allow 3% black pixels maximum */ + if (count > (3 * width) / 100) + { + DBG (DBG_io, "is_black_line=SANE_FALSE\n"); + return SANE_FALSE; + } + + DBG (DBG_io, "is_black_line=SANE_TRUE\n"); + return SANE_TRUE; +} + +/** detect a white scan line + * parses the given buffer and retrun SANE_TRUE if the line is an + * acceptable white line for calibration + * @param buffer data line to parse + * @param pixels number of pixels + * @param mode MODE_COLOR or MODE_GRAY + * @returns SANE_TRUE if it is considered as a white line + */ +static SANE_Bool +is_white_line (u_int8_t * buffer, unsigned int pixels, int mode) +{ + unsigned int i, start, end, count, width; + + /* compute width in bytes */ + if (mode == MODE_COLOR) + { + width = pixels * 3; + } + else + { + width = pixels; + } + + /* we allow the calibration target to be narrower than full width, ie + * black margin at both ends of the line */ + start = (5 * width) / 100; + end = (95 * width) / 100; + count = 0; + + /* count number of black bytes */ + for (i = start; i < end; i++) + { + if (buffer[i] < BLACK_LEVEL) + { + count++; + } + } + + /* we allow 3% black pixels maximum */ + if (count > (3 * width) / 100) + { + DBG (DBG_io, "is_white_line=SANE_FALSE\n"); + return SANE_FALSE; + } + + DBG (DBG_io, "is_white_line=SANE_TRUE\n"); + return SANE_TRUE; +} + +/* ------------------------------------------------------------------------- */ +/* writes gray data to a pnm file */ +static void +write_gray_data (unsigned char *image, char *name, SANE_Int width, + SANE_Int height) +{ + FILE *fdbg = NULL; + + fdbg = fopen (name, "wb"); + if (fdbg == NULL) + return; + fprintf (fdbg, "P5\n%d %d\n255\n", width, height); + fwrite (image, width, height, fdbg); + fclose (fdbg); +} + +/* ------------------------------------------------------------------------- */ +/* writes rgb data to a pnm file */ +static void +write_rgb_data (char *name, unsigned char *image, SANE_Int width, + SANE_Int height) +{ + FILE *fdbg = NULL; + + fdbg = fopen (name, "wb"); + if (fdbg == NULL) + return; + fprintf (fdbg, "P6\n%d %d\n255\n", width, height); + fwrite (image, width * 3, height, fdbg); + fclose (fdbg); +} + +/** give calibration file name + * computes the calibration file name to use based on the + * backend name and device + */ +static char * +calibration_file (char *devicename) +{ + char *ptr = NULL; + char tmp_str[PATH_MAX]; + + ptr = getenv ("HOME"); + if (ptr != NULL) + { + sprintf (tmp_str, "%s/.sane/p5-%s.cal", ptr, devicename); + } + else + { + ptr = getenv ("TMPDIR"); + if (ptr != NULL) + { + sprintf (tmp_str, "%s/p5-%s.cal", ptr, devicename); + } + else + { + sprintf (tmp_str, "/tmp/p5-%s.cal", devicename); + } + } + DBG (DBG_trace, "calibration_file: using >%s< for calibration file name\n", + tmp_str); + return strdup (tmp_str); +} + +/** restore calibration data + * restore calibration data by loading previously saved calibration data + * @param dev device to restore + * @return SANE_STATUS_GOOD on success, otherwise error code + */ +static SANE_Status +restore_calibration (P5_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + char *fname = NULL; + FILE *fcalib = NULL; + size_t size; + int i; + + DBG (DBG_proc, "restore_calibration: start\n"); + cleanup_calibration (dev); + fname = calibration_file (dev->model->name); + fcalib = fopen (fname, "rb"); + if (fcalib == NULL) + { + DBG (DBG_error, "restore_calibration: failed to open %s!\n", fname); + free (fname); + return SANE_STATUS_IO_ERROR; + } + + /* loop filling calibration data until EOF reached */ + i = 0; + while (!feof (fcalib) && (i < 2 * MAX_RESOLUTIONS)) + { + dev->calibration_data[i] = malloc (sizeof (P5_Calibration_Data)); + if (dev->calibration_data[i] == NULL) + { + cleanup_calibration (dev); + free (fname); + fclose (fcalib); + DBG (DBG_error, + "restore_calibration: failed to allocate memory for calibration\n"); + return SANE_STATUS_NO_MEM; + } + size = + fread (dev->calibration_data[i], 1, sizeof (P5_Calibration_Data), + fcalib); + if (feof (fcalib)) + { + free (dev->calibration_data[i]); + dev->calibration_data[i] = NULL; + } + else if (size != sizeof (P5_Calibration_Data)) + { + cleanup_calibration (dev); + free (fname); + fclose (fcalib); + DBG (DBG_error, "restore_calibration: failed to read from file\n"); + return SANE_STATUS_IO_ERROR; + } + DBG (DBG_trace, + "restore_calibration: read 1 calibration structure from file\n"); + i++; + } + + dev->calibrated = SANE_TRUE; + fclose (fcalib); + free (fname); + + DBG (DBG_proc, "restore_calibration: end\n"); + return status; +} + +/** save calibration data + * save calibration data from memory to file + * @param dev device calibration to save + * @return SANE_STATUS_GOOD on success, otherwise error code + */ +static SANE_Status +save_calibration (P5_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + char *fname = NULL; + FILE *fcalib = NULL; + int i; + size_t size; + + DBG (DBG_proc, "save_calibration: start\n"); + fname = calibration_file (dev->model->name); + fcalib = fopen (fname, "wb"); + if (fcalib == NULL) + { + DBG (DBG_error, "save_calibration: failed to open %s!\n", fname); + free (fname); + return SANE_STATUS_IO_ERROR; + } + + /* loop filling calibration data until EOF reached */ + i = 0; + while (dev->calibration_data[i] != NULL && (i < 2 * MAX_RESOLUTIONS)) + { + size = + fwrite (dev->calibration_data[i], sizeof (P5_Calibration_Data), 1, + fcalib); + if (size != sizeof (P5_Calibration_Data)) + { + free (fname); + fclose (fcalib); + DBG (DBG_error, "save_calibration: failed to write to file\n"); + return SANE_STATUS_IO_ERROR; + } + DBG (DBG_trace, + "save_calibration: wrote 1 calibration structure to file\n"); + i++; + } + + fclose (fcalib); + free (fname); + + DBG (DBG_proc, "save_calibration: end\n"); + return status; +} + +/** calibrate scanner + * calibrates scanner by scanning a white sheet to get + * reference data. The black reference data is extracted from the lines + * that precede the physical document. + * Calibration is done at 300 color, then data is built for other modes + * and resolutions. + * @param dev device to calibrate + */ +static SANE_Status +sheetfed_calibration (P5_Device * dev) +{ + u_int8_t buffer[MAX_SENSOR_PIXELS * 3]; + u_int16_t white_data[MAX_SENSOR_PIXELS * 3]; + u_int16_t black_data[MAX_SENSOR_PIXELS * 3]; + unsigned int i, j, k, dpi, pixels, read, black, white; + float coeff; + unsigned int red, green, blue; + int line; + SANE_Status status; + char title[40]; + + FILE *dbg = fopen ("debug.pnm", "wb"); + fprintf (dbg, "P6\n%d %d\n255\n", MAX_SENSOR_PIXELS, + CALIBRATION_SKIP_LINES * 4); + + DBG (DBG_proc, "sheetfed_calibration: start\n"); + + /* check calibration target has been loaded in ADF */ + status = test_document (dev->fd); + if (status == SANE_STATUS_NO_DOCS) + { + DBG (DBG_error, + "sheetfed_calibration: no calibration target present!\n"); + return SANE_STATUS_NO_DOCS; + } + + /* clean up calibration data */ + cleanup_calibration (dev); + + /* a RGB scan to get reference data */ + /* initialize calibration slot for the resolution */ + i = 0; + dpi = dev->model->max_xdpi; + pixels = MAX_SENSOR_PIXELS; + dev->calibration_data[i] = + (P5_Calibration_Data *) malloc (sizeof (P5_Calibration_Data)); + if (dev->calibration_data[i] == NULL) + { + cleanup_calibration (dev); + DBG (DBG_error, + "sheetfed_calibration: failed to allocate memory for calibration\n"); + return SANE_STATUS_NO_MEM; + } + dev->calibration_data[i]->dpi = dpi; + + /* start scan */ + status = start_scan (dev, MODE_COLOR, dpi, 0, pixels); + if (status != SANE_STATUS_GOOD) + { + cleanup_calibration (dev); + DBG (DBG_error, + "sheetfed_calibration: failed to start scan at %d dpi\n", dpi); + return SANE_STATUS_INVAL; + } + + white = 0; + black = 0; + read = 0; + for (j = 0; j < pixels * 3; j++) + { + black_data[j] = 0; + white_data[j] = 0; + } + + /* read lines and gather black and white ones until enough for sensor's + * native resolution */ + do + { + status = test_document (dev->fd); + if (status == SANE_STATUS_NO_DOCS && (white < 10 || black < 10)) + { + cleanup_calibration (dev); + DBG (DBG_error, + "sheetfed_calibration: calibration sheet too short!\n"); + return SANE_STATUS_INVAL; + } + memset (buffer, 0x00, MAX_SENSOR_PIXELS * 3); + line = + read_line (dev, buffer, pixels * 3, 1, SANE_FALSE, SANE_FALSE, + MODE_COLOR, SANE_FALSE); + if (line == -1) + { + DBG (DBG_error, "sheetfed_calibration: failed to read data\n"); + return SANE_STATUS_INVAL; + } + + /* if a data line has been read, add it to reference data */ + if (line) + { + read++; + fwrite (buffer, pixels * 3, 1, dbg); + if (is_white_line (buffer, pixels, MODE_COLOR) && white < 256) + { + white++; + /* first calibration lines are skipped */ + for (j = 0; j < pixels * 3 && read > CALIBRATION_SKIP_LINES; + j++) + { + white_data[j] += buffer[j]; + } + } + if (is_black_line (buffer, pixels, MODE_COLOR) && black < 256) + { + black++; + for (j = 0; j < pixels * 3; j++) + { + black_data[j] += buffer[j]; + } + } + } + } + while (test_document (dev->fd) != SANE_STATUS_NO_DOCS); + DBG (DBG_trace, "sheetfed_calibration: white lines=%d, black lines=%d\n", + white, black); + + /* average pixels and store in per dpi calibration data */ + for (j = 0; j < pixels * 3; j++) + { + dev->calibration_data[i]->white_data[j] = white_data[j] / white; + dev->calibration_data[i]->black_data[j] = black_data[j] / black; + } + + /* we average red, green and blue offset on the full sensor */ + red = 0; + green = 0; + blue = 0; + for (j = 0; j < pixels * 3; j += 3) + { + red += dev->calibration_data[i]->black_data[j]; + green += dev->calibration_data[i]->black_data[j + 1]; + blue += dev->calibration_data[i]->black_data[j + 2]; + } + for (j = 0; j < pixels * 3; j += 3) + { + dev->calibration_data[i]->black_data[j] = red / pixels; + dev->calibration_data[i]->black_data[j + 1] = green / pixels; + dev->calibration_data[i]->black_data[j + 2] = blue / pixels; + } + + /* trace calibration data for debug */ + if (DBG_LEVEL > DBG_data) + { + sprintf (title, "calibration-white-%d.pnm", + dev->calibration_data[i]->dpi); + write_rgb_data (title, dev->calibration_data[i]->white_data, pixels, 1); + sprintf (title, "calibration-black-%d.pnm", + dev->calibration_data[i]->dpi); + write_rgb_data (title, dev->calibration_data[i]->black_data, pixels, 1); + } + + /* loop on all remaining resolution and compute calibration data from it */ + for (i = 1; i < MAX_RESOLUTIONS && dev->model->xdpi_values[i] > 0; i++) + { + dev->calibration_data[i] = + (P5_Calibration_Data *) malloc (sizeof (P5_Calibration_Data)); + if (dev->calibration_data[i] == NULL) + { + cleanup_calibration (dev); + DBG (DBG_error, + "sheetfed_calibration: failed to allocate memory for calibration\n"); + return SANE_STATUS_INVAL; + } + dev->calibration_data[i]->dpi = dev->model->xdpi_values[i]; + + coeff = ((float) dev->model->xdpi_values[i]) / (float) dpi; + + /* generate data by decimation */ + for (j = 0; j < pixels / coeff; j++) + { + k = j * coeff; + dev->calibration_data[i]->white_data[j] = + dev->calibration_data[0]->white_data[k]; + dev->calibration_data[i]->white_data[j + 1] = + dev->calibration_data[0]->white_data[k + 1]; + dev->calibration_data[i]->white_data[j + 2] = + dev->calibration_data[0]->white_data[k + 2]; + dev->calibration_data[i]->black_data[j] = + dev->calibration_data[0]->black_data[k]; + dev->calibration_data[i]->black_data[j + 1] = + dev->calibration_data[0]->black_data[k + 1]; + dev->calibration_data[i]->black_data[j + 2] = + dev->calibration_data[0]->black_data[k + 2]; + } + } + + fclose (dbg); + dev->calibrated = SANE_TRUE; + + /* eject calibration target */ + eject (dev->fd); + + DBG (DBG_proc, "sheetfed_calibration: end\n"); + return SANE_STATUS_GOOD; +} + +/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */ diff --git a/backend/p5_device.h b/backend/p5_device.h new file mode 100644 index 000000000..8047b237b --- /dev/null +++ b/backend/p5_device.h @@ -0,0 +1,304 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2009 stef.dev@free.fr + + 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. +*/ + +/** @file p5_device.h + * @brief Declaration of low level structures used by the p5 backend. + * + * The structures and function declared here are used to do the low level + * communication with the physical device. + */ + +#ifndef P5_DEVICE_H +#define P5_DEVICE_H + +#include +#include +#include + +#ifdef HAVE_LINUX_PPDEV_H +#include +#include +#include +#include +#include +#include +#include +#endif + +/** @name debugging levels + */ +/* @{ */ +#define DBG_error0 0 /* errors/warnings printed even with devuglevel 0 */ +#define DBG_error 1 /* fatal errors */ +#define DBG_warn 2 /* warnings and non-fatal errors */ +#define DBG_info 4 /* informational messages */ +#define DBG_proc 8 /* starting/finishing functions */ +#define DBG_trace 16 /* tracing messages */ +#define DBG_io 32 /* io functions */ +#define DBG_io2 64 /* io functions that are called very often */ +#define DBG_data 128 /* log image data */ +/* @} */ + +/** + * maximal number of resolutions + */ +#define MAX_RESOLUTIONS 8 + +/**> sensor's number of pixels 8.5' @ 300 dpi */ +#define MAX_SENSOR_PIXELS 2550 + +/**> number of lines to skip when doing calibration */ +#define CALIBRATION_SKIP_LINES 80 + +/**> last value considered as black for calibration */ +#define BLACK_LEVEL 40 + +/**> white target value for calibration */ +#define WHITE_TARGET 220.0 + +/** per dpi calibration rgb data + * Calibration data structure + */ +typedef struct P5_Calibration_Data +{ + unsigned int dpi; + u_int8_t black_data[MAX_SENSOR_PIXELS * 3]; + u_int8_t white_data[MAX_SENSOR_PIXELS * 3]; +} P5_Calibration_Data; + +/** + * This structure describes a particular model which is handled by the backend. + * Contained data is immutable and is used to initalize the P5_Device + * structure. + */ +typedef struct P5_Model +{ + /** @name device identifier + * These values are set up once the physical device has been detected. They + * are used to build the return value of sane_get_devices(). + */ + /* @{ */ + SANE_String_Const name; + SANE_String_Const vendor; + SANE_String_Const product; + SANE_String_Const type; + /* @} */ + + /** @name resolution + * list of avalailable physical resolution. + * The resolutions must sorted from lower to higher value. The list is terminated + * by a value of 0. + */ + /* @{ */ + int xdpi_values[MAX_RESOLUTIONS]; /** possible x resolutions */ + int ydpi_values[MAX_RESOLUTIONS]; /** possible y resolutions */ + /* @} */ + + /** @name scan area description + * Minimal and maximal values. It's easier to have dedicated members instead + * of searching these values in the dpi lists. They are initialized from dpi + * lists. + */ + /* @{ */ + int max_xdpi; /** physical maximum x dpi */ + int max_ydpi; /** physical maximum y dpi */ + int min_xdpi; /** physical minimum x dpi */ + int min_ydpi; /** physical minimum y dpi */ + /* @} */ + + /** @name line distance shift + * Distance between CCD arrays for each color. Expressed in line + * number at maximum motor resolution. + */ + int lds; + + /** @name scan area description + * The geometry values are expressed from the head parking position, + * or the start. For a given model, the scan area selected by a frontend + * will have to fit within these values. + */ + /* @{ */ + SANE_Fixed x_offset; /** Start of scan area in mm */ + SANE_Fixed y_offset; /** Start of scan area in mm */ + SANE_Fixed x_size; /** Size of scan area in mm */ + SANE_Fixed y_size; /** Size of scan area in mm */ + /* @} */ + +} P5_Model; + + +/** + * Enumeration of configuration options for a device. It must starts at 0. + */ +enum P5_Configure_Option +{ + CFG_MODEL_NAME = 0, /**