diff options
Diffstat (limited to 'backend/genesys_gl646.c')
-rw-r--r-- | backend/genesys_gl646.c | 5811 |
1 files changed, 5811 insertions, 0 deletions
diff --git a/backend/genesys_gl646.c b/backend/genesys_gl646.c new file mode 100644 index 0000000..c7fa175 --- /dev/null +++ b/backend/genesys_gl646.c @@ -0,0 +1,5811 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2003 Oliver Rauch + Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> + Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de> + Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr> + Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> + Copyright (C) 2007 Luke <iceyfor@gmail.com> + Copyright (C) 2011 Alexey Osipov <simba@lerlan.ru> for HP2400 description + and tuning + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. +*/ + +#undef BACKEND_NAME +#define BACKEND_NAME genesys_gl646 + +#include "genesys_gl646.h" + +/** + * returns the value hold by a 3 word register + * @param regs register set from which reading the value + * @param regnum number of the register to read + * @return 24 bit value of the register + */ +static uint32_t +gl646_get_triple_reg (Genesys_Register_Set * regs, int regnum) +{ + Genesys_Register_Set *r = NULL; + uint32_t ret = 0; + + r = sanei_genesys_get_address (regs, regnum); + ret = r->value; + r = sanei_genesys_get_address (regs, regnum + 1); + ret = (ret << 8) + r->value; + r = sanei_genesys_get_address (regs, regnum + 2); + ret = (ret << 8) + r->value; + + return ret; +} + +/** + * returns the value hold by a 2 word register + * @param regs register set from which reading the value + * @param regnum number of the register to read + * @return 16 bit value of the register + */ +static uint32_t +gl646_get_double_reg (Genesys_Register_Set * regs, int regnum) +{ + Genesys_Register_Set *r = NULL; + uint32_t ret = 0; + + r = sanei_genesys_get_address (regs, regnum); + ret = r->value; + r = sanei_genesys_get_address (regs, regnum + 1); + ret = (ret << 8) + r->value; + + return ret; +} + +/* Write to many registers */ +static SANE_Status +gl646_bulk_write_register (Genesys_Device * dev, + Genesys_Register_Set * reg, size_t elems) +{ + SANE_Status status; + uint8_t outdata[8]; + uint8_t buffer[GENESYS_MAX_REGS * 2]; + size_t size; + unsigned int i; + + /* handle differently sized register sets, reg[0x00] may be the last one */ + i = 0; + while ((i < elems) && (reg[i].address != 0)) + i++; + elems = i; + size = i * 2; + + DBG (DBG_io, "gl646_bulk_write_register (elems= %lu, size = %lu)\n", + (u_long) elems, (u_long) size); + + + outdata[0] = BULK_OUT; + outdata[1] = BULK_REGISTER; + outdata[2] = 0x00; + outdata[3] = 0x00; + outdata[4] = (size & 0xff); + outdata[5] = ((size >> 8) & 0xff); + outdata[6] = ((size >> 16) & 0xff); + outdata[7] = ((size >> 24) & 0xff); + + status = + sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_BUFFER, + VALUE_BUFFER, INDEX, sizeof (outdata), outdata); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_write_register: failed while writing command: %s\n", + sane_strstatus (status)); + return status; + } + + /* copy registers and values in data buffer */ + for (i = 0; i < size; i += 2) + { + buffer[i] = reg[i / 2].address; + buffer[i + 1] = reg[i / 2].value; + } + + status = sanei_usb_write_bulk (dev->dn, buffer, &size); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_write_register: failed while writing bulk data: %s\n", + sane_strstatus (status)); + return status; + } + + if (DBG_LEVEL >= DBG_io2) + { + for (i = 0; i < size; i += 2) + { + DBG (DBG_io2, "reg[0x%02x] = 0x%02x\n", buffer[i], buffer[i + 1]); + } + /* when full size, decode register content */ + if (elems > 60) + { + DBG (DBG_io2, "DPISET =%d\n", + gl646_get_double_reg (reg, REG_DPISET)); + DBG (DBG_io2, "DUMMY =%d\n", + sanei_genesys_get_address (reg, REG_DUMMY)->value); + DBG (DBG_io2, "STRPIXEL =%d\n", + gl646_get_double_reg (reg, REG_STRPIXEL)); + DBG (DBG_io2, "ENDPIXEL =%d\n", + gl646_get_double_reg (reg, REG_ENDPIXEL)); + DBG (DBG_io2, "LINCNT =%d\n", + gl646_get_triple_reg (reg, REG_LINCNT)); + DBG (DBG_io2, "MAXWD =%d\n", + gl646_get_triple_reg (reg, REG_MAXWD)); + DBG (DBG_io2, "LPERIOD =%d\n", + gl646_get_double_reg (reg, REG_LPERIOD)); + DBG (DBG_io2, "FEEDL =%d\n", + gl646_get_triple_reg (reg, REG_FEEDL)); + } + } + + DBG (DBG_io, "gl646_bulk_write_register: wrote %lu bytes, %lu registers\n", + (u_long) size, (u_long) elems); + return status; +} + +/* Write bulk data (e.g. shading, gamma) */ +static SANE_Status +gl646_bulk_write_data (Genesys_Device * dev, uint8_t addr, + uint8_t * data, size_t len) +{ + SANE_Status status; + size_t size; + uint8_t outdata[8]; + + DBG (DBG_io, "gl646_bulk_write_data writing %lu bytes\n", (u_long) len); + + status = + sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_REGISTER, + VALUE_SET_REGISTER, INDEX, 1, &addr); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_write_data failed while setting register: %s\n", + sane_strstatus (status)); + return status; + } + + while (len) + { + if (len > BULKOUT_MAXSIZE) + size = BULKOUT_MAXSIZE; + else + size = len; + + outdata[0] = BULK_OUT; + outdata[1] = BULK_RAM; + outdata[2] = 0x00; + outdata[3] = 0x00; + outdata[4] = (size & 0xff); + outdata[5] = ((size >> 8) & 0xff); + outdata[6] = ((size >> 16) & 0xff); + outdata[7] = ((size >> 24) & 0xff); + + status = + sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_BUFFER, + VALUE_BUFFER, INDEX, sizeof (outdata), + outdata); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_write_data failed while writing command: %s\n", + sane_strstatus (status)); + return status; + } + + status = sanei_usb_write_bulk (dev->dn, data, &size); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_write_data failed while writing bulk data: %s\n", + sane_strstatus (status)); + return status; + } + + DBG (DBG_io2, + "gl646_bulk_write_data wrote %lu bytes, %lu remaining\n", + (u_long) size, (u_long) (len - size)); + + len -= size; + data += size; + } + + DBG (DBG_io, "gl646_bulk_write_data: end\n"); + + return status; +} + +/** + * reads value from gpio endpoint + */ +static SANE_Status +gl646_gpio_read (SANE_Int dn, uint8_t * value) +{ + return sanei_usb_control_msg (dn, REQUEST_TYPE_IN, + REQUEST_REGISTER, GPIO_READ, INDEX, 1, value); +} + +/** + * writes the given value to gpio endpoint + */ +static SANE_Status +gl646_gpio_write (SANE_Int dn, uint8_t value) +{ + DBG (DBG_proc, "gl646_gpio_write(0x%02x)\n", value); + return sanei_usb_control_msg (dn, REQUEST_TYPE_OUT, + REQUEST_REGISTER, GPIO_WRITE, + INDEX, 1, &value); +} + +/** + * writes the given value to gpio output enable endpoint + */ +static SANE_Status +gl646_gpio_output_enable (SANE_Int dn, uint8_t value) +{ + DBG (DBG_proc, "gl646_gpio_output_enable(0x%02x)\n", value); + return sanei_usb_control_msg (dn, REQUEST_TYPE_OUT, + REQUEST_REGISTER, GPIO_OUTPUT_ENABLE, + INDEX, 1, &value); +} + +/* Read bulk data (e.g. scanned data) */ +static SANE_Status +gl646_bulk_read_data (Genesys_Device * dev, uint8_t addr, + uint8_t * data, size_t len) +{ + SANE_Status status; + size_t size; + uint8_t outdata[8]; + + DBG (DBG_io, "gl646_bulk_read_data: requesting %lu bytes\n", (u_long) len); + + /* write requested size */ + status = + sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_REGISTER, + VALUE_SET_REGISTER, INDEX, 1, &addr); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_read_data failed while setting register: %s\n", + sane_strstatus (status)); + return status; + } + + outdata[0] = BULK_IN; + outdata[1] = BULK_RAM; + outdata[2] = 0x00; + outdata[3] = 0x00; + outdata[4] = (len & 0xff); + outdata[5] = ((len >> 8) & 0xff); + outdata[6] = ((len >> 16) & 0xff); + outdata[7] = ((len >> 24) & 0xff); + + status = + sanei_usb_control_msg (dev->dn, REQUEST_TYPE_OUT, REQUEST_BUFFER, + VALUE_BUFFER, INDEX, sizeof (outdata), outdata); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_read_data failed while writing command: %s\n", + sane_strstatus (status)); + return status; + } + + while (len) + { + if (len > GL646_BULKIN_MAXSIZE) + size = GL646_BULKIN_MAXSIZE; + else + size = len; + + DBG (DBG_io2, + "gl646_bulk_read_data: trying to read %lu bytes of data\n", + (u_long) size); + status = sanei_usb_read_bulk (dev->dn, data, &size); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_bulk_read_data failed while reading bulk data: %s\n", + sane_strstatus (status)); + return status; + } + + DBG (DBG_io2, + "gl646_bulk_read_data read %lu bytes, %lu remaining\n", + (u_long) size, (u_long) (len - size)); + + len -= size; + data += size; + } + + if (dev->model->is_sheetfed == SANE_TRUE) + { + gl646_detect_document_end (dev); + } + + DBG (DBG_io, "gl646_bulk_read_data: end\n"); + + return status; +} + +#if 0 +static SANE_Status +read_triple_reg (Genesys_Device * dev, int index, unsigned int *words) +{ + SANE_Status status; + uint8_t value; + + DBG (DBG_proc, "read_triple_reg\n"); + + RIE (sanei_genesys_read_register (dev, index + 2, &value)); + *words = value; + RIE (sanei_genesys_read_register (dev, index + 1, &value)); + *words += (value * 256); + RIE (sanei_genesys_read_register (dev, index, &value)); + if (dev->model->asic_type == GENESYS_GL646) + *words += ((value & 0x03) * 256 * 256); + else + *words += ((value & 0x0f) * 256 * 256); + + DBG (DBG_proc, "read_triple_reg: value=%d\n", *words); + return status; +} +#endif + + +static SANE_Bool +gl646_get_fast_feed_bit (Genesys_Register_Set * regs) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, 0x02); + if (r && (r->value & REG02_FASTFED)) + return SANE_TRUE; + return SANE_FALSE; +} + +static SANE_Bool +gl646_get_filter_bit (Genesys_Register_Set * regs) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, 0x04); + if (r && (r->value & REG04_FILTER)) + return SANE_TRUE; + return SANE_FALSE; +} + +static SANE_Bool +gl646_get_lineart_bit (Genesys_Register_Set * regs) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, 0x04); + if (r && (r->value & REG04_LINEART)) + return SANE_TRUE; + return SANE_FALSE; +} + +static SANE_Bool +gl646_get_bitset_bit (Genesys_Register_Set * regs) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, 0x04); + if (r && (r->value & REG04_BITSET)) + return SANE_TRUE; + return SANE_FALSE; +} + +static SANE_Bool +gl646_get_gain4_bit (Genesys_Register_Set * regs) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, 0x06); + if (r && (r->value & REG06_GAIN4)) + return SANE_TRUE; + return SANE_FALSE; +} + +static SANE_Bool +gl646_test_buffer_empty_bit (SANE_Byte val) +{ + if (val & REG41_BUFEMPTY) + return SANE_TRUE; + return SANE_FALSE; +} + +static SANE_Bool +gl646_test_motor_flag_bit (SANE_Byte val) +{ + if (val & REG41_MOTMFLG) + return SANE_TRUE; + return SANE_FALSE; +} + +static void +gl646_set_triple_reg (Genesys_Register_Set * regs, int regnum, uint32_t value) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, regnum); + r->value = LOBYTE (HIWORD (value)); + r = sanei_genesys_get_address (regs, regnum + 1); + r->value = HIBYTE (LOWORD (value)); + r = sanei_genesys_get_address (regs, regnum + 2); + r->value = LOBYTE (LOWORD (value)); +} + +static void +gl646_set_double_reg (Genesys_Register_Set * regs, int regnum, uint16_t value) +{ + Genesys_Register_Set *r = NULL; + + r = sanei_genesys_get_address (regs, regnum); + r->value = HIBYTE (LOWORD (value)); + r = sanei_genesys_get_address (regs, regnum + 1); + r->value = LOBYTE (LOWORD (value)); +} + +/** + * decodes and prints content of status (0x41) register + * @param val value read from reg41 + */ +static void +print_status (uint8_t val) +{ + char msg[80]; + + sprintf (msg, "%s%s%s%s%s%s%s%s", + val & REG41_PWRBIT ? "PWRBIT " : "", + val & REG41_BUFEMPTY ? "BUFEMPTY " : "", + val & REG41_FEEDFSH ? "FEEDFSH " : "", + val & REG41_SCANFSH ? "SCANFSH " : "", + val & REG41_HOMESNR ? "HOMESNR " : "", + val & REG41_LAMPSTS ? "LAMPSTS " : "", + val & REG41_FEBUSY ? "FEBUSY " : "", + val & REG41_MOTMFLG ? "MOTMFLG" : ""); + DBG (DBG_info, "status=%s\n", msg); +} + +/** + * start scanner's motor + * @param dev scanner's device + */ +static SANE_Status +gl646_start_motor (Genesys_Device * dev) +{ + return sanei_genesys_write_register (dev, 0x0f, 0x01); +} + + +/** + * stop scanner's motor + * @param dev scanner's device + */ +static SANE_Status +gl646_stop_motor (Genesys_Device * dev) +{ + return sanei_genesys_write_register (dev, 0x0f, 0x00); +} + + +/** + * find the lowest resolution for the sensor in the given mode. + * @param sensor id of the sensor + * @param color true is color mode + * @return the closest resolution for the sensor and mode + */ +static int +get_lowest_resolution (int sensor, SANE_Bool color) +{ + int i, nb; + int dpi; + + i = 0; + dpi = 9600; + nb = sizeof (sensor_master) / sizeof (Sensor_Master); + while (i < nb) + { + /* computes distance and keep mode if it is closer than previous */ + if (sensor == sensor_master[i].sensor + && sensor_master[i].color == color) + { + if (sensor_master[i].dpi < dpi) + { + dpi = sensor_master[i].dpi; + } + } + i++; + } + DBG (DBG_info, "get_lowest_resolution: %d\n", dpi); + return dpi; +} + +/** + * find the closest match in mode tables for the given resolution and scan mode. + * @param sensor id of the sensor + * @param required required resolution + * @param color true is color mode + * @return the closest resolution for the sensor and mode + */ +static int +get_closest_resolution (int sensor, int required, SANE_Bool color) +{ + int i, nb; + int dist, dpi; + + i = 0; + dpi = 0; + dist = 9600; + nb = sizeof (sensor_master) / sizeof (Sensor_Master); + while (i < nb) + { + /* exit on perfect match */ + if (sensor == sensor_master[i].sensor + && sensor_master[i].dpi == required + && sensor_master[i].color == color) + { + DBG (DBG_info, "get_closest_resolution: match found for %d\n", + required); + return required; + } + /* computes distance and keep mode if it is closer than previous */ + if (sensor == sensor_master[i].sensor + && sensor_master[i].color == color) + { + if (abs (sensor_master[i].dpi - required) < dist) + { + dpi = sensor_master[i].dpi; + dist = abs (sensor_master[i].dpi - required); + } + } + i++; + } + DBG (DBG_info, "get_closest_resolution: closest match for %d is %d\n", + required, dpi); + return dpi; +} + +/** + * Computes if sensor will be set up for half ccd pixels for the given + * scan mode. + * @param sensor id of the sensor + * @param required required resolution + * @param color true is color mode + * @return SANE_TRUE if half ccd is used + */ +static SANE_Bool +is_half_ccd (int sensor, int required, SANE_Bool color) +{ + int i, nb; + + i = 0; + nb = sizeof (sensor_master) / sizeof (Sensor_Master); + while (i < nb) + { + /* exit on perfect match */ + if (sensor == sensor_master[i].sensor + && sensor_master[i].dpi == required + && sensor_master[i].color == color) + { + DBG (DBG_io, "is_half_ccd: match found for %d (half_ccd=%d)\n", + required, sensor_master[i].half_ccd); + return sensor_master[i].half_ccd; + } + i++; + } + DBG (DBG_info, "is_half_ccd: failed to find match for %d dpi\n", required); + return SANE_FALSE; +} + +/** + * Returns the cksel values used by the required scan mode. + * @param sensor id of the sensor + * @param required required resolution + * @param color true is color mode + * @return cksel value for mode + */ +static int +get_cksel (int sensor, int required, SANE_Bool color) +{ + int i, nb; + + i = 0; + nb = sizeof (sensor_master) / sizeof (Sensor_Master); + while (i < nb) + { + /* exit on perfect match */ + if (sensor == sensor_master[i].sensor + && sensor_master[i].dpi == required + && sensor_master[i].color == color) + { + DBG (DBG_io, "get_cksel: match found for %d (cksel=%d)\n", + required, sensor_master[i].cksel); + return sensor_master[i].cksel; + } + i++; + } + DBG (DBG_error, "get_cksel: failed to find match for %d dpi\n", required); + /* fail safe fallback */ + return 1; +} + +/** + * Setup register and motor tables for a scan at the + * given resolution and color mode. TODO try to not use any filed from + * the device. + * @param dev pointer to a struct describing the device + * @param regs register set to fill + * @param scan_settings scan's settings + * @param slope_table1 first motor table to fill + * @param slope_table2 second motor table to fill + * @param resolution dpi of the scan + * @param move distance to move (at scan's dpi) before scan + * @param linecnt number of lines to scan at scan's dpi + * @param startx start of scan area on CCD at CCD's optical resolution + * @param endx end of scan area on CCD at CCD's optical resolution + * @param color SANE_TRUE is color scan + * @param depth 1, 8 or 16 bits data sample + * @return SANE_STATUS_GOOD if registers could be set, SANE_STATUS_INVAL if + * conditions can't be met. + * @note No harcoded SENSOR or MOTOR 'names' should be present and + * registers are set from settings tables and flags related + * to the hardware capabilities. + * */ +static SANE_Status +gl646_setup_registers (Genesys_Device * dev, + Genesys_Register_Set * regs, + Genesys_Settings scan_settings, + uint16_t * slope_table1, + uint16_t * slope_table2, + SANE_Int resolution, + uint32_t move, + uint32_t linecnt, + uint16_t startx, + uint16_t endx, SANE_Bool color, + SANE_Int depth) +{ + SANE_Status status = SANE_STATUS_GOOD; + int i, nb; + Sensor_Master *sensor = NULL; + Motor_Master *motor = NULL; + Sensor_Settings *settings = NULL; + Genesys_Register_Set *r; + unsigned int used1, used2, vfinal; + unsigned int bpp; /**> bytes per pixel */ + uint32_t z1, z2; + uint16_t ex, sx; + int channels = 1, stagger, words_per_line, max_shift; + size_t requested_buffer_size; + size_t read_buffer_size; + SANE_Bool half_ccd = SANE_FALSE; + SANE_Int xresolution; + int feedl; + + DBG (DBG_proc, "gl646_setup_registers: start\n"); + DBG (DBG_info, "gl646_setup_registers: startx=%d, endx=%d, linecnt=%d\n", + startx, endx, linecnt); + + /* x resolution is capped by sensor's capability */ + if (resolution > dev->sensor.optical_res) + { + xresolution = dev->sensor.optical_res; + } + else + { + xresolution = resolution; + } + + /* for the given resolution, search for master + * sensor mode setting */ + i = 0; + nb = sizeof (sensor_master) / sizeof (Sensor_Master); + while (i < nb) + { + if (dev->model->ccd_type == sensor_master[i].sensor + && sensor_master[i].dpi == xresolution + && sensor_master[i].color == color) + { + sensor = &sensor_master[i]; + } + i++; + } + if (sensor == NULL) + { + DBG (DBG_error, + "gl646_setup_registers: unable to find settings for sensor %d at %d dpi color=%d\n", + dev->model->ccd_type, xresolution, color); + return SANE_STATUS_INVAL; + } + + /* for the given resolution, search for master + * motor mode setting */ + i = 0; + nb = sizeof (motor_master) / sizeof (Motor_Master); + while (i < nb) + { + if (dev->model->motor_type == motor_master[i].motor + && motor_master[i].dpi == resolution + && motor_master[i].color == color) + { + motor = &motor_master[i]; + } + i++; + } + if (motor == NULL) + { + DBG (DBG_error, + "gl646_setup_registers: unable to find settings for motor %d at %d dpi, color=%d\n", + dev->model->motor_type, resolution, color); + return SANE_STATUS_INVAL; + } + + /* now we can search for the specific sensor settings */ + i = 0; + nb = sizeof (sensor_settings) / sizeof (Sensor_Settings); + while (i < nb) + { + if (sensor->sensor == sensor_settings[i].sensor + && sensor->cksel == sensor_settings[i].cksel) + { + settings = &sensor_settings[i]; + } + i++; + } + if (settings == NULL) + { + DBG (DBG_error, + "gl646_setup_registers: unable to find settings for sensor %d with '%d' ccd timing\n", + sensor->sensor, sensor->cksel); + return SANE_STATUS_INVAL; + } + + /* half_ccd if manual clock programming or dpi is half dpiset */ + half_ccd = sensor->half_ccd; + + /* now apply values from settings to registers */ + if (sensor->regs_0x10_0x15 != NULL) + { + for (i = 0; i < 6; i++) + { + r = sanei_genesys_get_address (regs, 0x10 + i); + r->value = sensor->regs_0x10_0x15[i]; + } + } + else + { + for (i = 0; i < 6; i++) + { + r = sanei_genesys_get_address (regs, 0x10 + i); + r->value = 0; + } + } + + for (i = 0; i < 4; i++) + { + r = sanei_genesys_get_address (regs, 0x08 + i); + if (half_ccd == SANE_TRUE) + r->value = settings->manual_0x08_0x0b[i]; + else + r->value = settings->regs_0x08_0x0b[i]; + } + + for (i = 0; i < 8; i++) + { + r = sanei_genesys_get_address (regs, 0x16 + i); + r->value = settings->regs_0x16_0x1d[i]; + } + + for (i = 0; i < 13; i++) + { + r = sanei_genesys_get_address (regs, 0x52 + i); + r->value = settings->regs_0x52_0x5e[i]; + } + if (half_ccd == SANE_TRUE) + { + for (i = 0; i < 7; i++) + { + r = sanei_genesys_get_address (regs, 0x52 + i); + r->value = settings->manual_0x52_0x58[i]; + } + } + + /* now generate slope tables : we are not using generate_slope_table3 yet */ + sanei_genesys_generate_slope_table (slope_table1, motor->steps1, + motor->steps1 + 1, motor->vend1, + motor->vstart1, motor->vend1, + motor->steps1, motor->g1, &used1, + &vfinal); + sanei_genesys_generate_slope_table (slope_table2, motor->steps2, + motor->steps2 + 1, motor->vend2, + motor->vstart2, motor->vend2, + motor->steps2, motor->g2, &used2, + &vfinal); + + if (color == SANE_TRUE) + channels = 3; + else + channels = 1; + + /* R01 */ + /* now setup other registers for final scan (ie with shading enabled) */ + /* watch dog + shading + scan enable */ + regs[reg_0x01].value |= REG01_DOGENB | REG01_DVDSET | REG01_SCAN; + if (dev->model->is_cis == SANE_TRUE) + regs[reg_0x01].value |= REG01_CISSET; + else + regs[reg_0x01].value &= ~REG01_CISSET; + + /* if device has no calibration, don't enable shading correction */ + if (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION) + { + regs[reg_0x01].value &= ~REG01_DVDSET; + } + + regs[reg_0x01].value &= ~REG01_FASTMOD; + if (motor->fastmod) + regs[reg_0x01].value |= REG01_FASTMOD; + + /* R02 */ + /* allow moving when buffer full by default */ + if (dev->model->is_sheetfed == SANE_FALSE) + dev->reg[reg_0x02].value &= ~REG02_ACDCDIS; + else + dev->reg[reg_0x02].value |= REG02_ACDCDIS; + + /* setup motor power and direction */ + regs[reg_0x02].value |= REG02_MTRPWR; + regs[reg_0x02].value &= ~REG02_MTRREV; + + /* fastfed enabled (2 motor slope tables) */ + if (motor->fastfed) + regs[reg_0x02].value |= REG02_FASTFED; + else + regs[reg_0x02].value &= ~REG02_FASTFED; + + /* step type */ + regs[reg_0x02].value &= ~REG02_STEPSEL; + switch (motor->steptype) + { + case FULL_STEP: + break; + case HALF_STEP: + regs[reg_0x02].value |= 1; + break; + case QUATER_STEP: + regs[reg_0x02].value |= 2; + break; + default: + regs[reg_0x02].value |= 3; + break; + } + + /* if sheetfed, no AGOHOME */ + if (dev->model->is_sheetfed == SANE_TRUE) + { + regs[reg_0x02].value &= ~REG02_AGOHOME; + } + else + { + regs[reg_0x02].value |= REG02_AGOHOME; + } + + /* R03 */ + regs[reg_0x03].value &= ~REG03_AVEENB; + /* regs[reg_0x03].value |= REG03_AVEENB; */ + regs[reg_0x03].value &= ~REG03_LAMPDOG; + + /* select XPA */ + regs[reg_0x03].value &= ~REG03_XPASEL; + if (scan_settings.scan_method == SCAN_METHOD_TRANSPARENCY) + { + regs[reg_0x03].value |= REG03_XPASEL; + } + + /* R04 */ + /* monochrome / color scan */ + switch (depth) + { + case 1: + regs[reg_0x04].value &= ~REG04_BITSET; + regs[reg_0x04].value |= REG04_LINEART; + break; + case 8: + regs[reg_0x04].value &= ~(REG04_LINEART | REG04_BITSET); + break; + case 16: + regs[reg_0x04].value &= ~REG04_LINEART; + regs[reg_0x04].value |= REG04_BITSET; + break; + } + + /* R05 */ + regs[reg_0x05].value &= ~REG05_DPIHW; + switch (dev->sensor.optical_res) + { + case 600: + regs[reg_0x05].value |= REG05_DPIHW_600; + break; + case 1200: + regs[reg_0x05].value |= REG05_DPIHW_1200; + break; + case 2400: + regs[reg_0x05].value |= REG05_DPIHW_2400; + break; + default: + regs[reg_0x05].value |= REG05_DPIHW; + } + + /* gamma enable for scans */ + if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) + regs[reg_0x05].value |= REG05_GMM14BIT; + if (depth < 16) + regs[reg_0x05].value |= REG05_GMMENB; + else + regs[reg_0x05].value &= ~REG05_GMMENB; + + /* true CIS gray if needed */ + if (dev->model->is_cis == SANE_TRUE && color == SANE_FALSE + && dev->settings.true_gray) + { + regs[reg_0x05].value |= REG05_LEDADD; + } + else + { + regs[reg_0x05].value &= ~REG05_LEDADD; + } + + /* cktoggle, ckdelay and cksel at once, cktdelay=2 => half_ccd for md5345 */ + regs[reg_0x18].value = sensor->r18; + + /* manual CCD/2 clock programming => half_ccd for hp2300 */ + regs[reg_0x1d].value = sensor->r1d; + + /* HP2400 1200dpi mode tuning */ + + if (dev->model->ccd_type == CCD_HP2400) + { + /* reset count of dummy lines to zero */ + regs[reg_0x1e].value &= ~REG1E_LINESEL; + if (scan_settings.xres >= 1200) + { + /* there must be one dummy line */ + regs[reg_0x1e].value |= 1 & REG1E_LINESEL; + + /* GPO12 need to be set to zero */ + regs[reg_0x66].value &= ~0x20; + } + else + { + /* set GPO12 back to one */ + regs[reg_0x66].value |= 0x20; + } + } + + /* motor steps used */ + regs[reg_0x21].value = motor->steps1; + regs[reg_0x22].value = motor->fwdbwd; + regs[reg_0x23].value = motor->fwdbwd; + regs[reg_0x24].value = motor->steps1; + + /* scanned area height must be enlarged by max color shift needed */ + max_shift=sanei_genesys_compute_max_shift(dev,channels,scan_settings.yres,0); + + /* we adjust linecnt according to real motor dpi */ + linecnt = (linecnt * motor->ydpi) / scan_settings.yres + max_shift; + + /* at QUATER_STEP lines are 'staggered' and need correction */ + stagger = 0; + if ((!half_ccd) && (dev->model->flags & GENESYS_FLAG_STAGGERED_LINE)) + { + /* for HP3670, stagger happens only at >=1200 dpi */ + if ((dev->model->motor_type != MOTOR_HP3670 + && dev->model->motor_type != MOTOR_HP2400) + || scan_settings.yres >= dev->sensor.optical_res) + { + stagger = (4 * scan_settings.yres) / dev->motor.base_ydpi; + } + } + linecnt += stagger; + + DBG (DBG_info, "gl646_setup_registers : max_shift=%d, stagger=%d lines\n", + max_shift, stagger); + + /* CIS scanners read one line per color channel + * since gray mode use 'add' we also read 3 channels even not in + * color mode */ + if (dev->model->is_cis == SANE_TRUE) + { + gl646_set_triple_reg (regs, REG_LINCNT, linecnt * 3); + linecnt *= channels; + } + else + { + gl646_set_triple_reg (regs, REG_LINCNT, linecnt); + } + + /* scanner's x coordinates are expressed in physical DPI but they must be divided by cksel */ + sx = startx / sensor->cksel; + ex = endx / sensor->cksel; + if (half_ccd == SANE_TRUE) + { + sx /= 2; + ex /= 2; + } + gl646_set_double_reg (regs, REG_STRPIXEL, sx); + gl646_set_double_reg (regs, REG_ENDPIXEL, ex); + DBG (DBG_info, "gl646_setup_registers: startx=%d, endx=%d, half_ccd=%d\n", + sx, ex, half_ccd); + + /* words_per_line must be computed according to the scan's resolution */ + /* in fact, words_per_line _gives_ the actual scan resolution */ + words_per_line = (((endx - startx) * sensor->xdpi) / dev->sensor.optical_res); + bpp=depth/8; + if (depth == 1) + { + words_per_line = (words_per_line+7)/8 ; + bpp=1; + } + else + { + words_per_line *= bpp; + } + dev->bpl = words_per_line; + words_per_line *= channels; + dev->wpl = words_per_line; + + DBG (DBG_info, "gl646_setup_registers: wpl=%d\n", words_per_line); + gl646_set_triple_reg (regs, REG_MAXWD, words_per_line); + + gl646_set_double_reg (regs, REG_DPISET, sensor->dpiset); + gl646_set_double_reg (regs, REG_LPERIOD, sensor->exposure); + + /* move distance must be adjusted to take into account the extra lines + * read to reorder data */ + feedl = move; + if (stagger + max_shift > 0 && feedl != 0) + { + if (feedl > + ((max_shift + stagger) * dev->motor.optical_ydpi) / motor->ydpi) + feedl = + feedl - + ((max_shift + stagger) * dev->motor.optical_ydpi) / motor->ydpi; + } + + /* we assume all scans are done with 2 tables */ + /* + feedl = feed_steps - fast_slope_steps*2 - + (slow_slope_steps >> scan_step_type); */ + /* but head has moved due to shading calibration => dev->scanhead_position_in_steps */ + if (feedl > 0) + { + /* take into account the distance moved during calibration */ + /* feedl -= dev->scanhead_position_in_steps; */ + DBG (DBG_info, "gl646_setup_registers: initial move=%d\n", feedl); + DBG (DBG_info, "gl646_setup_registers: scanhead_position_in_steps=%d\n", + dev->scanhead_position_in_steps); + + /* TODO clean up this when I'll fully understand. + * for now, special casing each motor */ + switch (dev->model->motor_type) + { + case MOTOR_5345: + switch (motor->ydpi) + { + case 200: + feedl -= 70; + break; + case 300: + feedl -= 70; + break; + case 400: + feedl += 130; + break; + case 600: + feedl += 160; + break; + case 1200: + feedl += 160; + break; + case 2400: + feedl += 180; + break; + default: + break; + } + break; + case MOTOR_HP2300: + switch (motor->ydpi) + { + case 75: + feedl -= 180; + break; + case 150: + feedl += 0; + break; + case 300: + feedl += 30; + break; + case 600: + feedl += 35; + break; + case 1200: + feedl += 45; + break; + default: + break; + } + break; + case MOTOR_HP2400: + switch (motor->ydpi) + { + case 150: + feedl += 150; + break; + case 300: + feedl += 220; + break; + case 600: + feedl += 260; + break; + case 1200: + feedl += 280; /* 300 */ + break; + case 50: + feedl += 0; + break; + case 100: + feedl += 100; + break; + default: + break; + } + break; + + /* theorical value */ + default: + if (motor->fastfed) + { + feedl = + feedl - 2 * motor->steps2 - + (motor->steps1 >> motor->steptype); + } + else + { + feedl = feedl - (motor->steps1 >> motor->steptype); + } + break; + } + /* security */ + if (feedl < 0) + feedl = 0; + } + + DBG (DBG_info, "gl646_setup_registers: final move=%d\n", feedl); + gl646_set_triple_reg (regs, REG_FEEDL, feedl); + + regs[reg_0x65].value = motor->mtrpwm; + + sanei_genesys_calculate_zmode2 (regs[reg_0x02].value & REG02_FASTFED, + sensor->exposure, + slope_table1, + motor->steps1, + move, motor->fwdbwd, &z1, &z2); + + /* no z1/z2 for sheetfed scanners */ + if (dev->model->is_sheetfed == SANE_TRUE) + { + z1 = 0; + z2 = 0; + } + gl646_set_double_reg (regs, REG_Z1MOD, z1); + gl646_set_double_reg (regs, REG_Z2MOD, z2); + regs[reg_0x6b].value = motor->steps2; + regs[reg_0x6c].value = + (regs[reg_0x6c].value & REG6C_TGTIME) | ((z1 >> 13) & 0x38) | ((z2 >> 16) + & 0x07); + + RIE (write_control (dev, xresolution)); + + /* setup analog frontend */ + RIE (gl646_set_fe (dev, AFE_SET, xresolution)); + + /* now we're done with registers setup values used by data transfer */ + /* we setup values needed for the data transfer */ + + /* we must use a round number of words_per_line */ + requested_buffer_size = 8 * words_per_line; + read_buffer_size = + 2 * requested_buffer_size + + ((max_shift + stagger) * scan_settings.pixels * channels * depth) / 8; + + RIE (sanei_genesys_buffer_free (&(dev->read_buffer))); + RIE (sanei_genesys_buffer_alloc (&(dev->read_buffer), read_buffer_size)); + + RIE (sanei_genesys_buffer_free (&(dev->lines_buffer))); + RIE (sanei_genesys_buffer_alloc (&(dev->lines_buffer), read_buffer_size)); + + RIE (sanei_genesys_buffer_free (&(dev->shrink_buffer))); + RIE (sanei_genesys_buffer_alloc (&(dev->shrink_buffer), + requested_buffer_size)); + + RIE (sanei_genesys_buffer_free (&(dev->out_buffer))); + RIE (sanei_genesys_buffer_alloc (&(dev->out_buffer), + 8 * scan_settings.pixels * channels * bpp)); + + /* scan bytes to read */ + dev->read_bytes_left = words_per_line * linecnt; + + DBG (DBG_info, + "gl646_setup_registers: physical bytes to read = %lu\n", + (u_long) dev->read_bytes_left); + dev->read_active = SANE_TRUE; + + dev->current_setup.pixels = + ((endx - startx) * sensor->xdpi) / dev->sensor.optical_res; + dev->current_setup.lines = linecnt; + dev->current_setup.depth = depth; + dev->current_setup.channels = channels; + dev->current_setup.exposure_time = sensor->exposure; + dev->current_setup.xres = sensor->xdpi; + dev->current_setup.yres = motor->ydpi; + dev->current_setup.half_ccd = half_ccd; + dev->current_setup.stagger = stagger; + dev->current_setup.max_shift = max_shift + stagger; + + /* total_bytes_to_read is the number of byte to send to frontend + * total_bytes_read is the number of bytes sent to frontend + * read_bytes_left is the number of bytes to read from the scanner + */ + dev->total_bytes_read = 0; + if (depth == 1) + dev->total_bytes_to_read = + ((scan_settings.pixels * scan_settings.lines) / 8 + + (((scan_settings.pixels * scan_settings.lines) % 8) ? 1 : 0)) * + channels; + else + dev->total_bytes_to_read = + scan_settings.pixels * scan_settings.lines * channels * bpp; + + DBG (DBG_proc, "gl646_setup_registers: end\n"); + return SANE_STATUS_GOOD; +} + + +/** copy sensor specific settings */ +/* *dev : device infos + *regs : regiters to be set + extended : do extended set up + half_ccd: set up for half ccd resolution + all registers 08-0B, 10-1D, 52-5E are set up. They shouldn't + appear anywhere else but in register init +*/ +static void +gl646_setup_sensor (Genesys_Device * dev, Genesys_Register_Set * regs) +{ + Genesys_Register_Set *r; + int i; + + DBG (DBG_proc, "gl646_setup_sensor: start\n"); + for (i = 0; i < 4; i++) + { + r = sanei_genesys_get_address (regs, 0x08 + i); + r->value = dev->sensor.regs_0x08_0x0b[i]; + } + + for (i = 0; i < 14; i++) + { + r = sanei_genesys_get_address (regs, 0x10 + i); + r->value = dev->sensor.regs_0x10_0x1d[i]; + } + + for (i = 0; i < 13; i++) + { + r = sanei_genesys_get_address (regs, 0x52 + i); + r->value = dev->sensor.regs_0x52_0x5e[i]; + } + DBG (DBG_proc, "gl646_setup_sensor: end\n"); + +} + +/** Test if the ASIC works + */ +static SANE_Status +gl646_asic_test (Genesys_Device * dev) +{ + SANE_Status status; + uint8_t val; + uint8_t *data; + uint8_t *verify_data; + size_t size, verify_size; + unsigned int i; + + DBG (DBG_proc, "gl646_asic_test: start\n"); + + /* set and read exposure time, compare if it's the same */ + status = sanei_genesys_write_register (dev, 0x38, 0xde); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to write register: %s\n", + sane_strstatus (status)); + return status; + } + + status = sanei_genesys_write_register (dev, 0x39, 0xad); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to write register: %s\n", + sane_strstatus (status)); + return status; + } + + status = sanei_genesys_read_register (dev, 0x4e, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to read register: %s\n", + sane_strstatus (status)); + return status; + } + if (val != 0xde) /* value of register 0x38 */ + { + DBG (DBG_error, "gl646_asic_test: register contains invalid value\n"); + return SANE_STATUS_IO_ERROR; + } + + status = sanei_genesys_read_register (dev, 0x4f, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to read register: %s\n", + sane_strstatus (status)); + return status; + } + if (val != 0xad) /* value of register 0x39 */ + { + DBG (DBG_error, "gl646_asic_test: register contains invalid value\n"); + return SANE_STATUS_IO_ERROR; + } + + /* ram test: */ + size = 0x40000; + verify_size = size + 0x80; + /* todo: looks like the read size must be a multiple of 128? + otherwise the read doesn't succeed the second time after the scanner has + been plugged in. Very strange. */ + + data = (uint8_t *) malloc (size); + if (!data) + { + DBG (DBG_error, "gl646_asic_test: could not allocate memory\n"); + return SANE_STATUS_NO_MEM; + } + + verify_data = (uint8_t *) malloc (verify_size); + if (!verify_data) + { + free (data); + DBG (DBG_error, "gl646_asic_test: could not allocate memory\n"); + return SANE_STATUS_NO_MEM; + } + + for (i = 0; i < (size - 1); i += 2) + { + data[i] = i / 512; + data[i + 1] = (i / 2) % 256; + } + + status = sanei_genesys_set_buffer_address (dev, 0x0000); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to set buffer address: %s\n", + sane_strstatus (status)); + free (data); + free (verify_data); + return status; + } + + status = gl646_bulk_write_data (dev, 0x3c, data, size); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to bulk write data: %s\n", + sane_strstatus (status)); + free (data); + free (verify_data); + return status; + } + + status = sanei_genesys_set_buffer_address (dev, 0x0000); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to set buffer address: %s\n", + sane_strstatus (status)); + free (data); + free (verify_data); + return status; + } + + status = + gl646_bulk_read_data (dev, 0x45, (uint8_t *) verify_data, verify_size); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_asic_test: failed to bulk read data: %s\n", + sane_strstatus (status)); + free (data); + free (verify_data); + return status; + } + + /* i + 2 is needed as the changed address goes into effect only after one + data word is sent. */ + for (i = 0; i < size; i++) + { + if (verify_data[i + 2] != data[i]) + { + DBG (DBG_error, "gl646_asic_test: data verification error\n"); + free (data); + free (verify_data); + return SANE_STATUS_IO_ERROR; + } + } + + free (data); + free (verify_data); + + DBG (DBG_info, "gl646_asic_test: end\n"); + + return SANE_STATUS_GOOD; +} + +/* returns the max register bulk size */ +static int +gl646_bulk_full_size (void) +{ + return GENESYS_GL646_MAX_REGS; +} + +/** + * Set all registers to default values after init + * @param dev scannerr's device to set + */ +static void +gl646_init_regs (Genesys_Device * dev) +{ + int nr, addr; + + DBG (DBG_proc, "gl646_init_regs\n"); + + nr = 0; + memset (dev->reg, 0, GENESYS_MAX_REGS * sizeof (Genesys_Register_Set)); + + for (addr = 1; addr <= 0x0b; addr++) + dev->reg[nr++].address = addr; + for (addr = 0x10; addr <= 0x29; addr++) + dev->reg[nr++].address = addr; + for (addr = 0x2c; addr <= 0x39; addr++) + dev->reg[nr++].address = addr; + for (addr = 0x3d; addr <= 0x3f; addr++) + dev->reg[nr++].address = addr; + for (addr = 0x52; addr <= 0x5e; addr++) + dev->reg[nr++].address = addr; + for (addr = 0x60; addr <= 0x6d; addr++) + dev->reg[nr++].address = addr; + + dev->reg[reg_0x01].value = 0x20 /*0x22 */ ; /* enable shading, CCD, color, 1M */ + dev->reg[reg_0x02].value = 0x30 /*0x38 */ ; /* auto home, one-table-move, full step */ + if (dev->model->motor_type == MOTOR_5345) + dev->reg[reg_0x02].value |= 0x01; /* half-step */ + switch (dev->model->motor_type) + { + case MOTOR_5345: + dev->reg[reg_0x02].value |= 0x01; /* half-step */ + break; + case MOTOR_XP200: + /* for this sheetfed scanner, no AGOHOME, nor backtracking */ + dev->reg[reg_0x02].value = 0x50; + break; + default: + break; + } + dev->reg[reg_0x03].value = 0x1f /*0x17 */ ; /* lamp on */ + dev->reg[reg_0x04].value = 0x13 /*0x03 */ ; /* 8 bits data, 16 bits A/D, color, Wolfson fe *//* todo: according to spec, 0x0 is reserved? */ + switch (dev->model->dac_type) + { + case DAC_AD_XP200: + dev->reg[reg_0x04].value = 0x12; + break; + default: + /* Wolfson frontend */ + dev->reg[reg_0x04].value = 0x13; + break; + } + + dev->reg[reg_0x05].value = 0x00; /* 12 bits gamma, disable gamma, 24 clocks/pixel */ + switch (dev->sensor.optical_res) + { + case 600: + dev->reg[reg_0x05].value |= REG05_DPIHW_600; + break; + case 1200: + dev->reg[reg_0x05].value |= REG05_DPIHW_1200; + break; + case 2400: + dev->reg[reg_0x05].value |= REG05_DPIHW_2400; + break; + default: + dev->reg[reg_0x05].value |= REG05_DPIHW; + break; + } + if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) + dev->reg[reg_0x05].value |= REG05_GMM14BIT; + if (dev->model->dac_type == DAC_AD_XP200) + dev->reg[reg_0x05].value |= 0x01; /* 12 clocks/pixel */ + + if (dev->model->ccd_type == CCD_HP2300) + dev->reg[reg_0x06].value = 0x00; /* PWRBIT off, shading gain=4, normal AFE image capture */ + else + dev->reg[reg_0x06].value = 0x18; /* PWRBIT on, shading gain=8, normal AFE image capture */ + + + gl646_setup_sensor (dev, dev->reg); + + dev->reg[reg_0x1e].value = 0xf0; /* watch-dog time */ + + switch (dev->model->ccd_type) + { + case CCD_HP2300: + dev->reg[reg_0x1e].value = 0xf0; + dev->reg[reg_0x1f].value = 0x10; + dev->reg[reg_0x20].value = 0x20; + break; + case CCD_HP2400: + dev->reg[reg_0x1e].value = 0x80; + dev->reg[reg_0x1f].value = 0x10; + dev->reg[reg_0x20].value = 0x20; + break; + case CCD_HP3670: + dev->reg[reg_0x19].value = 0x2a; + dev->reg[reg_0x1e].value = 0x80; + dev->reg[reg_0x1f].value = 0x10; + dev->reg[reg_0x20].value = 0x20; + break; + case CIS_XP200: + dev->reg[reg_0x1e].value = 0x10; + dev->reg[reg_0x1f].value = 0x01; + dev->reg[reg_0x20].value = 0x50; + break; + default: + dev->reg[reg_0x1f].value = 0x01; + dev->reg[reg_0x20].value = 0x50; + break; + } + + dev->reg[reg_0x21].value = 0x08 /*0x20 */ ; /* table one steps number for forward slope curve of the acc/dec */ + dev->reg[reg_0x22].value = 0x10 /*0x08 */ ; /* steps number of the forward steps for start/stop */ + dev->reg[reg_0x23].value = 0x10 /*0x08 */ ; /* steps number of the backward steps for start/stop */ + dev->reg[reg_0x24].value = 0x08 /*0x20 */ ; /* table one steps number backward slope curve of the acc/dec */ + dev->reg[reg_0x25].value = 0x00; /* scan line numbers (7000) */ + dev->reg[reg_0x26].value = 0x00 /*0x1b */ ; + dev->reg[reg_0x27].value = 0xd4 /*0x58 */ ; + dev->reg[reg_0x28].value = 0x01; /* PWM duty for lamp control */ + dev->reg[reg_0x29].value = 0xff; + + dev->reg[reg_0x2c].value = 0x02; /* set resolution (600 DPI) */ + dev->reg[reg_0x2d].value = 0x58; + dev->reg[reg_0x2e].value = 0x78; /* set black&white threshold high level */ + dev->reg[reg_0x2f].value = 0x7f; /* set black&white threshold low level */ + + dev->reg[reg_0x30].value = 0x00; /* begin pixel position (16) */ + dev->reg[reg_0x31].value = dev->sensor.dummy_pixel /*0x10 */ ; /* TGW + 2*TG_SHLD + x */ + dev->reg[reg_0x32].value = 0x2a /*0x15 */ ; /* end pixel position (5390) */ + dev->reg[reg_0x33].value = 0xf8 /*0x0e */ ; /* TGW + 2*TG_SHLD + y */ + dev->reg[reg_0x34].value = dev->sensor.dummy_pixel; + dev->reg[reg_0x35].value = 0x01 /*0x00 */ ; /* set maximum word size per line, for buffer full control (10800) */ + dev->reg[reg_0x36].value = 0x00 /*0x2a */ ; + dev->reg[reg_0x37].value = 0x00 /*0x30 */ ; + dev->reg[reg_0x38].value = HIBYTE (dev->settings.exposure_time) /*0x2a */ ; /* line period (exposure time = 11000 pixels) */ + dev->reg[reg_0x39].value = LOBYTE (dev->settings.exposure_time) /*0xf8 */ ; + dev->reg[reg_0x3d].value = 0x00; /* set feed steps number of motor move */ + dev->reg[reg_0x3e].value = 0x00; + dev->reg[reg_0x3f].value = 0x01 /*0x00 */ ; + + dev->reg[reg_0x60].value = 0x00; /* Z1MOD, 60h:61h:(6D b5:b3), remainder for start/stop */ + dev->reg[reg_0x61].value = 0x00; /* (21h+22h)/LPeriod */ + dev->reg[reg_0x62].value = 0x00; /* Z2MODE, 62h:63h:(6D b2:b0), remainder for start scan */ + dev->reg[reg_0x63].value = 0x00; /* (3Dh+3Eh+3Fh)/LPeriod for one-table mode,(21h+1Fh)/LPeriod */ + dev->reg[reg_0x64].value = 0x00; /* motor PWM frequency */ + dev->reg[reg_0x65].value = 0x00; /* PWM duty cycle for table one motor phase (63 = max) */ + if (dev->model->motor_type == MOTOR_5345) + dev->reg[reg_0x65].value = 0x02; /* PWM duty cycle for table one motor phase (63 = max) */ + dev->reg[reg_0x66].value = dev->gpo.value[0]; + dev->reg[reg_0x67].value = dev->gpo.value[1]; + dev->reg[reg_0x68].value = dev->gpo.enable[0]; + dev->reg[reg_0x69].value = dev->gpo.enable[1]; + + switch (dev->model->motor_type) + { + case MOTOR_HP2300: + case MOTOR_HP2400: + dev->reg[reg_0x6a].value = 0x7f; /* table two steps number for acc/dec */ + dev->reg[reg_0x6b].value = 0x78; /* table two steps number for acc/dec */ + dev->reg[reg_0x6d].value = 0x7f; + break; + case MOTOR_5345: + dev->reg[reg_0x6a].value = 0x42; /* table two fast moving step type, PWM duty for table two */ + dev->reg[reg_0x6b].value = 0xff; /* table two steps number for acc/dec */ + dev->reg[reg_0x6d].value = 0x41; /* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */ + break; + case MOTOR_XP200: + dev->reg[reg_0x6a].value = 0x7f; /* table two fast moving step type, PWM duty for table two */ + dev->reg[reg_0x6b].value = 0x08; /* table two steps number for acc/dec */ + dev->reg[reg_0x6d].value = 0x01; /* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */ + break; + case MOTOR_HP3670: + dev->reg[reg_0x6a].value = 0x41; /* table two steps number for acc/dec */ + dev->reg[reg_0x6b].value = 0xc8; /* table two steps number for acc/dec */ + dev->reg[reg_0x6d].value = 0x7f; + break; + default: + dev->reg[reg_0x6a].value = 0x40; /* table two fast moving step type, PWM duty for table two */ + dev->reg[reg_0x6b].value = 0xff; /* table two steps number for acc/dec */ + dev->reg[reg_0x6d].value = 0x01; /* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */ + break; + } + dev->reg[reg_0x6c].value = 0x00; /* peroid times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE (one period time) */ +} + + +/* Send slope table for motor movement + slope_table in machine byte order +*/ +static SANE_Status +gl646_send_slope_table (Genesys_Device * dev, int table_nr, + uint16_t * slope_table, int steps) +{ + int dpihw; + int start_address; + SANE_Status status; + uint8_t *table; +#ifdef WORDS_BIGENDIAN + int i; +#endif + + DBG (DBG_proc, + "gl646_send_slope_table (table_nr = %d, steps = %d)=%d .. %d\n", + table_nr, steps, slope_table[0], slope_table[steps - 1]); + + dpihw = dev->reg[reg_0x05].value >> 6; + + if (dpihw == 0) /* 600 dpi */ + start_address = 0x08000; + else if (dpihw == 1) /* 1200 dpi */ + start_address = 0x10000; + else if (dpihw == 2) /* 2400 dpi */ + start_address = 0x1f800; + else /* reserved */ + return SANE_STATUS_INVAL; + +#ifdef WORDS_BIGENDIAN + table = (uint8_t *) malloc (steps * 2); + for (i = 0; i < steps; i++) + { + table[i * 2] = slope_table[i] & 0xff; + table[i * 2 + 1] = slope_table[i] >> 8; + } +#else + table = (uint8_t *) slope_table; +#endif + + status = + sanei_genesys_set_buffer_address (dev, start_address + table_nr * 0x100); + if (status != SANE_STATUS_GOOD) + { +#ifdef WORDS_BIGENDIAN + free (table); +#endif + DBG (DBG_error, + "gl646_send_slope_table: failed to set buffer address: %s\n", + sane_strstatus (status)); + return status; + } + + status = gl646_bulk_write_data (dev, 0x3c, (uint8_t *) table, steps * 2); + if (status != SANE_STATUS_GOOD) + { +#ifdef WORDS_BIGENDIAN + free (table); +#endif + DBG (DBG_error, + "gl646_send_slope_table: failed to send slope table: %s\n", + sane_strstatus (status)); + return status; + } + +#ifdef WORDS_BIGENDIAN + free (table); +#endif + DBG (DBG_proc, "gl646_send_slope_table: end\n"); + return status; +} + +/* Set values of Analog Device type frontend */ +static SANE_Status +gl646_set_ad_fe (Genesys_Device * dev, uint8_t set) +{ + SANE_Status status = SANE_STATUS_GOOD; + int i; + uint16_t val; + + DBG (DBG_proc, "gl646_set_ad_fe(): start\n"); + if (set == AFE_INIT) + { + DBG (DBG_proc, "gl646_set_ad_fe(): setting DAC %u\n", + dev->model->dac_type); + + /* sets to default values */ + sanei_genesys_init_fe (dev); + + /* write them to analog frontend */ + val = dev->frontend.reg[0]; + status = sanei_genesys_fe_write_data (dev, 0x00, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_ad_fe: failed to write reg0: %s\n", + sane_strstatus (status)); + return status; + } + val = dev->frontend.reg[1]; + status = sanei_genesys_fe_write_data (dev, 0x01, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_ad_fe: failed to write reg1: %s\n", + sane_strstatus (status)); + return status; + } + } + if (set == AFE_SET) + { + for (i = 0; i < 3; i++) + { + val = dev->frontend.gain[i]; + status = sanei_genesys_fe_write_data (dev, 0x02 + i, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_set_ad_fe: failed to write gain %d: %s\n", i, + sane_strstatus (status)); + return status; + } + } + for (i = 0; i < 3; i++) + { + val = dev->frontend.offset[i]; + status = sanei_genesys_fe_write_data (dev, 0x05 + i, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_set_ad_fe: failed to write offset %d: %s\n", i, + sane_strstatus (status)); + return status; + } + } + } + /* + if (set == AFE_POWER_SAVE) + { + status = + sanei_genesys_fe_write_data (dev, 0x00, dev->frontend.reg[0] | 0x04); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_ad_fe: failed to write reg0: %s\n", + sane_strstatus (status)); + return status; + } + } */ + DBG (DBG_proc, "gl646_set_ad_fe(): end\n"); + + return status; +} + +/** set up analog frontend + * set up analog frontend + * @param dev device to set up + * @param set action from AFE_SET, AFE_INIT and AFE_POWERSAVE + * @param dpi resolution of the scan since it affects settings + * @return SANE_STATUS_GOOD if evrithing OK + */ +static SANE_Status +gl646_wm_hp3670 (Genesys_Device * dev, uint8_t set, int dpi) +{ + SANE_Status status = SANE_STATUS_GOOD; + int i; + + DBG (DBG_proc, "gl646_wm_hp3670: start \n"); + switch (set) + { + case AFE_INIT: + status = sanei_genesys_fe_write_data (dev, 0x04, 0x80); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: reset failed: %s\n", + sane_strstatus (status)); + return status; + } + usleep (200000UL); + RIE (sanei_genesys_write_register (dev, 0x50, 0x00)); + sanei_genesys_init_fe (dev); + status = sanei_genesys_fe_write_data (dev, 0x01, dev->frontend.reg[1]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing reg1 failed: %s\n", + sane_strstatus (status)); + return status; + } + status = sanei_genesys_fe_write_data (dev, 0x02, dev->frontend.reg[2]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing reg2 failed: %s\n", + sane_strstatus (status)); + return status; + } + status = gl646_gpio_output_enable (dev->dn, 0x07); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: failed to enable GPIO: %s\n", + sane_strstatus (status)); + return status; + } + break; + case AFE_POWER_SAVE: + status = sanei_genesys_fe_write_data (dev, 0x01, 0x06); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing reg1 failed: %s\n", + sane_strstatus (status)); + return status; + } + status = sanei_genesys_fe_write_data (dev, 0x06, 0x0f); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing reg6 failed: %s\n", + sane_strstatus (status)); + return status; + } + return status; + break; + default: /* AFE_SET */ + /* mode setup */ + i = dev->frontend.reg[3]; + if (dpi > dev->sensor.optical_res / 2) + { + /* fe_reg_0x03 must be 0x12 for 1200 dpi in DAC_WOLFSON_HP3670. + * DAC_WOLFSON_HP2400 in 1200 dpi mode works well with + * fe_reg_0x03 set to 0x32 or 0x12 but not to 0x02 */ + i = 0x12; + } + status = sanei_genesys_fe_write_data (dev, 0x03, i); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing reg3 failed: %s\n", + sane_strstatus (status)); + return status; + } + /* offset and sign (or msb/lsb ?) */ + for (i = 0; i < 3; i++) + { + status = + sanei_genesys_fe_write_data (dev, 0x20 + i, + dev->frontend.offset[i]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_wm_hp3670: writing offset%d failed: %s\n", i, + sane_strstatus (status)); + return status; + } + status = sanei_genesys_fe_write_data (dev, 0x24 + i, dev->frontend.sign[i]); /* MSB/LSB ? */ + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing sign%d failed: %s\n", + i, sane_strstatus (status)); + return status; + } + } + + /* gain */ + for (i = 0; i < 3; i++) + { + status = + sanei_genesys_fe_write_data (dev, 0x28 + i, + dev->frontend.gain[i]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_wm_hp3670: writing gain%d failed: %s\n", + i, sane_strstatus (status)); + return status; + } + } + } + + DBG (DBG_proc, "gl646_wm_hp3670: success \n"); + return status; +} + +/** Set values of analog frontend + * @param dev device to set + * @param set action to execute + * @param dpi dpi to setup the AFE + * @return error or SANE_STATUS_GOOD */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +gl646_set_fe (Genesys_Device * dev, uint8_t set, int dpi) +{ + SANE_Status status; + int i; + uint8_t val; + + DBG (DBG_proc, "gl646_set_fe (%s,%d)\n", + set == AFE_INIT ? "init" : set == AFE_SET ? "set" : set == + AFE_POWER_SAVE ? "powersave" : "huh?", dpi); + + /* Analog Device type frontend */ + if ((dev->reg[reg_0x04].value & REG04_FESET) == 0x02) + return gl646_set_ad_fe (dev, set); + + /* Wolfson type frontend */ + if ((dev->reg[reg_0x04].value & REG04_FESET) != 0x03) + { + DBG (DBG_proc, "gl646_set_fe(): unspported frontend type %d\n", + dev->reg[reg_0x04].value & REG04_FESET); + return SANE_STATUS_UNSUPPORTED; + } + + /* per frontend function to keep code clean */ + switch (dev->model->dac_type) + { + case DAC_WOLFSON_HP3670: + case DAC_WOLFSON_HP2400: + return gl646_wm_hp3670 (dev, set, dpi); + break; + default: + DBG (DBG_proc, "gl646_set_fe(): using old method\n"); + break; + } + + /* initialize analog frontend */ + if (set == AFE_INIT) + { + DBG (DBG_proc, "gl646_set_fe(): setting DAC %u\n", + dev->model->dac_type); + sanei_genesys_init_fe (dev); + + /* reset only done on init */ + status = sanei_genesys_fe_write_data (dev, 0x04, 0x80); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: init fe failed: %s\n", + sane_strstatus (status)); + return status; + } + + /* enable GPIO for some models */ + if (dev->model->ccd_type == CCD_HP2300) + { + val = 0x07; + status = gl646_gpio_output_enable (dev->dn, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_set_fe: failed to enable GPIO: %s\n", + sane_strstatus (status)); + return status; + } + } + return status; + } + + /* set fontend to power saving mode */ + if (set == AFE_POWER_SAVE) + { + status = sanei_genesys_fe_write_data (dev, 0x01, 0x02); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing data failed: %s\n", + sane_strstatus (status)); + } + return status; + } + + /* here starts AFE_SET */ + /* TODO : base this test on cfg reg3 or a CCD family flag to be created */ + /* if (dev->model->ccd_type != CCD_HP2300 + && dev->model->ccd_type != CCD_HP3670 + && dev->model->ccd_type != CCD_HP2400) */ + { + status = sanei_genesys_fe_write_data (dev, 0x00, dev->frontend.reg[0]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing reg0 failed: %s\n", + sane_strstatus (status)); + return status; + } + status = sanei_genesys_fe_write_data (dev, 0x02, dev->frontend.reg[2]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing reg2 failed: %s\n", + sane_strstatus (status)); + return status; + } + } + + /* start with reg3 */ + status = sanei_genesys_fe_write_data (dev, 0x03, dev->frontend.reg[3]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing reg3 failed: %s\n", + sane_strstatus (status)); + return status; + } + + switch (dev->model->ccd_type) + { + default: + for (i = 0; i < 3; i++) + { + status = + sanei_genesys_fe_write_data (dev, 0x24 + i, + dev->frontend.sign[i]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing sign[%d] failed: %s\n", + i, sane_strstatus (status)); + return status; + } + + status = + sanei_genesys_fe_write_data (dev, 0x28 + i, + dev->frontend.gain[i]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing gain[%d] failed: %s\n", + i, sane_strstatus (status)); + return status; + } + + status = + sanei_genesys_fe_write_data (dev, 0x20 + i, + dev->frontend.offset[i]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_set_fe: writing offset[%d] failed: %s\n", i, + sane_strstatus (status)); + return status; + } + } + break; + /* just can't have it to work .... + case CCD_HP2300: + case CCD_HP2400: + case CCD_HP3670: + + status = + sanei_genesys_fe_write_data (dev, 0x23, dev->frontend.offset[1]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_set_fe: writing offset[1] failed: %s\n", + sane_strstatus (status)); + return status; + } + status = sanei_genesys_fe_write_data (dev, 0x28, dev->frontend.gain[1]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing gain[1] failed: %s\n", + sane_strstatus (status)); + return status; + } + break; */ + } + + /* end with reg1 */ + status = sanei_genesys_fe_write_data (dev, 0x01, dev->frontend.reg[1]); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_set_fe: writing reg1 failed: %s\n", + sane_strstatus (status)); + return status; + } + + + DBG (DBG_proc, "gl646_set_fe: end\n"); + + return SANE_STATUS_GOOD; +} + +/** Set values of analog frontend + * this this the public interface, the gl646 as to use one more + * parameter to work effectively, hence the redirection + * @param dev device to set + * @param set action to execute + * @return error or SANE_STATUS_GOOD */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +gl646_public_set_fe (Genesys_Device * dev, uint8_t set) +{ + return gl646_set_fe (dev, set, dev->settings.yres); +} + +static void +gl646_set_motor_power (Genesys_Register_Set * regs, SANE_Bool set) +{ + if (set) + { + sanei_genesys_set_reg_from_set (regs, 0x02, + sanei_genesys_read_reg_from_set (regs, + 0x02) | + REG02_MTRPWR); + } + else + { + sanei_genesys_set_reg_from_set (regs, 0x02, + sanei_genesys_read_reg_from_set (regs, + 0x02) & + ~REG02_MTRPWR); + } +} + +static void +gl646_set_lamp_power (Genesys_Device * dev, + Genesys_Register_Set * regs, SANE_Bool set) +{ + if (dev) + { + if (set) + { + sanei_genesys_set_reg_from_set (regs, 0x03, + sanei_genesys_read_reg_from_set + (regs, 0x03) | REG03_LAMPPWR); + } + else + { + sanei_genesys_set_reg_from_set (regs, 0x03, + sanei_genesys_read_reg_from_set + (regs, 0x03) & ~REG03_LAMPPWR); + } + } +} + +/** + * enters or leaves power saving mode + * limited to AFE for now. + * @param dev scanner's device + * @param enable SANE_TRUE to enable power saving, SANE_FALSE to leave it + * @return allways SANE_STATUS_GOOD + */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +gl646_save_power (Genesys_Device * dev, SANE_Bool enable) +{ + + DBG (DBG_proc, "gl646_save_power: start\n"); + DBG (DBG_info, "gl646_save_power: enable = %d\n", enable); + + if (enable) + { + /* gl646_set_fe (dev, AFE_POWER_SAVE); */ + } + else + { + gl646_set_fe (dev, AFE_INIT, 0); + } + + DBG (DBG_proc, "gl646_save_power: end\n"); + return SANE_STATUS_GOOD; +} + +static SANE_Status +gl646_set_powersaving (Genesys_Device * dev, int delay /* in minutes */ ) +{ + SANE_Status status = SANE_STATUS_GOOD; + Genesys_Register_Set local_reg[6]; + int rate, exposure_time, tgtime, time; + + DBG (DBG_proc, "gl646_set_powersaving (delay = %d)\n", delay); + + local_reg[0].address = 0x01; + local_reg[0].value = sanei_genesys_read_reg_from_set (dev->reg, 0x01); /* disable fastmode */ + + local_reg[1].address = 0x03; + local_reg[1].value = sanei_genesys_read_reg_from_set (dev->reg, 0x03); /* Lamp power control */ + + local_reg[2].address = 0x05; + local_reg[2].value = sanei_genesys_read_reg_from_set (dev->reg, 0x05) & ~REG05_BASESEL; /* 24 clocks/pixel */ + + local_reg[3].address = 0x38; /* line period low */ + local_reg[3].value = 0x00; + + local_reg[4].address = 0x39; /* line period high */ + local_reg[4].value = 0x00; + + local_reg[5].address = 0x6c; /* period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE */ + local_reg[5].value = 0x00; + + if (!delay) + local_reg[1].value = local_reg[1].value & 0xf0; /* disable lampdog and set lamptime = 0 */ + else if (delay < 20) + local_reg[1].value = (local_reg[1].value & 0xf0) | 0x09; /* enable lampdog and set lamptime = 1 */ + else + local_reg[1].value = (local_reg[1].value & 0xf0) | 0x0f; /* enable lampdog and set lamptime = 7 */ + + time = delay * 1000 * 60; /* -> msec */ + exposure_time = + (uint32_t) (time * 32000.0 / + (24.0 * 64.0 * (local_reg[1].value & REG03_LAMPTIM) * + 1024.0) + 0.5); + /* 32000 = system clock, 24 = clocks per pixel */ + rate = (exposure_time + 65536) / 65536; + if (rate > 4) + { + rate = 8; + tgtime = 3; + } + else if (rate > 2) + { + rate = 4; + tgtime = 2; + } + else if (rate > 1) + { + rate = 2; + tgtime = 1; + } + else + { + rate = 1; + tgtime = 0; + } + + local_reg[5].value |= tgtime << 6; + exposure_time /= rate; + + if (exposure_time > 65535) + exposure_time = 65535; + + local_reg[3].value = exposure_time / 256; /* highbyte */ + local_reg[4].value = exposure_time & 255; /* lowbyte */ + + status = gl646_bulk_write_register (dev, local_reg, + sizeof (local_reg) / + sizeof (local_reg[0])); + if (status != SANE_STATUS_GOOD) + DBG (DBG_error, + "gl646_set_powersaving: Failed to bulk write registers: %s\n", + sane_strstatus (status)); + + DBG (DBG_proc, "gl646_set_powersaving: end\n"); + return status; +} + + +/** + * loads document into scanner + * currently only used by XP200 + * bit2 (0x04) of gpio is paper event (document in/out) on XP200 + * HOMESNR is set if no document in front of sensor, the sequence of events is + * paper event -> document is in the sheet feeder + * HOMESNR becomes 0 -> document reach sensor + * HOMESNR becomes 1 ->document left sensor + * paper event -> document is out + */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +gl646_load_document (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + Genesys_Register_Set regs[11]; + unsigned int used, vfinal, count; + uint16_t slope_table[255]; + uint8_t val; + + DBG (DBG_proc, "gl646_load_document: start\n"); + + /* no need to load document is flatbed scanner */ + if (dev->model->is_sheetfed == SANE_FALSE) + { + DBG (DBG_proc, "gl646_load_document: nothing to load\n"); + DBG (DBG_proc, "gl646_load_document: end\n"); + return SANE_STATUS_GOOD; + } + + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to read status: %s\n", + sane_strstatus (status)); + return status; + } + + /* HOMSNR is set if a document is inserted */ + if ((val & REG41_HOMESNR)) + { + /* if no document, waits for a paper event to start loading */ + /* with a 60 seconde minutes timeout */ + count = 0; + do + { + status = gl646_gpio_read (dev->dn, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to read paper sensor %s\n", + sane_strstatus (status)); + return status; + } + DBG (DBG_info, "gl646_load_document: GPIO=0x%02x\n", val); + if ((val & 0x04) != 0x04) + { + DBG (DBG_warn, "gl646_load_document: no paper detected\n"); + } + usleep (200000UL); /* sleep 200 ms */ + count++; + } + while (((val & 0x04) != 0x04) && (count < 300)); /* 1 min time out */ + if (count == 300) + { + DBG (DBG_error, + "gl646_load_document: timeout waiting for document\n"); + return SANE_STATUS_NO_DOCS; + } + } + + /* set up to fast move before scan then move until document is detected */ + regs[0].address = 0x01; + regs[0].value = 0x90; + + /* AGOME, 2 slopes motor moving */ + regs[1].address = 0x02; + regs[1].value = 0x79; + + /* motor feeding steps to 0 */ + regs[2].address = 0x3d; + regs[2].value = 0; + regs[3].address = 0x3e; + regs[3].value = 0; + regs[4].address = 0x3f; + regs[4].value = 0; + + /* 50 fast moving steps */ + regs[5].address = 0x6b; + regs[5].value = 50; + + /* set GPO */ + regs[6].address = 0x66; + regs[6].value = 0x30; + + /* stesp NO */ + regs[7].address = 0x21; + regs[7].value = 4; + regs[8].address = 0x22; + regs[8].value = 1; + regs[9].address = 0x23; + regs[9].value = 1; + regs[10].address = 0x24; + regs[10].value = 4; + + /* generate slope table 2 */ + sanei_genesys_generate_slope_table (slope_table, + 50, + 51, + 2400, + 6000, 2400, 50, 0.25, &used, &vfinal); +/* document loading: + * send regs + * start motor + * wait e1 status to become e0 + */ + status = gl646_send_slope_table (dev, 1, slope_table, 50); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to send slope table 1: %s\n", + sane_strstatus (status)); + return status; + } + status = + gl646_bulk_write_register (dev, regs, sizeof (regs) / sizeof (regs[0])); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to bulk write registers: %s\n", + sane_strstatus (status)); + return status; + } + + status = gl646_start_motor (dev); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to start motor: %s\n", + sane_strstatus (status)); + return SANE_STATUS_IO_ERROR; + } + + count = 0; + do + { + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to read status: %s\n", + sane_strstatus (status)); + return status; + } + usleep (200000UL); /* sleep 200 ms */ + count++; + } + while ((val & REG41_MOTMFLG) && (count < 300)); + if (count == 300) + { + DBG (DBG_error, "gl646_load_document: can't load document\n"); + return SANE_STATUS_JAMMED; + } + + /* when loading OK, document is here */ + dev->document = SANE_TRUE; + + /* set up to idle */ + regs[1].value = 0x71; + regs[4].value = 1; + regs[5].value = 8; + status = + gl646_bulk_write_register (dev, regs, sizeof (regs) / sizeof (regs[0])); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_load_document: failed to bulk write idle registers: %s\n", + sane_strstatus (status)); + return status; + } + + DBG (DBG_proc, "gl646_load_document: end\n"); + + return status; +} + +/** + * detects end of document and adjust current scan + * to take it into account + * used by sheetfed scanners + */ +static SANE_Status +gl646_detect_document_end (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + uint8_t val, gpio; + unsigned int bytes_left, lines; + + DBG (DBG_proc, "gl646_detect_document_end: start\n"); + + /* test for document presence */ + RIE (sanei_genesys_get_status (dev, &val)); + if (DBG_LEVEL > DBG_info) + { + print_status (val); + } + status = gl646_gpio_read (dev->dn, &gpio); + DBG (DBG_info, "gl646_detect_document_end: GPIO=0x%02x\n", gpio); + + /* detect document event. There one event when the document go in, + * then another when it leaves */ + if ((dev->document == SANE_TRUE) && (gpio & 0x04) + && (dev->total_bytes_read > 0)) + { + DBG (DBG_info, "gl646_detect_document_end: no more document\n"); + dev->document = SANE_FALSE; + + /* adjust number of bytes to read: + * total_bytes_to_read is the number of byte to send to frontend + * total_bytes_read is the number of bytes sent to frontend + * read_bytes_left is the number of bytes to read from the scanner + */ + DBG (DBG_io, "gl646_detect_document_end: total_bytes_to_read=%lu\n", + (u_long) dev->total_bytes_to_read); + DBG (DBG_io, "gl646_detect_document_end: total_bytes_read =%lu\n", + (u_long) dev->total_bytes_read); + DBG (DBG_io, "gl646_detect_document_end: read_bytes_left =%lu\n", + (u_long) dev->read_bytes_left); + + /* amount of data available from scanner is what to scan */ + status = sanei_genesys_read_valid_words (dev, &bytes_left); + + /* we add the number of lines needed to read the last part of the document in */ + lines = + (SANE_UNFIX (dev->model->y_offset) * dev->current_setup.yres) / + MM_PER_INCH; + DBG (DBG_io, "gl646_detect_document_end: adding %d line to flush\n", + lines); + bytes_left += lines * dev->wpl; + if (dev->current_setup.depth > 8) + { + bytes_left = 2 * bytes_left; + } + if (dev->current_setup.channels > 1) + { + bytes_left = 3 * bytes_left; + } + if (bytes_left < dev->read_bytes_left) + { + dev->total_bytes_to_read = dev->total_bytes_read + bytes_left; + dev->read_bytes_left = bytes_left; + } + DBG (DBG_io, "gl646_detect_document_end: total_bytes_to_read=%lu\n", + (u_long) dev->total_bytes_to_read); + DBG (DBG_io, "gl646_detect_document_end: total_bytes_read =%lu\n", + (u_long) dev->total_bytes_read); + DBG (DBG_io, "gl646_detect_document_end: read_bytes_left =%lu\n", + (u_long) dev->read_bytes_left); + } + DBG (DBG_proc, "gl646_detect_document_end: end\n"); + + return status; +} + +/** + * eject document from the feeder + * currently only used by XP200 + * TODO we currently rely on AGOHOME not being set for sheetfed scanners, + * maybe check this flag in eject to let the document being eject automaticaly + */ +static SANE_Status +gl646_eject_document (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + Genesys_Register_Set regs[11]; + unsigned int used, vfinal, count; + uint16_t slope_table[255]; + uint8_t gpio, state; + + DBG (DBG_proc, "gl646_eject_document: start\n"); + + /* at the end there will be noe more document */ + dev->document = SANE_FALSE; + + /* first check for document event */ + status = gl646_gpio_read (dev->dn, &gpio); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to read paper sensor %s\n", + sane_strstatus (status)); + return status; + } + DBG (DBG_info, "gl646_eject_document: GPIO=0x%02x\n", gpio); + + /* test status : paper event + HOMESNR -> no more doc ? */ + status = sanei_genesys_get_status (dev, &state); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to read status: %s\n", + sane_strstatus (status)); + return status; + } + DBG (DBG_info, "gl646_eject_document: state=0x%02x\n", state); + if (DBG_LEVEL > DBG_info) + { + print_status (state); + } + + /* HOMSNR=0 if no document inserted */ + if ((state & REG41_HOMESNR) != 0) + { + dev->document = SANE_FALSE; + DBG (DBG_info, "gl646_eject_document: no more document to eject\n"); + DBG (DBG_proc, "gl646_eject_document: end\n"); + return status; + } + + /* there is a document inserted, eject it */ + status = sanei_genesys_write_register (dev, 0x01, 0xb0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to write register: %s\n", + sane_strstatus (status)); + return status; + } + + /* wait for motor to stop */ + do + { + usleep (200000UL); + status = sanei_genesys_get_status (dev, &state); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to read status: %s\n", + sane_strstatus (status)); + return status; + } + } + while (state & REG41_MOTMFLG); + + /* set up to fast move before scan then move until document is detected */ + regs[0].address = 0x01; + regs[0].value = 0xb0; + + /* AGOME, 2 slopes motor moving , eject 'backward' */ + regs[1].address = 0x02; + regs[1].value = 0x5d; + + /* motor feeding steps to 119880 */ + regs[2].address = 0x3d; + regs[2].value = 1; + regs[3].address = 0x3e; + regs[3].value = 0xd4; + regs[4].address = 0x3f; + regs[4].value = 0x48; + + /* 60 fast moving steps */ + regs[5].address = 0x6b; + regs[5].value = 60; + + /* set GPO */ + regs[6].address = 0x66; + regs[6].value = 0x30; + + /* stesp NO */ + regs[7].address = 0x21; + regs[7].value = 4; + regs[8].address = 0x22; + regs[8].value = 1; + regs[9].address = 0x23; + regs[9].value = 1; + regs[10].address = 0x24; + regs[10].value = 4; + + /* generate slope table 2 */ + sanei_genesys_generate_slope_table (slope_table, + 60, + 61, + 1600, + 10000, 1600, 60, 0.25, &used, &vfinal); +/* document eject: + * send regs + * start motor + * wait c1 status to become c8 : HOMESNR and ~MOTFLAG + */ + status = gl646_send_slope_table (dev, 1, slope_table, 60); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to send slope table 1: %s\n", + sane_strstatus (status)); + return status; + } + status = + gl646_bulk_write_register (dev, regs, sizeof (regs) / sizeof (regs[0])); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to bulk write registers: %s\n", + sane_strstatus (status)); + return status; + } + + status = gl646_start_motor (dev); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to start motor: %s\n", + sane_strstatus (status)); + return SANE_STATUS_IO_ERROR; + } + + /* loop until paper sensor tells paper is out, and till motor is running */ + /* use a 30 timeout */ + count = 0; + do + { + status = sanei_genesys_get_status (dev, &state); + print_status (state); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to read status: %s\n", + sane_strstatus (status)); + return status; + } + usleep (200000UL); /* sleep 200 ms */ + count++; + } + while (((state & REG41_HOMESNR) == 0) && (count < 150)); + + /* read GPIO on exit */ + status = gl646_gpio_read (dev->dn, &gpio); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_eject_document: failed to read paper sensor %s\n", + sane_strstatus (status)); + return status; + } + DBG (DBG_info, "gl646_eject_document: GPIO=0x%02x\n", gpio); + + DBG (DBG_proc, "gl646_eject_document: end\n"); + return status; +} + +/* Send the low-level scan command */ +static SANE_Status +gl646_begin_scan (Genesys_Device * dev, Genesys_Register_Set * reg, + SANE_Bool start_motor) +{ + SANE_Status status; + Genesys_Register_Set local_reg[3]; + + DBG (DBG_proc, "gl646_begin_scan\n"); + + local_reg[0].address = 0x03; + local_reg[0].value = sanei_genesys_read_reg_from_set (reg, 0x03); + + local_reg[1].address = 0x01; + local_reg[1].value = sanei_genesys_read_reg_from_set (reg, 0x01) | REG01_SCAN; /* set scan bit */ + + local_reg[2].address = 0x0f; + if (start_motor) + local_reg[2].value = 0x01; + else + local_reg[2].value = 0x00; /* do not start motor yet */ + + status = gl646_bulk_write_register (dev, local_reg, + sizeof (local_reg) / + sizeof (local_reg[0])); + + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_begin_scan: failed to bulk write registers: %s\n", + sane_strstatus (status)); + return status; + } + + DBG (DBG_proc, "gl646_begin_scan: end\n"); + + return status; +} + + +/* Send the stop scan command */ +static SANE_Status +end_scan (Genesys_Device * dev, Genesys_Register_Set * reg, + SANE_Bool check_stop, SANE_Bool eject) +{ + SANE_Status status = SANE_STATUS_GOOD; + int i = 0; + uint8_t val, scanfsh = 0; + + DBG (DBG_proc, "end_scan (check_stop = %d, eject = %d)\n", check_stop, + eject); + + /* we need to compute scanfsh before cancelling scan */ + if (dev->model->is_sheetfed == SANE_TRUE) + { + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "end_scan: failed to read register: %s\n", + sane_strstatus (status)); + return status; + } + if (val & REG41_SCANFSH) + scanfsh = 1; + if (DBG_LEVEL > DBG_io2) + { + print_status (val); + } + } + + /* ends scan */ + val = sanei_genesys_read_reg_from_set (reg, 0x01); + val &= ~REG01_SCAN; + sanei_genesys_set_reg_from_set (reg, 0x01, val); + status = sanei_genesys_write_register (dev, 0x01, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "end_scan: failed to write register 01: %s\n", + sane_strstatus (status)); + return status; + } + + /* for sheetfed scanners, we may have to eject document */ + if (dev->model->is_sheetfed == SANE_TRUE) + { + if (eject == SANE_TRUE && dev->document == SANE_TRUE) + { + status = gl646_eject_document (dev); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "end_scan: failed to eject document\n"); + return status; + } + } + if (check_stop) + { + for (i = 0; i < 30; i++) /* do not wait longer than wait 3 seconds */ + { + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "end_scan: failed to read register: %s\n", + sane_strstatus (status)); + return status; + } + if (val & REG41_SCANFSH) + scanfsh = 1; + if (DBG_LEVEL > DBG_io2) + { + print_status (val); + } + + if (!(val & REG41_MOTMFLG) && (val & REG41_FEEDFSH) && scanfsh) + { + DBG (DBG_proc, "end_scan: scanfeed finished\n"); + break; /* leave for loop */ + } + + usleep (10000UL); /* sleep 100 ms */ + } + } + } + else /* flat bed scanners */ + { + if (check_stop) + { + for (i = 0; i < 300; i++) /* do not wait longer than wait 30 seconds */ + { + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "end_scan: failed to read register: %s\n", + sane_strstatus (status)); + return status; + } + if (val & REG41_SCANFSH) + scanfsh = 1; + if (DBG_LEVEL > DBG_io) + { + print_status (val); + } + + if (!(val & REG41_MOTMFLG) && (val & REG41_FEEDFSH) && scanfsh) + { + DBG (DBG_proc, "end_scan: scanfeed finished\n"); + break; /* leave while loop */ + } + + if ((!(val & REG41_MOTMFLG)) && (val & REG41_HOMESNR)) + { + DBG (DBG_proc, "end_scan: head at home\n"); + break; /* leave while loop */ + } + + usleep (10000UL); /* sleep 100 ms */ + } + } + } + + DBG (DBG_proc, "end_scan: end (i=%u)\n", i); + + return status; +} + +/* Send the stop scan command */ +static SANE_Status +gl646_end_scan (Genesys_Device * dev, Genesys_Register_Set * reg, + SANE_Bool check_stop) +{ + return end_scan (dev, reg, check_stop, SANE_FALSE); +} + +/** + * parks head + * @param dev scanner's device + * @param wait_until_home true if the function waits until head parked + */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +gl646_slow_back_home (Genesys_Device * dev, SANE_Bool wait_until_home) +{ + SANE_Status status; + Genesys_Settings settings; + uint8_t val; + int i; + int loop = 0; + + DBG (DBG_proc, "gl646_slow_back_home: start , wait_until_home = %d\n", + wait_until_home); + + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_slow_back_home: failed to read home sensor: %s\n", + sane_strstatus (status)); + return status; + } + if (DBG_LEVEL > DBG_io) + { + print_status (val); + } + + dev->scanhead_position_in_steps = 0; + + if (val & REG41_HOMESNR) /* is sensor at home? */ + { + DBG (DBG_info, "gl646_slow_back_home: end since already at home\n"); + return SANE_STATUS_GOOD; + } + + /* stop motor if needed */ + if (val & REG41_MOTMFLG) + { + status = gl646_stop_motor (dev); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_slow_back_home: failed to stop motor: %s\n", + sane_strstatus (status)); + return SANE_STATUS_IO_ERROR; + } + usleep (200000UL); + } + + /* when scanhead is moving then wait until scanhead stops or timeout */ + DBG (DBG_info, "gl646_slow_back_home: ensuring that motor is off\n"); + val = REG41_MOTMFLG; + for (i = 400; i > 0 && (val & REG41_MOTMFLG); i--) /* do not wait longer than 40 seconds, count down to get i = 0 when busy */ + { + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_slow_back_home: Failed to read home sensor & motor status: %s\n", + sane_strstatus (status)); + return status; + } + if (((val & (REG41_MOTMFLG | REG41_HOMESNR)) == REG41_HOMESNR)) /* at home and motor is off */ + { + DBG (DBG_info, + "gl646_slow_back_home: already at home and not moving\n"); + return SANE_STATUS_GOOD; + } + usleep (100 * 1000); /* sleep 100 ms (todo: fixed to really sleep 100 ms) */ + } + + if (!i) /* the loop counted down to 0, scanner still is busy */ + { + DBG (DBG_error, + "gl646_slow_back_home: motor is still on: device busy\n"); + return SANE_STATUS_DEVICE_BUSY; + } + + /* setup for a backward scan of 65535 steps, with no actual data reading */ + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_COLOR; + settings.xres = get_lowest_resolution (dev->model->ccd_type, SANE_FALSE); + settings.yres = settings.xres; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = 600; + settings.lines = 1; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + status = setup_for_scan (dev, settings, SANE_TRUE, SANE_TRUE, SANE_TRUE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "%s: failed to setup for scan: %s\n", __FUNCTION__, + sane_strstatus (status)); + DBGCOMPLETED; + return status; + } + + /* backward , no actual data scanned TODO more setup flags to avoid this register manipulations ? */ + dev->reg[reg_0x02].value |= REG02_MTRREV; + dev->reg[reg_0x01].value &= ~REG01_SCAN; + gl646_set_triple_reg (dev->reg, REG_FEEDL, 65535); + + /* sets frontend */ + status = gl646_set_fe (dev, AFE_SET, settings.xres); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "%s: failed to set frontend: %s\n", __FUNCTION__, + sane_strstatus (status)); + DBGCOMPLETED; + return status; + } + + /* write scan registers */ + status = gl646_bulk_write_register (dev, dev->reg, + sizeof (dev->reg) / + sizeof (dev->reg[0])); + if (status != SANE_STATUS_GOOD) + DBG (DBG_error, + "gl646_slow_back_home: failed to bulk write registers: %s\n", + sane_strstatus (status)); + + /* registers are restored to an iddl state, give up if no head to park */ + if (dev->model->is_sheetfed == SANE_TRUE) + { + DBG (DBG_proc, "gl646_slow_back_home: end \n"); + return SANE_STATUS_GOOD; + } + + /* starts scan */ + status = gl646_begin_scan (dev, dev->reg, SANE_TRUE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_slow_back_home: failed to begin scan: \n"); + return status; + } + + /* loop until head parked */ + if (wait_until_home) + { + while (loop < 300) /* do not wait longer then 30 seconds */ + { + status = sanei_genesys_get_status (dev, &val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_slow_back_home: Failed to read home sensor: %s\n", + sane_strstatus (status)); + return status; + } + + if (val & 0x08) /* home sensor */ + { + DBG (DBG_info, "gl646_slow_back_home: reached home position\n"); + DBG (DBG_proc, "gl646_slow_back_home: end\n"); + usleep (500000); /* sleep 500 ms before returning */ + return SANE_STATUS_GOOD; + } + usleep (100000); /* sleep 100 ms */ + ++loop; + } + + /* when we come here then the scanner needed too much time for this, so we better stop the motor */ + gl646_stop_motor (dev); + end_scan (dev, dev->reg, SANE_TRUE, SANE_FALSE); + DBG (DBG_error, + "gl646_slow_back_home: timeout while waiting for scanhead to go home\n"); + return SANE_STATUS_IO_ERROR; + } + + + DBG (DBG_info, "gl646_slow_back_home: scanhead is still moving\n"); + DBG (DBG_proc, "gl646_slow_back_home: end\n"); + return SANE_STATUS_GOOD; +} + +/** + * Automatically set top-left edge of the scan area by scanning an + * area at 300 dpi from very top of scanner + * @param dev device stucture describing the scanner + * @return SANE_STATUS_GOOD in cas of success, else failure code + */ +static SANE_Status +gl646_search_start_position (Genesys_Device * dev) +{ + SANE_Status status; + unsigned char *data = NULL; + Genesys_Settings settings; + unsigned int resolution, x, y; + + DBG (DBG_proc, "gl646_search_start_position: start\n"); + + /* we scan at 300 dpi */ + resolution = get_closest_resolution (dev->model->ccd_type, 300, SANE_FALSE); + + /* fill settings for a gray level scan */ + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_GRAY; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = 600; + settings.lines = dev->model->search_lines; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* scan the desired area */ + status = + simple_scan (dev, settings, SANE_TRUE, SANE_TRUE, SANE_FALSE, &data); + + /* process data if scan is OK */ + if (status == SANE_STATUS_GOOD) + { + /* handle stagger case : reorder gray data and thus loose some lines */ + if (dev->current_setup.stagger > 0) + { + DBG (DBG_proc, "gl646_search_start_position: 'un-staggering'\n"); + for (y = 0; y < settings.lines - dev->current_setup.stagger; y++) + { + /* one point out of 2 is 'unaligned' */ + for (x = 0; x < settings.pixels; x += 2) + { + data[y * settings.pixels + x] = + data[(y + dev->current_setup.stagger) * settings.pixels + + x]; + } + } + /* correct line number */ + settings.lines -= dev->current_setup.stagger; + } + if (DBG_LEVEL >= DBG_data) + { + sanei_genesys_write_pnm_file ("search_position.pnm", + data, + settings.depth, + 1, settings.pixels, settings.lines); + } + } + else + { + DBG (DBG_error, "gl646_search_start_position: simple_scan failed\n"); + free (data); + DBGCOMPLETED; + return status; + } + + /* now search reference points on the data */ + status = + sanei_genesys_search_reference_point (dev, data, + dev->sensor.CCD_start_xoffset, + resolution, settings.pixels, + settings.lines); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_search_start_position: failed to set search reference point: %s\n", + sane_strstatus (status)); + } + + free (data); + DBGCOMPLETED; + return status; +} + +/** + * internally overriden during effective calibration + * sets up register for coarse gain calibration + */ +static SANE_Status +gl646_init_regs_for_coarse_calibration (Genesys_Device * dev) +{ + DBG (DBG_proc, "gl646_init_regs_for_coarse_calibration\n"); + DBG (DBG_proc, "gl646_init_register_for_coarse_calibration: end\n"); + + /* to make compilers happy ... */ + if (!dev) + { + return SANE_STATUS_INVAL; + } + + return SANE_STATUS_GOOD; +} + + +/** + * init registers for shading calibration + * we assume that scanner's head is on an area suiting shading calibration. + * We scan a full scan width area by the shading line number for the device + * at either at full sensor's resolution or half depending upon half_ccd + * @param dev scanner's device + * @return SANE_STATUS_GOOD if success, else error code + */ +static SANE_Status +gl646_init_regs_for_shading (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + Genesys_Settings settings; + /* 1: no half_ccd, 2: use half number of pixels */ + int half_ccd = 1; + int cksel = 1; + + DBG (DBG_proc, "gl646_init_register_for_shading: start\n"); + + /* when shading all (full width) line, we must adapt to half_ccd case */ + if (dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE) + { + /* walk the master mode list to find if half_ccd */ + if (is_half_ccd (dev->model->ccd_type, dev->settings.xres, SANE_TRUE) == + SANE_TRUE) + { + half_ccd = 2; + } + } + + /* fill settings for scan : always a color scan */ + settings.scan_method = dev->settings.scan_method; + settings.scan_mode = dev->settings.scan_mode; + if (dev->model->is_cis == SANE_FALSE) + { + settings.scan_mode = SCAN_MODE_COLOR; + } + settings.xres = dev->sensor.optical_res / half_ccd; + cksel = get_cksel (dev->model->ccd_type, dev->settings.xres, SANE_TRUE); + settings.xres = settings.xres / cksel; + settings.yres = settings.xres; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = + (dev->sensor.sensor_pixels * settings.xres) / dev->sensor.optical_res; + dev->calib_lines = dev->model->shading_lines; + settings.lines = dev->calib_lines * (3 - half_ccd); + settings.depth = 16; + settings.color_filter = dev->settings.color_filter; + + settings.disable_interpolation = dev->settings.disable_interpolation; + settings.threshold = dev->settings.threshold; + settings.exposure_time = dev->settings.exposure_time; + settings.dynamic_lineart = SANE_FALSE; + + /* keep account of the movement for final scan move */ + dev->scanhead_position_in_steps += settings.lines; + + /* we don't want top offset, but we need right margin to be the same + * than the one for the final scan */ + status = setup_for_scan (dev, settings, SANE_TRUE, SANE_FALSE, SANE_FALSE); + + /* used when sending shading calibration data */ + dev->calib_pixels = settings.pixels; + dev->calib_channels = dev->current_setup.channels; + if (dev->model->is_cis == SANE_FALSE) + { + dev->calib_channels = 3; + } + + /* no shading */ + dev->reg[reg_0x01].value &= ~REG01_DVDSET; + dev->reg[reg_0x02].value |= REG02_ACDCDIS; /* ease backtracking */ + dev->reg[reg_0x02].value &= ~(REG02_FASTFED | REG02_AGOHOME); + dev->reg[reg_0x05].value &= ~REG05_GMMENB; + gl646_set_motor_power (dev->reg, SANE_FALSE); + + /* TODO another flag to setup regs ? */ + /* enforce needed LINCNT, getting rid of extra lines for color reordering */ + if (dev->model->is_cis == SANE_FALSE) + { + gl646_set_triple_reg (dev->reg, REG_LINCNT, dev->calib_lines); + } + else + { + gl646_set_triple_reg (dev->reg, REG_LINCNT, dev->calib_lines * 3); + } + + /* copy reg to calib_reg */ + memcpy (dev->calib_reg, dev->reg, + GENESYS_GL646_MAX_REGS * sizeof (Genesys_Register_Set)); + + /* this is an hack to make calibration cache working .... */ + /* if we don't do this, cache will be identified at the shading calibration + * dpi which is different from calibration one */ + dev->current_setup.xres = dev->settings.xres; + DBG (DBG_info, + "gl646_init_register_for_shading:\n\tdev->settings.xres=%d\n\tdev->settings.yres=%d\n", + dev->settings.xres, dev->settings.yres); + + DBG (DBG_proc, "gl646_init_register_for_shading: end\n"); + return status; +} + + +/** + * set up registers for the actual scan. The scan's parameters are given + * through the device settings. It allocates the scan buffers. + */ +static SANE_Status +gl646_init_regs_for_scan (Genesys_Device * dev) +{ + SANE_Status status; + + /* park head after calibration if needed */ + if (dev->scanhead_position_in_steps > 0 + && dev->settings.scan_method == SCAN_METHOD_FLATBED) + { + status = gl646_slow_back_home (dev, SANE_TRUE); + if (status != SANE_STATUS_GOOD) + { + return status; + + } + dev->scanhead_position_in_steps = 0; + } + + return setup_for_scan (dev, dev->settings, SANE_FALSE, SANE_TRUE, + SANE_TRUE); +} + +/** + * set up registers for the actual scan. The scan's parameters are given + * through the device settings. It allocates the scan buffers. + * @param dev scanner's device + * @param settings settings of scan + * @param split SANE_TRUE if move to scan area is split from scan, SANE_FALSE is + * scan first moves to area + * @param xcorrection take x geometry correction into account (fixed and detected offsets) + * @param ycorrection take y geometry correction into account + */ +static SANE_Status +setup_for_scan (Genesys_Device * dev, Genesys_Settings settings, + SANE_Bool split, SANE_Bool xcorrection, SANE_Bool ycorrection) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Bool color; + SANE_Int depth; + int channels; + uint16_t startx = 0, endx, pixels; + int move = 0; + + DBG (DBG_proc, "setup_for_scan: start\n"); + DBG (DBG_info, + "setup_for_scan settings:\nResolution: %ux%uDPI\n" + "Lines : %u\nPixels : %u\nStartpos : %.3f/%.3f\nScan mode : %d\nScan method: %s\n\n", + settings.xres, settings.yres, settings.lines, settings.pixels, + settings.tl_x, settings.tl_y, settings.scan_mode, + settings.scan_method == SCAN_METHOD_FLATBED ? "flatbed" : "XPA"); + + if (settings.scan_mode == SCAN_MODE_COLOR) /* single pass color */ + { + channels = 3; + color = SANE_TRUE; + } + else + { + channels = 1; + color = SANE_FALSE; + } + + depth=settings.depth; + if (settings.scan_mode == SCAN_MODE_LINEART) + { + if (settings.dynamic_lineart == SANE_TRUE) + { + depth = 8; + } + else + { + /* XXX STEF XXX : why does the common layer never send depth=1 ? */ + depth = 1; + } + } + + /* compute distance to move */ + move = 0; + /* XXX STEF XXX MD5345 -> optical_ydpi, other base_ydpi => half/full step ? */ + if (split == SANE_FALSE) + { + if (dev->model->is_sheetfed == SANE_FALSE) + { + if (ycorrection == SANE_TRUE) + { + move = + (SANE_UNFIX (dev->model->y_offset) * + dev->motor.optical_ydpi) / MM_PER_INCH; + } + + /* add tl_y to base movement */ + move += (settings.tl_y * dev->motor.optical_ydpi) / MM_PER_INCH; + + } + else + { + move += (settings.tl_y * dev->motor.optical_ydpi) / MM_PER_INCH; + } + + DBG (DBG_info, "setup_for_scan: move=%d steps\n", move); + + /* security check */ + if (move < 0) + { + DBG (DBG_error, + "setup_for_scan: overriding negative move value %d\n", move); + move = 0; + } + } + DBG (DBG_info, "setup_for_scan: move=%d steps\n", move); + + /* pixels are allways given at full CCD optical resolution */ + /* use detected left margin and fixed value */ + if (xcorrection == SANE_TRUE) + { + if (dev->sensor.CCD_start_xoffset > 0) + startx = dev->sensor.CCD_start_xoffset; + else + startx = dev->sensor.dummy_pixel; + if (settings.scan_method == SCAN_METHOD_FLATBED) + { + startx += + ((SANE_UNFIX (dev->model->x_offset) * dev->sensor.optical_res) / + MM_PER_INCH); + } + else + { + startx += + ((SANE_UNFIX (dev->model->x_offset_ta) * + dev->sensor.optical_res) / MM_PER_INCH); + } + } + else + { + /* startx cannot be below dummy pixel value */ + startx = dev->sensor.dummy_pixel; + } + + /* add x coordinates : expressed in sensor max dpi */ + startx += (settings.tl_x * dev->sensor.optical_res) / MM_PER_INCH; + + /* stagger works with odd start cordinates */ + if (dev->model->flags & GENESYS_FLAG_STAGGERED_LINE) + startx |= 1; + + pixels = (settings.pixels * dev->sensor.optical_res) / settings.xres; + /* special requirement for 400 dpi on 1200 dpi sensors */ + if (settings.xres == 400) + { + pixels = (pixels / 6) * 6; + } + endx = startx + pixels; + + /* TODO check for pixel width overflow */ + + /* set up correct values for scan (gamma and shading enabled) */ + status = gl646_setup_registers (dev, + dev->reg, + settings, + dev->slope_table0, + dev->slope_table1, + settings.xres, + move, + settings.lines, + startx, endx, color, + depth); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "setup_for_scan: failed setup registers: %s\n", + sane_strstatus (status)); + return status; + } + + /* now post-process values for register and options fine tuning */ + + /* select color filter based on settings */ + dev->reg[reg_0x04].value &= ~REG04_FILTER; + if (channels == 1) + { + switch (settings.color_filter) + { + /* red */ + case 0: + dev->reg[reg_0x04].value |= 0x04; + break; + /* green */ + case 1: + dev->reg[reg_0x04].value |= 0x08; + break; + /* blue */ + case 2: + dev->reg[reg_0x04].value |= 0x0c; + break; + default: + break; + } + } + + /* send computed slope tables */ + status = + gl646_send_slope_table (dev, 0, dev->slope_table0, + sanei_genesys_read_reg_from_set (dev->reg, 0x21)); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "setup_for_scan: failed to send slope table 0: %s\n", + sane_strstatus (status)); + return status; + } + + status = + gl646_send_slope_table (dev, 1, dev->slope_table1, + sanei_genesys_read_reg_from_set (dev->reg, 0x6b)); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "setup_for_scan: failed to send slope table 1: %s\n", + sane_strstatus (status)); + return status; + } + + DBG (DBG_proc, "setup_for_scan: end\n"); + return status; +} + +/** + * this function sen gamm table to ASIC + */ +static SANE_Status +gl646_send_gamma_table (Genesys_Device * dev) +{ + int size; + int address; + SANE_Status status; + uint8_t *gamma; + int bits; + + DBGSTART; + + /* gamma table size */ + if (dev->reg[reg_0x05].value & REG05_GMMTYPE) + { + size = 16384; + bits = 14; + } + else + { + size = 4096; + bits = 12; + } + + /* allocate temporary gamma tables: 16 bits words, 3 channels */ + gamma = (uint8_t *) malloc (size * 2 * 3); + if (gamma==NULL) + { + return SANE_STATUS_NO_MEM; + } + + RIE(sanei_genesys_generate_gamma_buffer(dev, bits, size-1, size, gamma)); + + /* table address */ + switch (dev->reg[reg_0x05].value >> 6) + { + case 0: /* 600 dpi */ + address = 0x09000; + break; + case 1: /* 1200 dpi */ + address = 0x11000; + break; + case 2: /* 2400 dpi */ + address = 0x20000; + break; + default: + free (gamma); + return SANE_STATUS_INVAL; + } + + /* send address */ + status = sanei_genesys_set_buffer_address (dev, address); + if (status != SANE_STATUS_GOOD) + { + free (gamma); + DBG (DBG_error, + "gl646_send_gamma_table: failed to set buffer address: %s\n", + sane_strstatus (status)); + return status; + } + + /* send data */ + status = gl646_bulk_write_data (dev, 0x3c, (uint8_t *) gamma, size * 2 * 3); + if (status != SANE_STATUS_GOOD) + { + free (gamma); + DBG (DBG_error, + "gl646_send_gamma_table: failed to send gamma table: %s\n", + sane_strstatus (status)); + return status; + } + free (gamma); + + DBGCOMPLETED; + return SANE_STATUS_GOOD; +} + +/** @brief this function does the led calibration. + * this function does the led calibration by scanning one line of the calibration + * area below scanner's top on white strip. The scope of this function is + * currently limited to the XP200 + */ +static SANE_Status +gl646_led_calibration (Genesys_Device * dev) +{ + int total_size; + uint8_t *line; + unsigned int i, j; + SANE_Status status = SANE_STATUS_GOOD; + int val; + unsigned int channels; + int avg[3], avga, avge; + int turn; + char fn[20]; + uint16_t expr, expg, expb; + Genesys_Settings settings; + SANE_Int resolution; + + SANE_Bool acceptable = SANE_FALSE; + + DBG (DBG_proc, "gl646_led_calibration\n"); + if (!dev->model->is_cis) + { + DBG (DBG_proc, + "gl646_led_calibration: not a cis scanner, nothing to do...\n"); + return SANE_STATUS_GOOD; + } + + /* get led calibration resolution */ + if (dev->settings.scan_mode == SCAN_MODE_COLOR) + { + resolution = + get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res, + SANE_TRUE); + settings.scan_mode = SCAN_MODE_COLOR; + channels = 3; + } + else + { + resolution = + get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res, + SANE_FALSE); + settings.scan_mode = SCAN_MODE_GRAY; + channels = 1; + } + + /* offset calibration is always done in color mode */ + settings.scan_method = SCAN_METHOD_FLATBED; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = + (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res; + settings.lines = 1; + settings.depth = 16; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* colors * bytes_per_color * scan lines */ + total_size = settings.pixels * channels * 2 * 1; + + line = malloc (total_size); + if (!line) + { + DBG (DBG_error, "gl646_led_calibration: failed to allocate %d bytes\n", + total_size); + return SANE_STATUS_NO_MEM; + } + +/* + we try to get equal bright leds here: + + loop: + average per color + adjust exposure times + + Sensor_Master uint8_t regs_0x10_0x15[6]; + */ + + expr = (dev->sensor.regs_0x10_0x1d[0] << 8) | dev->sensor.regs_0x10_0x1d[1]; + expg = (dev->sensor.regs_0x10_0x1d[2] << 8) | dev->sensor.regs_0x10_0x1d[3]; + expb = (dev->sensor.regs_0x10_0x1d[4] << 8) | dev->sensor.regs_0x10_0x1d[5]; + + turn = 0; + + do + { + + dev->sensor.regs_0x10_0x1d[0] = (expr >> 8) & 0xff; + dev->sensor.regs_0x10_0x1d[1] = expr & 0xff; + dev->sensor.regs_0x10_0x1d[2] = (expg >> 8) & 0xff; + dev->sensor.regs_0x10_0x1d[3] = expg & 0xff; + dev->sensor.regs_0x10_0x1d[4] = (expb >> 8) & 0xff; + dev->sensor.regs_0x10_0x1d[5] = expb & 0xff; + + DBG (DBG_info, "gl646_led_calibration: starting first line reading\n"); + + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line); + if (status != SANE_STATUS_GOOD) + { + free(line); + DBG (DBG_error, + "gl646_led_calibration: failed to setup scan: %s\n", + sane_strstatus (status)); + return status; + } + + if (DBG_LEVEL >= DBG_data) + { + snprintf (fn, 20, "led_%02d.pnm", turn); + sanei_genesys_write_pnm_file (fn, + line, + 16, channels, settings.pixels, 1); + } + + acceptable = SANE_TRUE; + + for (j = 0; j < channels; j++) + { + avg[j] = 0; + for (i = 0; i < settings.pixels; i++) + { + if (dev->model->is_cis) + val = + line[i * 2 + j * 2 * settings.pixels + 1] * 256 + + line[i * 2 + j * 2 * settings.pixels]; + else + val = + line[i * 2 * channels + 2 * j + 1] * 256 + + line[i * 2 * channels + 2 * j]; + avg[j] += val; + } + + avg[j] /= settings.pixels; + } + + DBG (DBG_info, "gl646_led_calibration: average: " + "%d,%d,%d\n", avg[0], avg[1], avg[2]); + + acceptable = SANE_TRUE; + + if (!acceptable) + { + avga = (avg[0] + avg[1] + avg[2]) / 3; + expr = (expr * avga) / avg[0]; + expg = (expg * avga) / avg[1]; + expb = (expb * avga) / avg[2]; + + /* keep exposure time in a working window */ + avge = (expr + expg + expb) / 3; + if (avge > 0x2000) + { + expr = (expr * 0x2000) / avge; + expg = (expg * 0x2000) / avge; + expb = (expb * 0x2000) / avge; + } + if (avge < 0x400) + { + expr = (expr * 0x400) / avge; + expg = (expg * 0x400) / avge; + expb = (expb * 0x400) / avge; + } + } + + turn++; + + } + while (!acceptable && turn < 100); + + DBG (DBG_info, + "gl646_led_calibration: acceptable exposure: 0x%04x,0x%04x,0x%04x\n", + expr, expg, expb); + + /* cleanup before return */ + free (line); + + DBGCOMPLETED; + return status; +} + +/** + * average dark pixels of a scan + */ +static int +dark_average (uint8_t * data, unsigned int pixels, unsigned int lines, + unsigned int channels, unsigned int black) +{ + unsigned int i, j, k, average, count; + unsigned int avg[3]; + uint8_t val; + + /* computes average value on black margin */ + for (k = 0; k < channels; k++) + { + avg[k] = 0; + count = 0; + for (i = 0; i < lines; i++) + { + for (j = 0; j < black; j++) + { + val = data[i * channels * pixels + j + k]; + avg[k] += val; + count++; + } + } + if (count) + avg[k] /= count; + DBG (DBG_info, "dark_average: avg[%d] = %d\n", k, avg[k]); + } + average = 0; + for (i = 0; i < channels; i++) + average += avg[i]; + average /= channels; + DBG (DBG_info, "dark_average: average = %d\n", average); + return average; +} + + +/** @brief calibration for AD frontend devices + * we do simple scan until all black_pixels are higher than 0, + * raising offset at each turn. + */ +static SANE_Status +ad_fe_offset_calibration (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + uint8_t *line; + unsigned int channels; + char title[32]; + int pass = 0; + SANE_Int resolution; + Genesys_Settings settings; + unsigned int x, y, adr, min; + unsigned int bottom, black_pixels; + + DBG (DBG_proc, "ad_fe_offset_calibration: start\n"); + resolution = + get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res, + SANE_TRUE); + channels = 3; + black_pixels = + (dev->sensor.black_pixels * resolution) / dev->sensor.optical_res; + DBG (DBG_io2, "ad_fe_offset_calibration: black_pixels=%d\n", black_pixels); + + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_COLOR; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = + (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res; + settings.lines = CALIBRATION_LINES; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* scan first line of data with no gain */ + dev->frontend.gain[0] = 0; + dev->frontend.gain[1] = 0; + dev->frontend.gain[2] = 0; + + /* scan with no move */ + bottom = 1; + do + { + pass++; + dev->frontend.offset[0] = bottom; + dev->frontend.offset[1] = bottom; + dev->frontend.offset[2] = bottom; + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line); + if (status != SANE_STATUS_GOOD) + { + free(line); + DBG (DBG_error, + "ad_fe_offset_calibration: failed to scan first line\n"); + return status; + } + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "offset%03d.pnm", (int)bottom); + sanei_genesys_write_pnm_file (title, line, 8, channels, + settings.pixels, settings.lines); + } + + min = 0; + for (y = 0; y < settings.lines; y++) + { + for (x = 0; x < black_pixels; x++) + { + adr = (x + y * settings.pixels) * channels; + if (line[adr] > min) + min = line[adr]; + if (line[adr + 1] > min) + min = line[adr + 1]; + if (line[adr + 2] > min) + min = line[adr + 2]; + } + } + + free (line); + DBG (DBG_io2, "ad_fe_offset_calibration: pass=%d, min=%d\n", pass, min); + bottom++; + } + while (pass < 128 && min == 0); + if (pass == 128) + { + DBG (DBG_error, + "ad_fe_offset_calibration: failed to find correct offset\n"); + return SANE_STATUS_INVAL; + } + + DBG (DBG_info, "ad_fe_offset_calibration: offset=(%d,%d,%d)\n", + dev->frontend.offset[0], dev->frontend.offset[1], + dev->frontend.offset[2]); + DBG (DBG_proc, "ad_fe_offset_calibration: end\n"); + return status; +} + +#define DARK_TARGET 8 +/** + * This function does the offset calibration by scanning one line of the calibration + * area below scanner's top. There is a black margin and the remaining is white. + * genesys_search_start() must have been called so that the offsets and margins + * are already known. + * @param dev scanner's device + * @return SANE_STATUS_GOOD if success, else error code is failure +*/ +static SANE_Status +gl646_offset_calibration (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + uint8_t *first_line, *second_line; + unsigned int channels; + char title[32]; + int pass = 0, avg; + SANE_Int resolution; + Genesys_Settings settings; + int topavg, bottomavg; + int top, bottom, black_pixels; + + /* Analog Device fronted have a different calibration */ + if (dev->model->dac_type == DAC_AD_XP200) + { + return ad_fe_offset_calibration (dev); + } + + DBG (DBG_proc, "gl646_offset_calibration: start\n"); + + /* setup for a RGB scan, one full sensor's width line */ + /* resolution is the one from the final scan */ + if (dev->settings.xres > dev->sensor.optical_res) + { + resolution = + get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res, + SANE_TRUE); + } + else + { + resolution = + get_closest_resolution (dev->model->ccd_type, dev->settings.xres, + SANE_TRUE); + } + channels = 3; + black_pixels = + (dev->sensor.black_pixels * resolution) / dev->sensor.optical_res; + DBG (DBG_io2, "gl646_offset_calibration: black_pixels=%d\n", black_pixels); + + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_COLOR; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = + (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res; + settings.lines = CALIBRATION_LINES; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* scan first line of data with no gain, but with offset from + * last calibration */ + dev->frontend.gain[0] = 0; + dev->frontend.gain[1] = 0; + dev->frontend.gain[2] = 0; + + /* scan with no move */ + bottom = 90; + dev->frontend.offset[0] = bottom; + dev->frontend.offset[1] = bottom; + dev->frontend.offset[2] = bottom; + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, + &first_line); + if (status != SANE_STATUS_GOOD) + { + free(first_line); + DBG (DBG_error, + "gl646_offset_calibration: failed to scan first line\n"); + return status; + } + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "offset%03d.pnm", bottom); + sanei_genesys_write_pnm_file (title, first_line, 8, channels, + settings.pixels, settings.lines); + } + bottomavg = + dark_average (first_line, settings.pixels, settings.lines, channels, + black_pixels); + free (first_line); + DBG (DBG_io2, "gl646_offset_calibration: bottom avg=%d\n", bottomavg); + + /* now top value */ + top = 231; + dev->frontend.offset[0] = top; + dev->frontend.offset[1] = top; + dev->frontend.offset[2] = top; + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, + &second_line); + if (status != SANE_STATUS_GOOD) + { + free(second_line); + DBG (DBG_error, + "gl646_offset_calibration: failed to scan first line\n"); + return status; + } + + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "offset%03d.pnm", top); + sanei_genesys_write_pnm_file (title, second_line, 8, channels, + settings.pixels, settings.lines); + } + topavg = + dark_average (second_line, settings.pixels, settings.lines, channels, + black_pixels); + free (second_line); + DBG (DBG_io2, "gl646_offset_calibration: top avg=%d\n", topavg); + + /* loop until acceptable level */ + while ((pass < 32) && (top - bottom > 1)) + { + pass++; + + /* settings for new scan */ + dev->frontend.offset[0] = (top + bottom) / 2; + dev->frontend.offset[1] = (top + bottom) / 2; + dev->frontend.offset[2] = (top + bottom) / 2; + + /* scan with no move */ + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, + &second_line); + if (status != SANE_STATUS_GOOD) + { + free(second_line); + DBG (DBG_error, + "gl646_offset_calibration: failed to scan first line\n"); + return status; + } + + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "offset%03d.pnm", dev->frontend.offset[1]); + sanei_genesys_write_pnm_file (title, second_line, 8, channels, + settings.pixels, settings.lines); + } + + avg = + dark_average (second_line, settings.pixels, settings.lines, channels, + black_pixels); + DBG (DBG_info, "gl646_offset_calibration: avg=%d offset=%d\n", avg, + dev->frontend.offset[1]); + free (second_line); + + /* compute new boundaries */ + if (topavg == avg) + { + topavg = avg; + top = dev->frontend.offset[1]; + } + else + { + bottomavg = avg; + bottom = dev->frontend.offset[1]; + } + } + + /* in case of debug do a final scan to get result */ + if (DBG_LEVEL >= DBG_data) + { + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, + &second_line); + if (status != SANE_STATUS_GOOD) + { + free(second_line); + DBG (DBG_error, + "gl646_offset_calibration: failed to scan final line\n"); + return status; + } + sanei_genesys_write_pnm_file ("offset-final.pnm", second_line, 8, + channels, settings.pixels, + settings.lines); + free (second_line); + } + + DBG (DBG_info, "gl646_offset_calibration: offset=(%d,%d,%d)\n", + dev->frontend.offset[0], dev->frontend.offset[1], + dev->frontend.offset[2]); + DBG (DBG_proc, "gl646_offset_calibration: end\n"); + return status; +} + +/** @brief gain calibration for Analog Device frontends + * Alternative coarse gain calibration + */ +static SANE_Status +ad_fe_coarse_gain_calibration (Genesys_Device * dev, int dpi) +{ + uint8_t *line; + unsigned int i, channels, val; + unsigned int size, count, resolution, pass; + SANE_Status status = SANE_STATUS_GOOD; + float average; + Genesys_Settings settings; + char title[32]; + + DBG (DBG_proc, "ad_fe_coarse_gain_calibration: start\n"); + + /* setup for a RGB scan, one full sensor's width line */ + /* resolution is the one from the final scan */ + resolution = get_closest_resolution (dev->model->ccd_type, dpi, SANE_TRUE); + channels = 3; + settings.scan_mode = SCAN_MODE_COLOR; + + settings.scan_method = SCAN_METHOD_FLATBED; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = + (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res; + settings.lines = CALIBRATION_LINES; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + size = channels * settings.pixels * settings.lines; + + /* start gain value */ + dev->frontend.gain[0] = 1; + dev->frontend.gain[1] = 1; + dev->frontend.gain[2] = 1; + + average = 0; + pass = 0; + + /* loop until each channel raises to acceptable level */ + while ((average < dev->sensor.gain_white_ref) && (pass < 30)) + { + /* scan with no move */ + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line); + if (status != SANE_STATUS_GOOD) + { + free(line); + DBG (DBG_error, + "ad_fe_coarse_gain_calibration: failed to scan first line\n"); + return status; + } + + /* log scanning data */ + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "alternative_coarse%02d.pnm", (int)pass); + sanei_genesys_write_pnm_file (title, line, 8, + channels, settings.pixels, + settings.lines); + } + pass++; + + /* computes white average */ + average = 0; + count = 0; + for (i = 0; i < size; i++) + { + val = line[i]; + average += val; + count++; + } + average = average / count; + + /* adjusts gain for the channel */ + if (average < dev->sensor.gain_white_ref) + dev->frontend.gain[0]++; + dev->frontend.gain[1] = dev->frontend.gain[0]; + dev->frontend.gain[2] = dev->frontend.gain[0]; + + DBG (DBG_proc, + "ad_fe_coarse_gain_calibration: average = %.2f, gain = %d\n", + average, dev->frontend.gain[0]); + free (line); + } + + DBG (DBG_info, "ad_fe_coarse_gain_calibration: gains=(%d,%d,%d)\n", + dev->frontend.gain[0], dev->frontend.gain[1], dev->frontend.gain[2]); + DBG (DBG_proc, "ad_fe_coarse_gain_calibration: end\n"); + return status; +} + +/** + * Alternative coarse gain calibration + * this on uses the settings from offset_calibration. First scan moves so + * we can go to calibration area for XPA. + * @param dev device for scan + * @param dpi resolutnio to calibrate at + */ +static SANE_Status +gl646_coarse_gain_calibration (Genesys_Device * dev, int dpi) +{ + uint8_t *line; + unsigned int i, j, k, channels, val, maximum, idx; + unsigned int count, resolution, pass; + SANE_Status status = SANE_STATUS_GOOD; + float average[3]; + Genesys_Settings settings; + char title[32]; + + if (dev->model->ccd_type == CIS_XP200) + { + return ad_fe_coarse_gain_calibration (dev, dev->sensor.optical_res); + } + DBG (DBG_proc, "gl646_coarse_gain_calibration: start\n"); + + /* setup for a RGB scan, one full sensor's width line */ + /* resolution is the one from the final scan */ + channels = 3; + + /* we are searching a sensor resolution */ + if (dpi > dev->sensor.optical_res) + { + resolution = + get_closest_resolution (dev->model->ccd_type, dev->sensor.optical_res, + SANE_TRUE); + } + else + { + resolution = + get_closest_resolution (dev->model->ccd_type, dev->settings.xres, + SANE_TRUE); + } + + settings.scan_method = dev->settings.scan_method; + settings.scan_mode = SCAN_MODE_COLOR; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_y = 0; + if (settings.scan_method == SCAN_METHOD_FLATBED) + { + settings.tl_x = 0; + settings.pixels = + (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res; + } + else + { + settings.tl_x = SANE_UNFIX (dev->model->x_offset_ta); + settings.pixels = + (SANE_UNFIX (dev->model->x_size_ta) * resolution) / MM_PER_INCH; + } + settings.lines = CALIBRATION_LINES; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* start gain value */ + dev->frontend.gain[0] = 1; + dev->frontend.gain[1] = 1; + dev->frontend.gain[2] = 1; + + if (channels > 1) + { + average[0] = 0; + average[1] = 0; + average[2] = 0; + idx = 0; + } + else + { + average[0] = 255; + average[1] = 255; + average[2] = 255; + idx = dev->settings.color_filter; + average[idx] = 0; + } + pass = 0; + + /* loop until each channel raises to acceptable level */ + while (((average[0] < dev->sensor.gain_white_ref) + || (average[1] < dev->sensor.gain_white_ref) + || (average[2] < dev->sensor.gain_white_ref)) && (pass < 30)) + { + /* scan with no move */ + status = + simple_scan (dev, settings, SANE_FALSE, SANE_TRUE, SANE_FALSE, &line); + if (status != SANE_STATUS_GOOD) + { + free(line); + DBG (DBG_error, + "gl646_coarse_gain_calibration: failed to scan first line\n"); + return status; + } + + /* log scanning data */ + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "coarse_gain%02d.pnm", (int)pass); + sanei_genesys_write_pnm_file (title, line, 8, + channels, settings.pixels, + settings.lines); + } + pass++; + + /* average high level for each channel and compute gain + to reach the target code + we only use the central half of the CCD data */ + for (k = idx; k < idx + channels; k++) + { + /* we find the maximum white value, so we can deduce a threshold + to average white values */ + maximum = 0; + for (i = 0; i < settings.lines; i++) + { + for (j = 0; j < settings.pixels; j++) + { + val = line[i * channels * settings.pixels + j + k]; + if (val > maximum) + maximum = val; + } + } + + /* threshold */ + maximum *= 0.9; + + /* computes white average */ + average[k] = 0; + count = 0; + for (i = 0; i < settings.lines; i++) + { + for (j = 0; j < settings.pixels; j++) + { + /* averaging only white points allow us not to care about dark margins */ + val = line[i * channels * settings.pixels + j + k]; + if (val > maximum) + { + average[k] += val; + count++; + } + } + } + average[k] = average[k] / count; + + /* adjusts gain for the channel */ + if (average[k] < dev->sensor.gain_white_ref) + dev->frontend.gain[k]++; + + DBG (DBG_proc, + "gl646_coarse_gain_calibration: channel %d, average = %.2f, gain = %d\n", + k, average[k], dev->frontend.gain[k]); + } + free (line); + } + + if (channels < 3) + { + dev->frontend.gain[1] = dev->frontend.gain[0]; + dev->frontend.gain[2] = dev->frontend.gain[0]; + } + + DBG (DBG_info, "gl646_coarse_gain_calibration: gains=(%d,%d,%d)\n", + dev->frontend.gain[0], dev->frontend.gain[1], dev->frontend.gain[2]); + DBG (DBG_proc, "gl646_coarse_gain_calibration: end\n"); + return status; +} + +/** + * sets up the scanner's register for warming up. We scan 2 lines without moving. + * + */ +static SANE_Status +gl646_init_regs_for_warmup (Genesys_Device * dev, + Genesys_Register_Set * local_reg, + int *channels, int *total_size) +{ + SANE_Status status = SANE_STATUS_GOOD; + Genesys_Settings settings; + int resolution, lines; + + DBG (DBG_proc, "gl646_init_regs_for_warmup: start\n"); + + sanei_genesys_init_fe (dev); + + resolution = get_closest_resolution (dev->model->ccd_type, 300, SANE_FALSE); + + /* set up for a half width 2 lines gray scan without moving */ + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_GRAY; + settings.xres = resolution; + settings.yres = resolution; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = + (dev->sensor.sensor_pixels * resolution) / dev->sensor.optical_res; + settings.lines = 2; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* setup for scan */ + status = setup_for_scan (dev, settings, SANE_TRUE, SANE_FALSE, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_init_regs_for_warmup: setup_for_scan failed (%s)\n", + sane_strstatus (status)); + return status; + } + + /* we are not going to move, so clear these bits */ + dev->reg[reg_0x02].value &= ~(REG02_FASTFED | REG02_AGOHOME); + + /* don't enable any correction for this scan */ + dev->reg[reg_0x01].value &= ~REG01_DVDSET; + /* XXX STEF XXX + dev->reg[reg_0x05].value &= ~REG05_GMMENB; */ + + /* copy to local_reg */ + memcpy (local_reg, dev->reg, (GENESYS_GL646_MAX_REGS + 1) * sizeof (Genesys_Register_Set)); + + /* turn off motor during this scan */ + gl646_set_motor_power (local_reg, SANE_FALSE); + + /* returned value to higher level warmup function */ + *channels = 1; + lines = gl646_get_triple_reg (local_reg, REG_LINCNT) + 1; + *total_size = lines * settings.pixels; + + /* now registers are ok, write them to scanner */ + RIE (gl646_set_fe (dev, AFE_SET, settings.xres)); + RIE (gl646_bulk_write_register (dev, local_reg, GENESYS_GL646_MAX_REGS)); + + DBG (DBG_proc, "gl646_init_regs_for_warmup: end\n"); + return status; +} + + +/* + * this function moves head without scanning, forward, then backward + * so that the head goes to park position. + * as a by-product, also check for lock + */ +static SANE_Status +gl646_repark_head (Genesys_Device * dev) +{ + SANE_Status status; + Genesys_Settings settings; + unsigned int expected, steps; + + DBG (DBG_proc, "gl646_repark_head: start\n"); + + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_COLOR; + settings.xres = + get_closest_resolution (dev->model->ccd_type, 75, SANE_FALSE); + settings.yres = settings.xres; + settings.tl_x = 0; + settings.tl_y = 5; + settings.pixels = 600; + settings.lines = 4; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + status = setup_for_scan (dev, settings, SANE_FALSE, SANE_FALSE, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_repark_head: failed to setup for scan: %s\n", + sane_strstatus (status)); + return status; + } + + /* TODO seems wrong ... no effective scan */ + dev->reg[reg_0x01].value &= ~REG01_SCAN; + + status = gl646_bulk_write_register (dev, dev->reg, GENESYS_GL646_MAX_REGS); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_repark_head: failed to send registers: %s\n", + sane_strstatus (status)); + return status; + } + + /* start scan */ + status = gl646_begin_scan (dev, dev->reg, SANE_TRUE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_repark_head: failed to begin scan: \n"); + return status; + } + + expected = gl646_get_triple_reg (dev->reg, REG_FEEDL); + do + { + usleep (100 * 1000); + status = sanei_genesys_read_feed_steps (dev, &steps); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_repark_head: failed to read feed steps: %s\n", + sane_strstatus (status)); + return status; + } + } + while (steps < expected); + + /* toggle motor flag, put an huge step number and redo move backward */ + status = gl646_slow_back_home (dev, 1); + DBG (DBG_proc, "gl646_repark_head: end\n"); + return status; +} + +/* * + * initialize ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + * @param dev device description of the scanner to initailize + * @return SANE_STATUS_GOOD if success, error code if failure + */ +static SANE_Status +gl646_init (Genesys_Device * dev) +{ + SANE_Status status; + struct timeval tv; + uint8_t cold = 0, val = 0; + uint32_t addr = 0xdead; + int size, i; + size_t len; + + DBG_INIT (); + DBG (DBG_proc, "gl646_init: start\n"); + + /* to detect real power up condition, we write to REG41 + * with pwrbit set, then read it back. When scanner is cold (just replugged) + * PWRBIT will be set in the returned value + */ + RIE (sanei_genesys_get_status (dev, &cold)); + DBG (DBG_info, "gl646_init: status=0x%02x\n", cold); + cold = !(cold & REG41_PWRBIT); + if (cold) + { + DBG (DBG_info, "gl646_init: device is cold\n"); + } + else + { + DBG (DBG_info, "gl646_init: device is hot\n"); + } + + /* if scanning session hasn't been initialized, set it up */ + if (!dev->already_initialized) + { + dev->dark_average_data = NULL; + dev->white_average_data = NULL; + + dev->settings.color_filter = 1; /* green filter by default */ + gettimeofday (&tv, NULL); + dev->init_date = tv.tv_sec; + + switch (dev->model->motor_type) + { + /* set to 11111 to spot bugs, sanei_genesys_exposure_time should + have obsoleted this field */ + case MOTOR_5345: + dev->settings.exposure_time = 11111; + break; + + case MOTOR_ST24: + dev->settings.exposure_time = 11000; + break; + default: + dev->settings.exposure_time = 11000; + break; + } + + /* Set default values for registers */ + gl646_init_regs (dev); + + /* build default gamma tables */ + if (dev->reg[reg_0x05].value & REG05_GMMTYPE) + size = 16384; + else + size = 4096; + + for(i=0;i<3;i++) + { + if (dev->sensor.gamma_table[i] == NULL) + { + dev->sensor.gamma_table[i] = (uint16_t *) malloc (2 * size); + if (dev->sensor.gamma_table[i] == NULL) + { + DBG (DBG_error, "gl646_init: could not allocate memory for gamma table %d\n",i); + return SANE_STATUS_NO_MEM; + } + sanei_genesys_create_gamma_table (dev->sensor.gamma_table[i], + size, + size - 1, + size - 1, + dev->sensor.gamma[i]); + } + } + + /* Init shading data */ + RIE (sanei_genesys_init_shading_data (dev, dev->sensor.sensor_pixels)); + + /* initial calibration reg values */ + memcpy (dev->calib_reg, dev->reg, + (GENESYS_GL646_MAX_REGS + 1) * sizeof (Genesys_Register_Set)); + } + + /* execute physical unit init only if cold */ + if (cold) + { + DBG (DBG_info, "gl646_init: device is cold\n"); + val = 0x04; + RIE (sanei_usb_control_msg + (dev->dn, REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_INIT, + INDEX, 1, &val)); + + /* ASIC reset */ + RIE (sanei_genesys_write_register (dev, 0x0e, 0x00)); + usleep (100000UL); /* sleep 100 ms */ + + /* Write initial registers */ + RIE (gl646_bulk_write_register (dev, dev->reg, GENESYS_GL646_MAX_REGS)); + + /* Test ASIC and RAM */ + if (!(dev->model->flags & GENESYS_FLAG_LAZY_INIT)) + { + RIE (gl646_asic_test (dev)); + } + + /* send gamma tables if needed */ + status = gl646_send_gamma_table (dev); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_init: failed to send generic gamma tables: %s\n", + sane_strstatus (status)); + return status; + } + + /* Set powersaving (default = 15 minutes) */ + RIE (gl646_set_powersaving (dev, 15)); + } /* end if cold */ + + /* Set analog frontend */ + RIE (gl646_set_fe (dev, AFE_INIT, 0)); + + /* GPO enabling for XP200 */ + if (dev->model->ccd_type == CIS_XP200) + { + sanei_genesys_write_register (dev, 0x68, dev->gpo.enable[0]); + sanei_genesys_write_register (dev, 0x69, dev->gpo.enable[1]); + + /* enable GPIO */ + val = 6; + status = gl646_gpio_output_enable (dev->dn, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_init: GPO enable failed ... %s\n", + sane_strstatus (status)); + } + val = 0; + + /* writes 0 to GPIO */ + status = gl646_gpio_write (dev->dn, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_init: GPO write failed ... %s\n", + sane_strstatus (status)); + } + + /* clear GPIO enable */ + status = gl646_gpio_output_enable (dev->dn, val); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_init: GPO disable failed ... %s\n", + sane_strstatus (status)); + } + sanei_genesys_write_register (dev, 0x66, 0x10); + sanei_genesys_write_register (dev, 0x66, 0x00); + sanei_genesys_write_register (dev, 0x66, 0x10); + } + + /* MD6471/G2410 and XP200 read/write data from an undocumented memory area which + * is after the second slope table */ + if (dev->model->gpo_type != GPO_HP3670 + && dev->model->gpo_type != GPO_HP2400) + { + switch (dev->sensor.optical_res) + { + case 600: + addr = 0x08200; + break; + case 1200: + addr = 0x10200; + break; + case 2400: + addr = 0x1fa00; + break; + } + status = sanei_genesys_set_buffer_address (dev, addr); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_init: failed to set up control address\n"); + return SANE_STATUS_INVAL; + } + sanei_usb_set_timeout (2 * 1000); + len = 6; + status = gl646_bulk_read_data (dev, 0x45, dev->control, len); + /* for some reason, read fails here for MD6471, HP2300 and XP200 + * one time out of 2 scanimage launches + */ + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_warn, "gl646_init: failed to read control\n"); + status = gl646_bulk_read_data (dev, 0x45, dev->control, len); + } + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_warn, "gl646_init: failed to read control\n"); + return SANE_STATUS_INVAL; + } + else + { + DBG (DBG_info, + "gl646_init: control read=0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + dev->control[0], dev->control[1], dev->control[2], + dev->control[3], dev->control[4], dev->control[5]); + } + sanei_usb_set_timeout (30 * 1000); + } + else + /* HP2400 and HP3670 case */ + { + dev->control[0] = 0x00; + dev->control[1] = 0x00; + dev->control[2] = 0x01; + dev->control[3] = 0x00; + dev->control[4] = 0x00; + dev->control[5] = 0x00; + } + + /* ensure head is correctly parked, and check lock */ + if (dev->model->is_sheetfed == SANE_FALSE) + { + if (dev->model->flags & GENESYS_FLAG_REPARK) + { + status = gl646_repark_head (dev); + if (status != SANE_STATUS_GOOD) + { + if (status == SANE_STATUS_INVAL) + { + DBG (DBG_error0, + "Your scanner is locked. Please move the lock switch " + "to the unlocked position\n"); +#ifdef SANE_STATUS_HW_LOCKED + return SANE_STATUS_HW_LOCKED; +#else + return SANE_STATUS_JAMMED; +#endif + } + else + DBG (DBG_error, + "gl646_init: gl646_repark_head failed: %s\n", + sane_strstatus (status)); + return status; + } + } + else + { + RIE (gl646_slow_back_home (dev, SANE_TRUE)); + } + } + + /* here session and device are initialized */ + dev->already_initialized = SANE_TRUE; + + DBG (DBG_proc, "gl646_init: end\n"); + return SANE_STATUS_GOOD; +} + +#ifndef UNIT_TESTING +static +#endif +SANE_Status +gl646_move_to_ta (Genesys_Device * dev) +{ + SANE_Status status = SANE_STATUS_GOOD; + + DBG (DBG_proc, "gl646_move_to_ta: starting\n"); + if (simple_move (dev, SANE_UNFIX (dev->model->y_offset_calib_ta)) != + SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_move_to_ta: failed to move to calibration area\n"); + return status; + } + DBG (DBG_proc, "gl646_move_to_ta: end\n"); + + return status; +} + + +/** + * Does a simple scan: ie no line reordering and avanced data buffering and + * shading correction. Memory for data is allocated in this function + * and must be freed by caller. + * @param dev device of the scanner + * @param settings parameters of the scan + * @param move SANE_TRUE if moving during scan + * @param forward SANE_TRUE if moving forward during scan + * @param shading SANE_TRUE to enable shading correction + * @param data pointer for the data + */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +simple_scan (Genesys_Device * dev, Genesys_Settings settings, SANE_Bool move, + SANE_Bool forward, SANE_Bool shading, unsigned char **data) +{ + SANE_Status status = SANE_STATUS_INVAL; + unsigned int size, lines, x, y, bpp; + SANE_Bool empty, split; + unsigned char *buffer; + int count; + uint8_t val; + + DBG (DBG_proc, "simple_scan: starting\n"); + DBG (DBG_io, "simple_scan: move=%d, forward=%d, shading=%d\n", move, + forward, shading); + + /* round up to multiple of 3 in case of CIS scanner */ + if (dev->model->is_cis == SANE_TRUE) + { + settings.lines = ((settings.lines + 2) / 3) * 3; + } + + /* setup for move then scan */ + if (move == SANE_TRUE && settings.tl_y > 0) + { + split = SANE_FALSE; + } + else + { + split = SANE_TRUE; + } + status = setup_for_scan (dev, settings, split, SANE_FALSE, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "simple_scan: setup_for_scan failed (%s)\n", + sane_strstatus (status)); + return status; + } + + /* allocate memory fo scan : LINCNT may have been adjusted for CCD reordering */ + if (dev->model->is_cis == SANE_TRUE) + { + lines = gl646_get_triple_reg (dev->reg, REG_LINCNT) / 3; + } + else + { + lines = gl646_get_triple_reg (dev->reg, REG_LINCNT) + 1; + } + size = lines * settings.pixels; + if (settings.depth == 16) + bpp = 2; + else + bpp = 1; + size *= bpp; + if (settings.scan_mode == SCAN_MODE_COLOR) /* single pass color */ + size *= 3; + *data = malloc (size); + if (!*data) + { + DBG (DBG_error, + "simple_scan: failed to allocate %d bytes of memory\n", size); + return SANE_STATUS_NO_MEM; + } + DBG (DBG_io, "simple_scan: allocated %d bytes of memory for %d lines\n", + size, lines); + + /* put back real line number in settings */ + settings.lines = lines; + + /* initialize frontend */ + status = gl646_set_fe (dev, AFE_SET, settings.xres); + if (status != SANE_STATUS_GOOD) + { + free (*data); + DBG (DBG_error, + "simple_scan: failed to set frontend: %s\n", + sane_strstatus (status)); + return status; + } + + /* no shading correction and not watch dog for simple scan */ + dev->reg[reg_0x01].value &= ~(REG01_DVDSET | REG01_DOGENB); + if (shading == SANE_TRUE) + { + dev->reg[reg_0x01].value |= REG01_DVDSET; + } + + /* one table movement for simple scan */ + dev->reg[reg_0x02].value &= ~REG02_FASTFED; + + if (move == SANE_FALSE) + { + /* clear motor power flag if no move */ + dev->reg[reg_0x02].value &= ~REG02_MTRPWR; + + /* no automatic go home if no movement */ + dev->reg[reg_0x02].value &= ~REG02_AGOHOME; + } + if (forward == SANE_FALSE) + { + dev->reg[reg_0x02].value |= REG02_MTRREV; + } + else + { + dev->reg[reg_0x02].value &= ~REG02_MTRREV; + } + + /* no automatic go home when using XPA */ + if (settings.scan_method == SCAN_METHOD_TRANSPARENCY) + { + dev->reg[reg_0x02].value &= ~REG02_AGOHOME; + } + + /* write scan registers */ + status = gl646_bulk_write_register (dev, dev->reg, + sizeof (dev->reg) / + sizeof (dev->reg[0])); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "simple_scan: failed to bulk write registers: %s\n", + sane_strstatus (status)); + free (*data); + return status; + } + + /* starts scan */ + status = gl646_begin_scan (dev, dev->reg, move); + if (status != SANE_STATUS_GOOD) + { + free (*data); + DBG (DBG_error, "simple_scan: failed to begin scan: \n"); + return status; + } + + /* wait for buffers to be filled */ + count = 0; + do + { + usleep (10000UL); + RIE (sanei_genesys_get_status (dev, &val)); + if (DBG_LEVEL > DBG_info) + { + print_status (val); + } + RIE (sanei_genesys_test_buffer_empty (dev, &empty)); + count++; + } + while (empty && count < 1000); + if (count == 1000) + { + free (*data); + DBG (DBG_error, "simple_scan: failed toread data\n"); + return SANE_STATUS_IO_ERROR; + } + + /* now we're on target, we can read data */ + status = sanei_genesys_read_data_from_scanner (dev, *data, size); + if (status != SANE_STATUS_GOOD) + { + free (*data); + DBG (DBG_error, + "simple_scan: failed to read data: %s\n", sane_strstatus (status)); + return status; + } + + /* in case of CIS scanner, we must reorder data */ + if (dev->model->is_cis == SANE_TRUE + && settings.scan_mode == SCAN_MODE_COLOR) + { + /* alloc one line sized working buffer */ + buffer = (unsigned char *) malloc (settings.pixels * 3 * bpp); + if (buffer == NULL) + { + DBG (DBG_error, + "simple_scan: failed to allocate %d bytes of memory\n", + settings.pixels * 3); + return SANE_STATUS_NO_MEM; + } + + /* reorder one line of data and put it back to buffer */ + if (bpp == 1) + { + for (y = 0; y < lines; y++) + { + /* reorder line */ + for (x = 0; x < settings.pixels; x++) + { + buffer[x * 3] = (*data)[y * settings.pixels * 3 + x]; + buffer[x * 3 + 1] = + (*data)[y * settings.pixels * 3 + settings.pixels + x]; + buffer[x * 3 + 2] = + (*data)[y * settings.pixels * 3 + 2 * settings.pixels + + x]; + } + /* copy line back */ + memcpy ((*data) + settings.pixels * 3 * y, buffer, + settings.pixels * 3); + } + } + else + { + for (y = 0; y < lines; y++) + { + /* reorder line */ + for (x = 0; x < settings.pixels; x++) + { + buffer[x * 6] = (*data)[y * settings.pixels * 6 + x * 2]; + buffer[x * 6 + 1] = + (*data)[y * settings.pixels * 6 + x * 2 + 1]; + buffer[x * 6 + 2] = + (*data)[y * settings.pixels * 6 + 2 * settings.pixels + + x * 2]; + buffer[x * 6 + 3] = + (*data)[y * settings.pixels * 6 + 2 * settings.pixels + + x * 2 + 1]; + buffer[x * 6 + 4] = + (*data)[y * settings.pixels * 6 + 4 * settings.pixels + + x * 2]; + buffer[x * 6 + 5] = + (*data)[y * settings.pixels * 6 + 4 * settings.pixels + + x * 2 + 1]; + } + /* copy line back */ + memcpy ((*data) + settings.pixels * 6 * y, buffer, + settings.pixels * 6); + } + } + free (buffer); + } + + /* end scan , waiting the motor to stop if needed (if moving), but without ejecting doc */ + status = end_scan (dev, dev->reg, SANE_TRUE, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + free (*data); + DBG (DBG_error, + "simple_scan: failed to end scan: %s\n", sane_strstatus (status)); + return status; + } + + DBG (DBG_proc, "simple_scan: end\n"); + return status; +} + +/** + * Does a simple move of the given distance by doing a scan at lowest resolution + * shading correction. Memory for data is allocated in this function + * and must be freed by caller. + * @param dev device of the scanner + * @param distance distance to move in MM + */ +#ifndef UNIT_TESTING +static +#endif + SANE_Status +simple_move (Genesys_Device * dev, SANE_Int distance) +{ + SANE_Status status; + unsigned char *data = NULL; + Genesys_Settings settings; + + DBG (DBG_proc, "simple_move: %d mm\n", distance); + + /* TODO give a no AGOHOME flag */ + settings.scan_method = SCAN_METHOD_TRANSPARENCY; + settings.scan_mode = SCAN_MODE_COLOR; + settings.xres = get_lowest_resolution (dev->model->ccd_type, SANE_TRUE); + settings.yres = settings.xres; + settings.tl_y = 0; + settings.tl_x = 0; + settings.pixels = + (dev->sensor.sensor_pixels * settings.xres) / dev->sensor.optical_res; + settings.lines = (distance * settings.xres) / MM_PER_INCH; + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + status = simple_scan (dev, settings, SANE_TRUE, SANE_TRUE, SANE_FALSE, &data); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "simple_move: simple_scan failed\n"); + } + + free (data); + DBGCOMPLETED + return status; +} + +/** + * update the status of the required sensor in the scanner session + * the last_val fileds are used to make events 'sticky' + */ +static SANE_Status +gl646_update_hardware_sensors (Genesys_Scanner * session) +{ + Genesys_Device *dev = session->dev; + uint8_t value; + SANE_Status status; + + /* do what is needed to get a new set of events, but try to not loose + any of them. + */ + status = gl646_gpio_read (dev->dn, &value); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "gl646_update_hardware_sensors: failed to read GPIO %s\n", + sane_strstatus (status)); + return status; + } + DBG (DBG_io, "gl646_update_hardware_sensors: GPIO=0x%02x\n", value); + + /* scan button */ + if ((dev->model->buttons & GENESYS_HAS_SCAN_SW) + && session->val[OPT_SCAN_SW].b == session->last_val[OPT_SCAN_SW].b) + { + switch (dev->model->gpo_type) + { + case GPO_XP200: + session->val[OPT_SCAN_SW].b = ((value & 0x02) != 0); + break; + case GPO_5345: + session->val[OPT_SCAN_SW].b = (value == 0x16); + break; + case GPO_HP2300: + session->val[OPT_SCAN_SW].b = (value == 0x6c); + break; + case GPO_HP3670: + case GPO_HP2400: + session->val[OPT_SCAN_SW].b = ((value & 0x20) == 0); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + /* email button */ + if ((dev->model->buttons & GENESYS_HAS_EMAIL_SW) + && session->val[OPT_EMAIL_SW].b == session->last_val[OPT_EMAIL_SW].b) + { + switch (dev->model->gpo_type) + { + case GPO_5345: + session->val[OPT_EMAIL_SW].b = (value == 0x12); + break; + case GPO_HP3670: + case GPO_HP2400: + session->val[OPT_EMAIL_SW].b = ((value & 0x08) == 0); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + /* copy button */ + if ((dev->model->buttons & GENESYS_HAS_COPY_SW) + && session->val[OPT_COPY_SW].b == session->last_val[OPT_COPY_SW].b) + { + switch (dev->model->gpo_type) + { + case GPO_5345: + session->val[OPT_COPY_SW].b = (value == 0x11); + break; + case GPO_HP2300: + session->val[OPT_COPY_SW].b = (value == 0x5c); + break; + case GPO_HP3670: + case GPO_HP2400: + session->val[OPT_COPY_SW].b = ((value & 0x10) == 0); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + /* power button */ + if ((dev->model->buttons & GENESYS_HAS_POWER_SW) + && session->val[OPT_POWER_SW].b == session->last_val[OPT_POWER_SW].b) + { + switch (dev->model->gpo_type) + { + case GPO_5345: + session->val[OPT_POWER_SW].b = (value == 0x14); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + /* ocr button */ + if ((dev->model->buttons & GENESYS_HAS_OCR_SW) + && session->val[OPT_OCR_SW].b == session->last_val[OPT_OCR_SW].b) + { + switch (dev->model->gpo_type) + { + case GPO_5345: + session->val[OPT_OCR_SW].b = (value == 0x13); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + /* document detection */ + if ((dev->model->buttons & GENESYS_HAS_PAGE_LOADED_SW) + && session->val[OPT_PAGE_LOADED_SW].b == + session->last_val[OPT_PAGE_LOADED_SW].b) + { + switch (dev->model->gpo_type) + { + case GPO_XP200: + session->val[OPT_PAGE_LOADED_SW].b = ((value & 0x04) != 0); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + /* XPA detection */ + if (dev->model->flags & GENESYS_FLAG_XPA) + { + switch (dev->model->gpo_type) + { + case GPO_HP3670: + case GPO_HP2400: + /* test if XPA is plugged-in */ + if ((value & 0x40) == 0) + { + DBG (DBG_io, "gl646_update_hardware_sensors: enabling XPA\n"); + session->opt[OPT_SOURCE].cap &= ~SANE_CAP_INACTIVE; + } + else + { + DBG (DBG_io, "gl646_update_hardware_sensors: disabling XPA\n"); + session->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE; + } + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + + return status; +} + + +static SANE_Status +write_control (Genesys_Device * dev, int resolution) +{ + SANE_Status status; + uint8_t control[4]; + uint32_t addr = 0xdead; + + /* 2300 does not write to 'control' */ + if (dev->model->motor_type == MOTOR_HP2300) + return SANE_STATUS_GOOD; + + /* MD6471/G2410/HP2300 and XP200 read/write data from an undocumented memory area which + * is after the second slope table */ + switch (dev->sensor.optical_res) + { + case 600: + addr = 0x08200; + break; + case 1200: + addr = 0x10200; + break; + case 2400: + addr = 0x1fa00; + break; + default: + DBG (DBG_error, "write_control: failed to compute control address\n"); + return SANE_STATUS_INVAL; + } + + /* XP200 sets dpi, what other scanner put is unknown yet */ + switch (dev->model->motor_type) + { + case MOTOR_XP200: + /* we put scan's dpi, not motor one */ + control[0] = LOBYTE (resolution); + control[1] = HIBYTE (resolution); + control[2] = dev->control[4]; + control[3] = dev->control[5]; + break; + case MOTOR_HP3670: + case MOTOR_HP2400: + case MOTOR_5345: + default: + control[0] = dev->control[2]; + control[1] = dev->control[3]; + control[2] = dev->control[4]; + control[3] = dev->control[5]; + break; + } + + DBG (DBG_info, + "write_control: control write=0x%02x 0x%02x 0x%02x 0x%02x\n", + control[0], control[1], control[2], control[3]); + status = sanei_genesys_set_buffer_address (dev, addr); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "write_control: failed to set up control address\n"); + return SANE_STATUS_INVAL; + } + status = gl646_bulk_write_data (dev, 0x3c, control, 4); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "write_control: failed to set up control\n"); + return SANE_STATUS_INVAL; + } + return status; +} + +/** + * check if a stored calibration is compatible with requested scan. + * @return SANE_STATUS_GOOD if compatible, SANE_STATUS_UNSUPPORTED if not. + * Whenever an error is met, it is returned. + * @param dev scanner device + * @param cache cache entry to test + * @param for_overwrite reserved for future use ... + */ +static SANE_Status +gl646_is_compatible_calibration (Genesys_Device * dev, + Genesys_Calibration_Cache * cache, + int for_overwrite) +{ +#ifdef HAVE_SYS_TIME_H + struct timeval time; +#endif + int compatible = 1; + + DBG (DBG_proc, + "gl646_is_compatible_calibration: start (for_overwrite=%d)\n", + for_overwrite); + + if (cache == NULL) + return SANE_STATUS_UNSUPPORTED; + + /* build minimal current_setup for calibration cache use only, it will be better + * computed when during setup for scan + */ + if (dev->settings.scan_mode == SCAN_MODE_COLOR) + { + dev->current_setup.channels = 3; + } + else + { + dev->current_setup.channels = 1; + } + dev->current_setup.xres = dev->settings.xres; + dev->current_setup.scan_method = dev->settings.scan_method; + + DBG (DBG_io, + "gl646_is_compatible_calibration: requested=(%d,%f), tested=(%d,%f)\n", + dev->current_setup.channels, dev->current_setup.xres, + cache->used_setup.channels, cache->used_setup.xres); + + /* a calibration cache is compatible if color mode and x dpi match the user + * requested scan. In the case of CIS scanners, dpi isn't a criteria */ + if (dev->model->is_cis == SANE_FALSE) + { + compatible = + ((dev->current_setup.channels == cache->used_setup.channels) + && (((int) dev->current_setup.xres) == + ((int) cache->used_setup.xres))); + } + else + { + compatible = + (dev->current_setup.channels == cache->used_setup.channels); + } + if (dev->current_setup.scan_method != cache->used_setup.scan_method) + { + DBG (DBG_io, + "gl646_is_compatible_calibration: current method=%d, used=%d\n", + dev->current_setup.scan_method, cache->used_setup.scan_method); + compatible = 0; + } + if (!compatible) + { + DBG (DBG_proc, + "gl646_is_compatible_calibration: completed, non compatible cache\n"); + return SANE_STATUS_UNSUPPORTED; + } + + /* a cache entry expires after 30 minutes for non sheetfed scanners */ + /* this is not taken into account when overwriting cache entries */ +#ifdef HAVE_SYS_TIME_H + if(for_overwrite == SANE_FALSE) + { + gettimeofday (&time, NULL); + if ((time.tv_sec - cache->last_calibration > 30 * 60) + && (dev->model->is_sheetfed == SANE_FALSE)) + { + DBG (DBG_proc, + "gl646_is_compatible_calibration: expired entry, non compatible cache\n"); + return SANE_STATUS_UNSUPPORTED; + } + } +#endif + + DBG (DBG_proc, + "gl646_is_compatible_calibration: completed, cache compatible\n"); + return SANE_STATUS_GOOD; +} + +/** + * search for a full width black or white strip. + * @param dev scanner device + * @param forward SANE_TRUE if searching forward, SANE_FALSE if searching backward + * @param black SANE_TRUE if searching for a black strip, SANE_FALSE for a white strip + * @return SANE_STATUS_GOOD if a matching strip is found, SANE_STATUS_UNSUPPORTED if not + */ +static SANE_Status +gl646_search_strip (Genesys_Device * dev, SANE_Bool forward, SANE_Bool black) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Bool half_ccd = SANE_FALSE; + Genesys_Settings settings; + int res = get_closest_resolution (dev->model->ccd_type, 75, SANE_FALSE); + unsigned char *data = NULL; + unsigned int pass, count, found, x, y; + char title[80]; + + DBG (DBG_proc, "gl646_search_strip: start\n"); + /* adapt to half_ccd case */ + if (dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE) + { + /* walk the master mode list to find if half_ccd */ + if (is_half_ccd (dev->model->ccd_type, res, SANE_TRUE) == SANE_TRUE) + { + half_ccd = SANE_TRUE; + } + } + + /* we set up for a lowest available resolution color grey scan, full width */ + settings.scan_method = SCAN_METHOD_FLATBED; + settings.scan_mode = SCAN_MODE_GRAY; + settings.xres = res; + settings.yres = res; + settings.tl_x = 0; + settings.tl_y = 0; + settings.pixels = (SANE_UNFIX (dev->model->x_size) * res) / MM_PER_INCH; + if (half_ccd == SANE_TRUE) + { + settings.pixels /= 2; + } + + /* 15 mm at at time */ + settings.lines = (15 * settings.yres) / MM_PER_INCH; /* may become a parameter from genesys_devices.c */ + settings.depth = 8; + settings.color_filter = 0; + + settings.disable_interpolation = 0; + settings.threshold = 0; + settings.exposure_time = 0; + settings.dynamic_lineart = SANE_FALSE; + + /* signals if a strip of the given color has been found */ + found = 0; + + /* detection pass done */ + pass = 0; + + /* loop until strip is found or maximum pass number done */ + while (pass < 20 && !found) + { + /* scan a full width strip */ + status = + simple_scan (dev, settings, SANE_TRUE, forward, SANE_FALSE, &data); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "gl646_search_strip: simple_scan failed\n"); + free (data); + return status; + } + if (DBG_LEVEL >= DBG_data) + { + sprintf (title, "search_strip_%s%02d.pnm", forward ? "fwd" : "bwd", + (int)pass); + sanei_genesys_write_pnm_file (title, data, settings.depth, 1, + settings.pixels, settings.lines); + } + + /* search data to find black strip */ + /* when searching forward, we only need one line of the searched color since we + * will scan forward. But when doing backward search, we need all the area of the + * same color */ + if (forward) + { + for (y = 0; y < settings.lines && !found; y++) + { + count = 0; + /* count of white/black pixels depending on the color searched */ + for (x = 0; x < settings.pixels; x++) + { + /* when searching for black, detect white pixels */ + if (black && data[y * settings.pixels + x] > 90) + { + count++; + } + /* when searching for white, detect black pixels */ + if (!black && data[y * settings.pixels + x] < 60) + { + count++; + } + } + + /* at end of line, if count >= 3%, line is not fully of the desired color + * so we must go to next line of the buffer */ + /* count*100/pixels < 3 */ + if ((count * 100) / settings.pixels < 3) + { + found = 1; + DBG (DBG_data, + "gl646_search_strip: strip found forward during pass %d at line %d\n", + pass, y); + } + else + { + DBG (DBG_data, "gl646_search_strip: pixels=%d, count=%d\n", + settings.pixels, count); + } + } + } + else /* since calibration scans are done forward, we need the whole area + to be of the required color when searching backward */ + { + count = 0; + for (y = 0; y < settings.lines; y++) + { + /* count of white/black pixels depending on the color searched */ + for (x = 0; x < settings.pixels; x++) + { + /* when searching for black, detect white pixels */ + if (black && data[y * settings.pixels + x] > 60) + { + count++; + } + /* when searching for white, detect black pixels */ + if (!black && data[y * settings.pixels + x] < 60) + { + count++; + } + } + } + + /* at end of area, if count >= 3%, area is not fully of the desired color + * so we must go to next buffer */ + if ((count * 100) / (settings.pixels * settings.lines) < 3) + { + found = 1; + DBG (DBG_data, + "gl646_search_strip: strip found backward during pass %d \n", + pass); + } + else + { + DBG (DBG_data, "gl646_search_strip: pixels=%d, count=%d\n", + settings.pixels, count); + } + } + pass++; + } + free (data); + if (found) + { + status = SANE_STATUS_GOOD; + DBG (DBG_info, "gl646_search_strip: strip found\n"); + } + else + { + status = SANE_STATUS_UNSUPPORTED; + DBG (DBG_info, "gl646_search_strip: strip not found\n"); + } + return status; +} + +/** the gl646 command set */ +static Genesys_Command_Set gl646_cmd_set = { + "gl646-generic", /* the name of this set */ + + gl646_init, + gl646_init_regs_for_warmup, + gl646_init_regs_for_coarse_calibration, + gl646_init_regs_for_shading, + gl646_init_regs_for_scan, + + gl646_get_filter_bit, + gl646_get_lineart_bit, + gl646_get_bitset_bit, + gl646_get_gain4_bit, + gl646_get_fast_feed_bit, + gl646_test_buffer_empty_bit, + gl646_test_motor_flag_bit, + + gl646_bulk_full_size, + + gl646_public_set_fe, + gl646_set_powersaving, + gl646_save_power, + gl646_set_motor_power, + gl646_set_lamp_power, + + gl646_begin_scan, + gl646_end_scan, + + gl646_send_gamma_table, + + gl646_search_start_position, + + gl646_offset_calibration, + gl646_coarse_gain_calibration, + gl646_led_calibration, + + gl646_slow_back_home, + + gl646_bulk_write_register, + gl646_bulk_write_data, + gl646_bulk_read_data, + + gl646_update_hardware_sensors, + + /* sheetfed related functions */ + gl646_load_document, + gl646_detect_document_end, + gl646_eject_document, + gl646_search_strip, + + gl646_is_compatible_calibration, + gl646_move_to_ta, + NULL, + NULL, + NULL +}; + +SANE_Status +sanei_gl646_init_cmd_set (Genesys_Device * dev) +{ + dev->model->cmd_set = &gl646_cmd_set; + return SANE_STATUS_GOOD; +} + +/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */ |