diff options
Diffstat (limited to 'backend/genesys/gl843.cpp')
| -rw-r--r-- | backend/genesys/gl843.cpp | 3060 | 
1 files changed, 3060 insertions, 0 deletions
| diff --git a/backend/genesys/gl843.cpp b/backend/genesys/gl843.cpp new file mode 100644 index 0000000..f83ac8d --- /dev/null +++ b/backend/genesys/gl843.cpp @@ -0,0 +1,3060 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + + +   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. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "gl843_registers.h" +#include "gl843.h" +#include "test_settings.h" + +#include <string> +#include <vector> + +namespace genesys { +namespace gl843 { + +// Set address for writing data +static void gl843_set_buffer_address(Genesys_Device* dev, uint32_t addr) +{ +    DBG_HELPER_ARGS(dbg, "setting address to 0x%05x", addr & 0xffff); + +    dev->interface->write_register(0x5b, ((addr >> 8) & 0xff)); +    dev->interface->write_register(0x5c, (addr & 0xff)); +} + +/** + * compute the step multiplier used + */ +static int gl843_get_step_multiplier(Genesys_Register_Set* regs) +{ +    GenesysRegister *r = sanei_genesys_get_address(regs, REG_0x9D); +    int value = 1; +  if (r != nullptr) +    { +      switch (r->value & 0x0c) +	{ +	case 0x04: +	  value = 2; +	  break; +	case 0x08: +	  value = 4; +	  break; +	default: +	  value = 1; +	} +    } +  DBG(DBG_io, "%s: step multiplier is %d\n", __func__, value); +  return value; +} + +/** copy sensor specific settings */ +static void gl843_setup_sensor(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set* regs) +{ +    DBG_HELPER(dbg); +    for (const auto& custom_reg : sensor.custom_regs) { +        regs->set8(custom_reg.address, custom_reg.value); +    } +    if (!(dev->model->flags & GENESYS_FLAG_FULL_HWDPI_MODE) && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300 && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        regs->set8(0x7d, 0x90); +    } + +    dev->segment_order = sensor.segment_order; +} + + +/** @brief set all registers to default values . + * This function is called only once at the beginning and + * fills register startup values for registers reused across scans. + * Those that are rarely modified or not modified are written + * individually. + * @param dev device structure holding register set to initialize + */ +static void +gl843_init_registers (Genesys_Device * dev) +{ +  // Within this function SENSOR_DEF marker documents that a register is part +  // of the sensors definition and the actual value is set in +  // gl843_setup_sensor(). + +    // 0x6c, 0x6d, 0x6e, 0x6f, 0xa6, 0xa7, 0xa8, 0xa9 are defined in the Gpo sensor struct + +    DBG_HELPER(dbg); + +    dev->reg.clear(); + +    dev->reg.init_reg(0x01, 0x00); +    dev->reg.init_reg(0x02, 0x78); +    dev->reg.init_reg(0x03, 0x1f); +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x03, 0x1d); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x03, 0x1c); +    } + +    dev->reg.init_reg(0x04, 0x10); +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x04, 0x22); +    } + +    // fine tune upon device description +    dev->reg.init_reg(0x05, 0x80); +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +      dev->reg.init_reg(0x05, 0x08); +    } + +    const auto& sensor = sanei_genesys_find_sensor_any(dev); +    sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res); + +    // TODO: on 8600F the windows driver turns off GAIN4 which is recommended +    dev->reg.init_reg(0x06, 0xd8); /* SCANMOD=110, PWRBIT and GAIN4 */ +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x06, 0xd8); /* SCANMOD=110, PWRBIT and GAIN4 */ +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) { +        dev->reg.init_reg(0x06, 0xd0); +    } +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x06, 0xf0); /* SCANMOD=111, PWRBIT and no GAIN4 */ +    } + +  dev->reg.init_reg(0x08, 0x00); +  dev->reg.init_reg(0x09, 0x00); +  dev->reg.init_reg(0x0a, 0x00); +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x0a, 0x18); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x0a, 0x10); +    } + +    // This register controls clock and RAM settings and is further modified in +    // gl843_boot +    dev->reg.init_reg(0x0b, 0x6a); + +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x0b, 0x69); // 16M only +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x0b, 0x89); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) { +        dev->reg.init_reg(0x0b, 0x2a); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) { +        dev->reg.init_reg(0x0b, 0x4a); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x0b, 0x69); +    } + +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +        dev->reg.init_reg(0x0c, 0x00); +    } + +    // EXPR[0:15], EXPG[0:15], EXPB[0:15]: Exposure time settings. +    dev->reg.init_reg(0x10, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x11, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x12, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x13, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x14, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x15, 0x00); // SENSOR_DEF +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.set16(REG_EXPR, 0x9c40); +        dev->reg.set16(REG_EXPG, 0x9c40); +        dev->reg.set16(REG_EXPB, 0x9c40); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.set16(REG_EXPR, 0x2c09); +        dev->reg.set16(REG_EXPG, 0x22b8); +        dev->reg.set16(REG_EXPB, 0x10f0); +    } + +    // CCD signal settings. +    dev->reg.init_reg(0x16, 0x33); // SENSOR_DEF +    dev->reg.init_reg(0x17, 0x1c); // SENSOR_DEF +    dev->reg.init_reg(0x18, 0x10); // SENSOR_DEF + +    // EXPDMY[0:7]: Exposure time of dummy lines. +    dev->reg.init_reg(0x19, 0x2a); // SENSOR_DEF + +    // Various CCD clock settings. +    dev->reg.init_reg(0x1a, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x1c, 0x20); // SENSOR_DEF +    dev->reg.init_reg(0x1d, 0x04); // SENSOR_DEF + +    dev->reg.init_reg(0x1e, 0x10); +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0x1e, 0x20); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x1e, 0xa0); +    } + +    dev->reg.init_reg(0x1f, 0x01); +    if (dev->model->model_id == ModelId::CANON_8600F) { +      dev->reg.init_reg(0x1f, 0xff); +    } + +    dev->reg.init_reg(0x20, 0x10); +    dev->reg.init_reg(0x21, 0x04); + +    dev->reg.init_reg(0x22, 0x10); +    dev->reg.init_reg(0x23, 0x10); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x22, 0xc8); +        dev->reg.init_reg(0x23, 0xc8); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x22, 0x50); +        dev->reg.init_reg(0x23, 0x50); +    } + +    dev->reg.init_reg(0x24, 0x04); +    dev->reg.init_reg(0x25, 0x00); +    dev->reg.init_reg(0x26, 0x00); +    dev->reg.init_reg(0x27, 0x00); +    dev->reg.init_reg(0x2c, 0x02); +    dev->reg.init_reg(0x2d, 0x58); +    // BWHI[0:7]: high level of black and white threshold +    dev->reg.init_reg(0x2e, 0x80); +    // BWLOW[0:7]: low level of black and white threshold +    dev->reg.init_reg(0x2f, 0x80); +    dev->reg.init_reg(0x30, 0x00); +    dev->reg.init_reg(0x31, 0x14); +    dev->reg.init_reg(0x32, 0x27); +    dev->reg.init_reg(0x33, 0xec); + +    // DUMMY: CCD dummy and optically black pixel count +    dev->reg.init_reg(0x34, 0x24); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x34, 0x14); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x34, 0x3c); +    } + +    // MAXWD: If available buffer size is less than 2*MAXWD words, then +    // "buffer full" state will be set. +    dev->reg.init_reg(0x35, 0x00); +    dev->reg.init_reg(0x36, 0xff); +    dev->reg.init_reg(0x37, 0xff); + +    // LPERIOD: Line period or exposure time for CCD or CIS. +    dev->reg.init_reg(0x38, 0x55); // SENSOR_DEF +    dev->reg.init_reg(0x39, 0xf0); // SENSOR_DEF + +    // FEEDL[0:24]: The number of steps of motor movement. +    dev->reg.init_reg(0x3d, 0x00); +    dev->reg.init_reg(0x3e, 0x00); +    dev->reg.init_reg(0x3f, 0x01); + +    // Latch points for high and low bytes of R, G and B channels of AFE. If +    // multiple clocks per pixel are consumed, then the setting defines during +    // which clock the corresponding value will be read. +    // RHI[0:4]: The latch point for high byte of R channel. +    // RLOW[0:4]: The latch point for low byte of R channel. +    // GHI[0:4]: The latch point for high byte of G channel. +    // GLOW[0:4]: The latch point for low byte of G channel. +    // BHI[0:4]: The latch point for high byte of B channel. +    // BLOW[0:4]: The latch point for low byte of B channel. +    dev->reg.init_reg(0x52, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x53, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x54, 0x07); // SENSOR_DEF +    dev->reg.init_reg(0x55, 0x0a); // SENSOR_DEF +    dev->reg.init_reg(0x56, 0x0d); // SENSOR_DEF +    dev->reg.init_reg(0x57, 0x10); // SENSOR_DEF + +    // VSMP[0:4]: The position of the image sampling pulse for AFE in cycles. +    // VSMPW[0:2]: The length of the image sampling pulse for AFE in cycles. +    dev->reg.init_reg(0x58, 0x1b); // SENSOR_DEF + +    dev->reg.init_reg(0x59, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x5a, 0x40); // SENSOR_DEF + +    // 0x5b-0x5c: GMMADDR[0:15] address for gamma or motor tables download +    // SENSOR_DEF + +    // DECSEL[0:2]: The number of deceleration steps after touching home sensor +    // STOPTIM[0:4]: The stop duration between change of directions in +    // backtracking +    dev->reg.init_reg(0x5e, 0x23); +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x5e, 0x3f); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x5e, 0x85); +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x5e, 0x1f); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x5e, 0x01); +    } + +    //FMOVDEC: The number of deceleration steps in table 5 for auto-go-home +    dev->reg.init_reg(0x5f, 0x01); +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x5f, 0xf0); +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x5f, 0xf0); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x5f, 0x01); +    } + +    // Z1MOD[0:20] +    dev->reg.init_reg(0x60, 0x00); +    dev->reg.init_reg(0x61, 0x00); +    dev->reg.init_reg(0x62, 0x00); + +    // Z2MOD[0:20] +    dev->reg.init_reg(0x63, 0x00); +    dev->reg.init_reg(0x64, 0x00); +    dev->reg.init_reg(0x65, 0x00); + +    // STEPSEL[0:1]. Motor movement step mode selection for tables 1-3 in +    // scanning mode. +    // MTRPWM[0:5]. Motor phase PWM duty cycle setting for tables 1-3 +    dev->reg.init_reg(0x67, 0x7f); +    // FSTPSEL[0:1]: Motor movement step mode selection for tables 4-5 in +    // command mode. +    // FASTPWM[5:0]: Motor phase PWM duty cycle setting for tables 4-5 +    dev->reg.init_reg(0x68, 0x7f); + +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300) { +        dev->reg.init_reg(0x67, 0x80); +        dev->reg.init_reg(0x68, 0x80); +    } + +    // FSHDEC[0:7]: The number of deceleration steps after scanning is finished +    // (table 3) +    dev->reg.init_reg(0x69, 0x01); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x69, 64); +    } + +    // FMOVNO[0:7] The number of acceleration or deceleration steps for fast +    // moving (table 4) +    dev->reg.init_reg(0x6a, 0x04); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x69, 64); +    } + +    // GPIO-related register bits +    dev->reg.init_reg(0x6b, 0x30); +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0x6b, 0x72); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x6b, 0xb1); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x6b, 0xf4); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x6b, 0x31); +    } + +    // 0x6c, 0x6d, 0x6e, 0x6f are set according to gpio tables. See +    // gl843_init_gpio. + +    // RSH[0:4]: The position of rising edge of CCD RS signal in cycles +    // RSL[0:4]: The position of falling edge of CCD RS signal in cycles +    // CPH[0:4]: The position of rising edge of CCD CP signal in cycles. +    // CPL[0:4]: The position of falling edge of CCD CP signal in cycles +    dev->reg.init_reg(0x70, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x71, 0x03); // SENSOR_DEF +    dev->reg.init_reg(0x72, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x73, 0x05); // SENSOR_DEF + +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x70, 0x01); +        dev->reg.init_reg(0x71, 0x03); +        dev->reg.init_reg(0x72, 0x01); +        dev->reg.init_reg(0x73, 0x03); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x70, 0x01); +        dev->reg.init_reg(0x71, 0x03); +        dev->reg.init_reg(0x72, 0x03); +        dev->reg.init_reg(0x73, 0x04); +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x70, 0x00); +        dev->reg.init_reg(0x71, 0x02); +        dev->reg.init_reg(0x72, 0x02); +        dev->reg.init_reg(0x73, 0x04); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x70, 0x00); +        dev->reg.init_reg(0x71, 0x02); +        dev->reg.init_reg(0x72, 0x00); +        dev->reg.init_reg(0x73, 0x00); +    } + +    // CK1MAP[0:17], CK3MAP[0:17], CK4MAP[0:17]: CCD clock bit mapping setting. +    dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF +    dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF +    dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF + +    // various AFE settings +    dev->reg.init_reg(0x7d, 0x00); +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x7d, 0x20); +    } + +    // GPOLED[x]: LED vs GPIO settings +    dev->reg.init_reg(0x7e, 0x00); + +    // BSMPDLY, VSMPDLY +    // LEDCNT[0:1]: Controls led blinking and its period +    dev->reg.init_reg(0x7f, 0x00); + +    // VRHOME, VRMOVE, VRBACK, VRSCAN: Vref settings of the motor driver IC for +    // moving in various situations. +    dev->reg.init_reg(0x80, 0x00); +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x80, 0x0c); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x80, 0x28); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x80, 0x50); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x80, 0x0f); +    } + +    if (dev->model->model_id != ModelId::CANON_4400F) { +        dev->reg.init_reg(0x81, 0x00); +        dev->reg.init_reg(0x82, 0x00); +        dev->reg.init_reg(0x83, 0x00); +        dev->reg.init_reg(0x84, 0x00); +        dev->reg.init_reg(0x85, 0x00); +        dev->reg.init_reg(0x86, 0x00); +    } + +    dev->reg.init_reg(0x87, 0x00); +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0x87, 0x02); +    } + +    // MTRPLS[0:7]: The width of the ADF motor trigger signal pulse. +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +        dev->reg.init_reg(0x94, 0xff); +    } + +    // 0x95-0x97: SCANLEN[0:19]: Controls when paper jam bit is set in sheetfed +    // scanners. + +    // ONDUR[0:15]: The duration of PWM ON phase for LAMP control +    // OFFDUR[0:15]: The duration of PWM OFF phase for LAMP control +    // both of the above are in system clocks +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x98, 0x00); +        dev->reg.init_reg(0x99, 0x00); +        dev->reg.init_reg(0x9a, 0x00); +        dev->reg.init_reg(0x9b, 0x00); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        // TODO: move to set for scan +        dev->reg.init_reg(0x98, 0x03); +        dev->reg.init_reg(0x99, 0x30); +        dev->reg.init_reg(0x9a, 0x01); +        dev->reg.init_reg(0x9b, 0x80); +    } + +    // RMADLY[0:1], MOTLAG, CMODE, STEPTIM, MULDMYLN, IFRS +    dev->reg.init_reg(0x9d, 0x04); +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x9d, 0x00); +    } +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8400F || +        dev->model->model_id == ModelId::CANON_8600F || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +        dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x9d, 0x08); // sets the multiplier for slope tables +    } + + +    // SEL3INV, TGSTIME[0:2], TGWTIME[0:2] +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +      dev->reg.init_reg(0x9e, 0x00); // SENSOR_DEF +    } + +    if (dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) { +        dev->reg.init_reg(0xa2, 0x0f); +    } + +    // RFHSET[0:4]: Refresh time of SDRAM in units of 2us +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0xa2, 0x1f); +    } + +    // 0xa6-0xa9: controls gpio, see gl843_gpio_init + +    // not documented +    if (dev->model->model_id != ModelId::CANON_4400F && +        dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +        dev->reg.init_reg(0xaa, 0x00); +    } + +    // GPOM9, MULSTOP[0-2], NODECEL, TB3TB1, TB5TB2, FIX16CLK. Not documented +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) { +        dev->reg.init_reg(0xab, 0x50); +    } +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0xab, 0x00); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        // BUG: this should apply to ModelId::CANON_CANOSCAN_8600F too, but due to previous bug +        // the 8400F case overwrote it +        dev->reg.init_reg(0xab, 0x40); +    } + +    // VRHOME[3:2], VRMOVE[3:2], VRBACK[3:2]: Vref setting of the motor driver IC +    // for various situations. +    if (dev->model->model_id == ModelId::CANON_8600F || +        dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0xac, 0x00); +    } + +    dev->calib_reg = dev->reg; + +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) { +        uint8_t data[32] = { +            0x8c, 0x8f, 0xc9, 0x00, 0x01, 0x00, 0x00, 0x00, +            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +            0x6a, 0x73, 0x63, 0x68, 0x69, 0x65, 0x6e, 0x00, +        }; + +        dev->interface->write_buffer(0x3c, 0x3ff000, data, 32, +                                     ScannerInterface::FLAG_SWAP_REGISTERS); +    } +} + +// Send slope table for motor movement slope_table in machine byte order +static void gl843_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps) +{ +    DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps); + +  int i; +  char msg[10000]; + +  std::vector<uint8_t> table(steps * 2); +  for (i = 0; i < steps; i++) +    { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +    } + +  if (DBG_LEVEL >= DBG_io) +    { +        std::sprintf(msg, "write slope %d (%d)=", table_nr, steps); +        for (i = 0; i < steps; i++) { +            std::sprintf (msg+strlen(msg), "%d", slope_table[i]); +	} +      DBG(DBG_io, "%s: %s\n", __func__, msg); +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } + +    // slope table addresses are fixed : 0x40000,  0x48000,  0x50000,  0x58000,  0x60000 +    // XXX STEF XXX USB 1.1 ? sanei_genesys_write_0x8c (dev, 0x0f, 0x14); +    dev->interface->write_gamma(0x28,  0x40000 + 0x8000 * table_nr, table.data(), steps * 2, +                                ScannerInterface::FLAG_SWAP_REGISTERS); + +    // FIXME: remove this when updating tests +    gl843_set_buffer_address(dev, 0); +} + +static void gl843_set_ad_fe(Genesys_Device* dev) +{ +    for (const auto& reg : dev->frontend.regs) { +        dev->interface->write_fe_register(reg.address, reg.value); +    } +} + +// Set values of analog frontend +void CommandSetGl843::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" : +                               set == AFE_SET ? "set" : +                               set == AFE_POWER_SAVE ? "powersave" : "huh?"); +    (void) sensor; +  int i; + +  if (set == AFE_INIT) +    { +        DBG(DBG_proc, "%s(): setting DAC %u\n", __func__, +            static_cast<unsigned>(dev->model->adc_id)); +      dev->frontend = dev->frontend_initial; +        dev->frontend_is_init = true; +    } + +    // check analog frontend type +    // FIXME: looks like we write to that register with initial data +    uint8_t fe_type = dev->interface->read_register(REG_0x04) & REG_0x04_FESET; +    if (fe_type == 2) { +        gl843_set_ad_fe(dev); +        return; +    } +    if (fe_type != 0) { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "unsupported frontend type %d", fe_type); +    } + +  DBG(DBG_proc, "%s(): frontend reset complete\n", __func__); + +  for (i = 1; i <= 3; i++) +    { +        // FIXME: the check below is just historical artifact, we can remove it when convenient +        if (!dev->frontend_is_init) { +            dev->interface->write_fe_register(i, 0x00); +        } else { +            dev->interface->write_fe_register(i, dev->frontend.regs.get_value(0x00 + i)); +        } +    } +    for (const auto& reg : sensor.custom_fe_regs) { +        dev->interface->write_fe_register(reg.address, reg.value); +    } + +  for (i = 0; i < 3; i++) +    { +         // FIXME: the check below is just historical artifact, we can remove it when convenient +        if (!dev->frontend_is_init) { +            dev->interface->write_fe_register(0x20 + i, 0x00); +        } else { +            dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i)); +        } +    } + +    if (dev->model->sensor_id == SensorId::CCD_KVSS080) { +      for (i = 0; i < 3; i++) +	{ +            // FIXME: the check below is just historical artifact, we can remove it when convenient +            if (!dev->frontend_is_init) { +                dev->interface->write_fe_register(0x24 + i, 0x00); +            } else { +                dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i)); +            } +	} +    } + +  for (i = 0; i < 3; i++) +    { +        // FIXME: the check below is just historical artifact, we can remove it when convenient +        if (!dev->frontend_is_init) { +            dev->interface->write_fe_register(0x28 + i, 0x00); +        } else { +            dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i)); +        } +    } +} + + +static void gl843_init_motor_regs_scan(Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       Genesys_Register_Set* reg, +                                       const Motor_Profile& motor_profile, +                                       unsigned int exposure, +                                       unsigned scan_yres, +                                       unsigned int scan_lines, +                                       unsigned int scan_dummy, +                                       unsigned int feed_steps, +                                       MotorFlag flags) +{ +    DBG_HELPER_ARGS(dbg, "exposure=%d, scan_yres=%d, step_type=%d, scan_lines=%d, scan_dummy=%d, " +                         "feed_steps=%d, flags=%x", +                    exposure, scan_yres, static_cast<unsigned>(motor_profile.step_type), +                    scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags)); + +  int use_fast_fed, coeff; +  unsigned int lincnt; +    unsigned feedl, dist; +  GenesysRegister *r; +  uint32_t z1, z2; + +  /* get step multiplier */ +    unsigned step_multiplier = gl843_get_step_multiplier (reg); + +  use_fast_fed = 0; + +    if ((scan_yres >= 300 && feed_steps > 900) || (has_flag(flags, MotorFlag::FEED))) { +        use_fast_fed = 1; +    } + +  lincnt=scan_lines; +    reg->set24(REG_LINCNT, lincnt); +  DBG(DBG_io, "%s: lincnt=%d\n", __func__, lincnt); + +  /* compute register 02 value */ +    r = sanei_genesys_get_address(reg, REG_0x02); +  r->value = 0x00; +  sanei_genesys_set_motor_power(*reg, true); + +    if (use_fast_fed) { +        r->value |= REG_0x02_FASTFED; +    } else { +        r->value &= ~REG_0x02_FASTFED; +    } + +  /* in case of automatic go home, move until home sensor */ +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME; +    } + +  /* disable backtracking */ +    if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) +      ||(scan_yres>=2400 && dev->model->model_id != ModelId::CANON_4400F) +      ||(scan_yres>=sensor.optical_res)) +    { +        r->value |= REG_0x02_ACDCDIS; +    } + +    if (has_flag(flags, MotorFlag::REVERSE)) { +        r->value |= REG_0x02_MTRREV; +    } else { +        r->value &= ~REG_0x02_MTRREV; +    } + +  /* scan and backtracking slope table */ +    auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres, exposure, +                                                dev->motor.base_ydpi, step_multiplier, +                                                motor_profile); + +    gl843_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count); +    gl843_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count); + +    reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier); + +    // fast table +    unsigned fast_yres = sanei_genesys_get_lowest_ydpi(dev); +    auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_yres, exposure, +                                                dev->motor.base_ydpi, step_multiplier, +                                                motor_profile); +    gl843_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count); +    gl843_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count); +    gl843_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count); + +    reg->set8(REG_FSHDEC, fast_table.steps_count / step_multiplier); +    reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier); + +  /* substract acceleration distance from feedl */ +  feedl=feed_steps; +    feedl <<= static_cast<unsigned>(motor_profile.step_type); + +    dist = scan_table.steps_count / step_multiplier; +  if (use_fast_fed) +    { +        dist += (fast_table.steps_count / step_multiplier) * 2; +    } +  DBG(DBG_io2, "%s: acceleration distance=%d\n", __func__, dist); + +  /* get sure when don't insane value : XXX STEF XXX in this case we should +   * fall back to single table move */ +    if (dist < feedl) { +        feedl -= dist; +    } else { +        feedl = 1; +    } + +    reg->set24(REG_FEEDL, feedl); +  DBG(DBG_io, "%s: feedl=%d\n", __func__, feedl); + +  /* doesn't seem to matter that much */ +    sanei_genesys_calculate_zmod(use_fast_fed, +				  exposure, +                                 scan_table.table, +                                 scan_table.steps_count / step_multiplier, +				  feedl, +                                 scan_table.steps_count / step_multiplier, +                                  &z1, +                                  &z2); +  if(scan_yres>600) +    { +      z1=0; +      z2=0; +    } + +    reg->set24(REG_Z1MOD, z1); +  DBG(DBG_info, "%s: z1 = %d\n", __func__, z1); + +    reg->set24(REG_Z2MOD, z2); +  DBG(DBG_info, "%s: z2 = %d\n", __func__, z2); + +    r = sanei_genesys_get_address(reg, REG_0x1E); +  r->value &= 0xf0;		/* 0 dummy lines */ +  r->value |= scan_dummy;	/* dummy lines */ + +    reg->set8_mask(REG_0x67, static_cast<unsigned>(motor_profile.step_type) << REG_0x67S_STEPSEL, 0xc0); +    reg->set8_mask(REG_0x68, static_cast<unsigned>(motor_profile.step_type) << REG_0x68S_FSTPSEL, 0xc0); + +    // steps for STOP table +    reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier); + +  /* Vref XXX STEF XXX : optical divider or step type ? */ +  r = sanei_genesys_get_address (reg, 0x80); +  if (!(dev->model->flags & GENESYS_FLAG_FULL_HWDPI_MODE)) +    { +      r->value = 0x50; +        coeff = sensor.get_hwdpi_divisor_for_dpi(scan_yres); +        if (dev->model->motor_id == MotorId::KVSS080) { +          if(coeff>=1) +            { +              r->value |= 0x05; +            } +        } +      else { +        switch(coeff) +          { +          case 4: +              r->value |= 0x0a; +              break; +          case 2: +              r->value |= 0x0f; +              break; +          case 1: +              r->value |= 0x0f; +              break; +          } +        } +    } +} + + +/** @brief setup optical related registers + * start and pixels are expressed in optical sensor resolution coordinate + * space. + * @param dev device to use + * @param reg registers to set up + * @param exposure exposure time to use + * @param used_res scanning resolution used, may differ from + *        scan's one + * @param start logical start pixel coordinate + * @param pixels logical number of pixels to use + * @param channels number of color channles used (1 or 3) + * @param depth bit depth of the scan (1, 8 or 16 bits) + * @param ccd_size_divisor true specifies how much x coordinates must be shrunk + * @param color_filter to choose the color channel used in gray scans + * @param flags to drive specific settings such no calibration, XPA use ... + */ +static void gl843_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set* reg, unsigned int exposure, +                                         const ScanSession& session) +{ +    DBG_HELPER_ARGS(dbg, "exposure=%d", exposure); +    unsigned int dpihw; +  unsigned int tgtime;          /**> exposure time multiplier */ +  GenesysRegister *r; + +  /* tgtime */ +  tgtime = exposure / 65536 + 1; +  DBG(DBG_io2, "%s: tgtime=%d\n", __func__, tgtime); + +    // to manage high resolution device while keeping good low resolution scanning speed, we make +    // hardware dpi vary +    dpihw = sensor.get_register_hwdpi(session.output_resolution); +    DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw); + +  /* sensor parameters */ +    gl843_setup_sensor(dev, sensor, reg); + +    // resolution is divided according to CKSEL +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); +    DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel); + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); + +  /* enable shading */ +    regs_set_optical_off(dev->model->asic_type, *reg); +    r = sanei_genesys_get_address (reg, REG_0x01); +    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) || +        (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION || +        (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE))) +    { +        r->value &= ~REG_0x01_DVDSET; +    } else { +        r->value |= REG_0x01_DVDSET; +    } + +    bool use_shdarea = dpihw > 600; +    if (dev->model->model_id == ModelId::CANON_4400F) { +        use_shdarea = session.params.xres <= 600; +    } else if (dev->model->model_id == ModelId::CANON_8400F) { +        use_shdarea = session.params.xres <= 400; +    } +    if (use_shdarea) { +        r->value |= REG_0x01_SHDAREA; +    } else { +        r->value &= ~REG_0x01_SHDAREA; +    } + +    r = sanei_genesys_get_address (reg, REG_0x03); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        r->value |= REG_0x03_AVEENB; +    } else { +        r->value &= ~REG_0x03_AVEENB; +  } + +    // FIXME: we probably don't need to set exposure to registers at this point. It was this way +    // before a refactor. +    sanei_genesys_set_lamp_power(dev, sensor, *reg, +                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP)); + +  /* select XPA */ +    r->value &= ~REG_0x03_XPASEL; +    if (has_flag(session.params.flags, ScanFlag::USE_XPA)) { +        r->value |= REG_0x03_XPASEL; +    } +    reg->state.is_xpa_on = has_flag(session.params.flags, ScanFlag::USE_XPA); + +  /* BW threshold */ +    r = sanei_genesys_get_address(reg, REG_0x2E); +  r->value = dev->settings.threshold; +    r = sanei_genesys_get_address(reg, REG_0x2F); +  r->value = dev->settings.threshold; + +  /* monochrome / color scan */ +    r = sanei_genesys_get_address(reg, REG_0x04); +    switch (session.params.depth) { +    case 8: +            r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +      break; +    case 16: +            r->value &= ~REG_0x04_LINEART; +            r->value |= REG_0x04_BITSET; +      break; +    } + +    r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD); +  if (session.params.channels == 1) +    { +      switch (session.params.color_filter) +	{ +            case ColorFilter::RED: +                r->value |= 0x14; +                break; +            case ColorFilter::BLUE: +                r->value |= 0x1c; +                break; +            case ColorFilter::GREEN: +                r->value |= 0x18; +                break; +            default: +                break; // should not happen +	} +    } else { +        switch (dev->frontend.layout.type) { +            case FrontendType::WOLFSON: +                r->value |= 0x10; // pixel by pixel +                break; +            case FrontendType::ANALOG_DEVICES: +                r->value |= 0x20; // slow color pixel by pixel +                break; +            default: +                throw SaneException("Invalid frontend type %d", +                                    static_cast<unsigned>(dev->frontend.layout.type)); +        } +    } + +    sanei_genesys_set_dpihw(*reg, sensor, dpihw); + +    if (should_enable_gamma(session, sensor)) { +        reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB; +    } else { +        reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB; +    } + +    unsigned dpiset = session.output_resolution * session.ccd_size_divisor * +            ccd_pixels_per_system_pixel; + +    if (sensor.dpiset_override != 0) { +        dpiset = sensor.dpiset_override; +    } +    reg->set16(REG_DPISET, dpiset); +    DBG(DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset); + +    reg->set16(REG_STRPIXEL, session.pixel_startx); +    reg->set16(REG_ENDPIXEL, session.pixel_endx); + +  /* MAXWD is expressed in 2 words unit */ +  /* nousedspace = (mem_bank_range * 1024 / 256 -1 ) * 4; */ +    // BUG: the division by ccd_size_divisor likely does not make sense +    reg->set24(REG_MAXWD, (session.output_line_bytes / session.ccd_size_divisor) >> 1); + +    reg->set16(REG_LPERIOD, exposure / tgtime); +  DBG(DBG_io2, "%s: exposure used=%d\n", __func__, exposure/tgtime); + +  r = sanei_genesys_get_address (reg, REG_DUMMY); +  r->value = sensor.dummy_pixel; +} + +void CommandSetGl843::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* reg, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +  int exposure; + +  int slope_dpi = 0; +  int dummy = 0; + +  /* we enable true gray for cis scanners only, and just when doing +   * scan since color calibration is OK for this mode +   */ + +  dummy = 0; +    if (dev->model->model_id == ModelId::CANON_4400F && session.params.yres == 1200) { +        dummy = 1; +    } + +  /* slope_dpi */ +  /* cis color scan is effectively a gray scan with 3 gray lines per color line and a FILTER of 0 */ +  if (dev->model->is_cis) +    slope_dpi = session.params.yres * session.params.channels; +  else +    slope_dpi = session.params.yres; +  slope_dpi = slope_dpi * (1 + dummy); + +  /* scan_step_type */ +  exposure = sensor.exposure_lperiod; +  if (exposure < 0) { +      throw std::runtime_error("Exposure not defined in sensor definition"); +  } +    const auto& motor_profile = sanei_genesys_get_motor_profile(*gl843_motor_profiles, +                                                                dev->model->motor_id, +                                                                exposure); + +  DBG(DBG_info, "%s : exposure=%d pixels\n", __func__, exposure); +    DBG(DBG_info, "%s : scan_step_type=%d\n", __func__, +        static_cast<unsigned>(motor_profile.step_type)); + +    // now _LOGICAL_ optical values used are known, setup registers +    gl843_init_optical_regs_scan(dev, sensor, reg, exposure, session); + +  /*** motor parameters ***/ +    MotorFlag mflags = MotorFlag::NONE; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) { +        mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE; +    } +    if (has_flag(session.params.flags, ScanFlag::FEEDING)) { +        mflags |= MotorFlag::FEED; +    } +    if (has_flag(session.params.flags, ScanFlag::USE_XPA)) { +        mflags |= MotorFlag::USE_XPA; +    } +    if (has_flag(session.params.flags, ScanFlag::REVERSE)) { +        mflags |= MotorFlag::REVERSE; +    } + +    unsigned scan_lines = dev->model->is_cis ? session.output_line_count * session.params.channels +                                             : session.output_line_count; + +    gl843_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure, slope_dpi, +                               scan_lines, dummy, session.params.starty, mflags); + +    dev->read_buffer.clear(); +    dev->read_buffer.alloc(session.buffer_size_read); + +    build_image_pipeline(dev, session); + +    dev->read_active = true; + +    dev->session = session; + +  dev->total_bytes_read = 0; +    dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines; + +    DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read); +} + +ScanSession CommandSetGl843::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +    DBG_HELPER(dbg); +    debug_dump(DBG_info, settings); + +  int start; + +  /* we have 2 domains for ccd: xres below or above half ccd max dpi */ +  unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(settings.xres); + +    if (settings.scan_method == ScanMethod::TRANSPARENCY || +        settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        start = static_cast<int>(dev->model->x_offset_ta); +    } else { +        start = static_cast<int>(dev->model->x_offset); +    } + +    if (dev->model->model_id == ModelId::CANON_8600F) +    { +        // FIXME: this is probably just an artifact of a bug elsewhere +        start /= ccd_size_divisor; +    } + +    start += static_cast<int>(settings.tl_x); +    start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = start; // not used +    session.params.starty = 0; // not used +    session.params.pixels = settings.pixels; +    session.params.requested_pixels = settings.requested_pixels; +    session.params.lines = settings.lines; +    session.params.depth = settings.depth; +    session.params.channels = settings.get_channels(); +    session.params.scan_method = settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; + +    compute_session(dev, session, sensor); + +    return session; +} + +/** + * for fast power saving methods only, like disabling certain amplifiers + * @param dev device to use + * @param enable true to set inot powersaving + * */ +void CommandSetGl843::save_power(Genesys_Device* dev, bool enable) const +{ +    DBG_HELPER_ARGS(dbg, "enable = %d", enable); + +    // switch KV-SS080 lamp off +    if (dev->model->gpio_id == GpioId::KVSS080) { +        uint8_t val = dev->interface->read_register(REG_0x6C); +        if (enable) { +            val &= 0xef; +        } else { +            val |= 0x10; +        } +        dev->interface->write_register(REG_0x6C, val); +    } +} + +void CommandSetGl843::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    (void) dev; +    DBG_HELPER_ARGS(dbg, "delay = %d", delay); +} + +static bool gl843_get_paper_sensor(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +    uint8_t val = dev->interface->read_register(REG_0x6D); + +    return (val & 0x1) == 0; +} + +void CommandSetGl843::eject_document(Genesys_Device* dev) const +{ +    (void) dev; +    DBG_HELPER(dbg); +} + + +void CommandSetGl843::load_document(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +    (void) dev; +} + +/** + * detects end of document and adjust current scan + * to take it into account + * used by sheetfed scanners + */ +void CommandSetGl843::detect_document_end(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +    bool paper_loaded = gl843_get_paper_sensor(dev); + +  /* sheetfed scanner uses home sensor as paper present */ +    if (dev->document && !paper_loaded) { +      DBG(DBG_info, "%s: no more document\n", __func__); +        dev->document = false; + +        unsigned scanned_lines = 0; +        catch_all_exceptions(__func__, [&](){ sanei_genesys_read_scancnt(dev, &scanned_lines); }); + +        std::size_t output_lines = dev->session.output_line_count; + +        std::size_t offset_lines = static_cast<std::size_t>( +                (dev->model->post_scan * dev->session.params.yres) / MM_PER_INCH); + +        std::size_t scan_end_lines = scanned_lines + offset_lines; + +        std::size_t remaining_lines = dev->get_pipeline_source().remaining_bytes() / +                dev->session.output_line_bytes_raw; + +        DBG(DBG_io, "%s: scanned_lines=%u\n", __func__, scanned_lines); +        DBG(DBG_io, "%s: scan_end_lines=%zu\n", __func__, scan_end_lines); +        DBG(DBG_io, "%s: output_lines=%zu\n", __func__, output_lines); +        DBG(DBG_io, "%s: remaining_lines=%zu\n", __func__, remaining_lines); + +        if (scan_end_lines > output_lines) { +            auto skip_lines = scan_end_lines - output_lines; + +            if (remaining_lines > skip_lines) { +                DBG(DBG_io, "%s: skip_lines=%zu\n", __func__, skip_lines); + +                remaining_lines -= skip_lines; +                dev->get_pipeline_source().set_remaining_bytes(remaining_lines * +                                                               dev->session.output_line_bytes_raw); +                dev->total_bytes_to_read -= skip_lines * dev->session.output_line_bytes_requested; +            } +        } +    } +} + +// enables or disables XPA slider motor +void gl843_set_xpa_motor_power(Genesys_Device* dev, Genesys_Register_Set& regs, bool set) +{ +    DBG_HELPER(dbg); +    uint8_t val; + +    if (dev->model->model_id == ModelId::CANON_8400F) { + +        if (set) { +            val = dev->interface->read_register(0x6c); +            val &= ~(REG_0x6C_GPIO16 | REG_0x6C_GPIO13); +            if (dev->session.output_resolution >= 2400) { +                val &= ~REG_0x6C_GPIO10; +            } +            dev->interface->write_register(0x6c, val); + +            val = dev->interface->read_register(0xa9); +            val |= REG_0xA9_GPO30; +            val &= ~REG_0xA9_GPO29; +            dev->interface->write_register(0xa9, val); +        } else { +            val = dev->interface->read_register(0x6c); +            val |= REG_0x6C_GPIO16 | REG_0x6C_GPIO13; +            dev->interface->write_register(0x6c, val); + +            val = dev->interface->read_register(0xa9); +            val &= ~REG_0xA9_GPO30; +            val |= REG_0xA9_GPO29; +            dev->interface->write_register(0xa9, val); +        } +    } else if (dev->model->model_id == ModelId::CANON_8600F) { +        if (set) { +            val = dev->interface->read_register(REG_0x6C); +            val &= ~REG_0x6C_GPIO14; +            if (dev->session.output_resolution >= 2400) { +                val |= REG_0x6C_GPIO10; +            } +            dev->interface->write_register(REG_0x6C, val); + +            val = dev->interface->read_register(REG_0xA6); +            val |= REG_0xA6_GPIO17; +            val &= ~REG_0xA6_GPIO23; +            dev->interface->write_register(REG_0xA6, val); +        } else { +            val = dev->interface->read_register(REG_0x6C); +            val |= REG_0x6C_GPIO14; +            val &= ~REG_0x6C_GPIO10; +            dev->interface->write_register(REG_0x6C, val); + +            val = dev->interface->read_register(REG_0xA6); +            val &= ~REG_0xA6_GPIO17; +            val &= ~REG_0xA6_GPIO23; +            dev->interface->write_register(REG_0xA6, val); +        } +    } else if (dev->model->model_id == ModelId::HP_SCANJET_G4050) { +        if (set) { +            // set MULTFILM et GPOADF +            val = dev->interface->read_register(REG_0x6B); +            val |=REG_0x6B_MULTFILM|REG_0x6B_GPOADF; +            dev->interface->write_register(REG_0x6B, val); + +            val = dev->interface->read_register(REG_0x6C); +            val &= ~REG_0x6C_GPIO15; +            dev->interface->write_register(REG_0x6C, val); + +            /* Motor power ? No move at all without this one */ +            val = dev->interface->read_register(REG_0xA6); +            val |= REG_0xA6_GPIO20; +            dev->interface->write_register(REG_0xA6, val); + +            val = dev->interface->read_register(REG_0xA8); +            val &= ~REG_0xA8_GPO27; +            dev->interface->write_register(REG_0xA8, val); + +            val = dev->interface->read_register(REG_0xA9); +            val |= REG_0xA9_GPO32|REG_0xA9_GPO31; +            dev->interface->write_register(REG_0xA9, val); +        } else { +            // unset GPOADF +            val = dev->interface->read_register(REG_0x6B); +            val &= ~REG_0x6B_GPOADF; +            dev->interface->write_register(REG_0x6B, val); + +            val = dev->interface->read_register(REG_0xA8); +            val |= REG_0xA8_GPO27; +            dev->interface->write_register(REG_0xA8, val); + +            val = dev->interface->read_register(REG_0xA9); +            val &= ~REG_0xA9_GPO31; +            dev->interface->write_register(REG_0xA9, val); +        } +    } +    regs.state.is_xpa_motor_on = set; +} + + +/** @brief light XPA lamp + * toggle gpios to switch off regular lamp and light on the + * XPA light + * @param dev device to set up + */ +static void gl843_set_xpa_lamp_power(Genesys_Device* dev, bool set) +{ +    DBG_HELPER(dbg); + +    struct LampSettings { +        ModelId model_id; +        ScanMethod scan_method; +        GenesysRegisterSettingSet regs_on; +        GenesysRegisterSettingSet regs_off; +    }; + +    // FIXME: BUG: we're not clearing the registers to the previous state when returning back when +    // turning off the lamp +    LampSettings settings[] = { +        {   ModelId::CANON_8400F, ScanMethod::TRANSPARENCY, { +                { 0xa6, 0x34, 0xf4 }, +            }, { +                { 0xa6, 0x40, 0x70 }, +            } +        }, +        {   ModelId::CANON_8400F, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0x6c, 0x40, 0x40 }, +                { 0xa6, 0x01, 0xff }, +            }, { +                { 0x6c, 0x00, 0x40 }, +                { 0xa6, 0x00, 0xff }, +            } +        }, +        {   ModelId::CANON_8600F, ScanMethod::TRANSPARENCY, { +                { 0xa6, 0x34, 0xf4 }, +                { 0xa7, 0xe0, 0xe0 }, +            }, { +                { 0xa6, 0x40, 0x70 }, +            } +        }, +        {   ModelId::CANON_8600F, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0xa6, 0x00, 0xc0 }, +                { 0xa7, 0xe0, 0xe0 }, +                { 0x6c, 0x80, 0x80 }, +            }, { +                { 0xa6, 0x00, 0xc0 }, +                { 0x6c, 0x00, 0x80 }, +            } +        }, +        {   ModelId::PLUSTEK_OPTICFILM_7200I, ScanMethod::TRANSPARENCY, { +            }, { +                { 0xa6, 0x40, 0x70 }, // BUG: remove this cleanup write, it was enabled by accident +            } +        }, +        {   ModelId::PLUSTEK_OPTICFILM_7200I, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0xa8, 0x07, 0x07 }, +            }, { +                { 0xa8, 0x00, 0x07 }, +            } +        }, +        {   ModelId::PLUSTEK_OPTICFILM_7300, ScanMethod::TRANSPARENCY, {}, {} }, +        {   ModelId::PLUSTEK_OPTICFILM_7500I, ScanMethod::TRANSPARENCY, {}, {} }, +        {   ModelId::PLUSTEK_OPTICFILM_7500I, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0xa8, 0x07, 0x07 }, +            }, { +                { 0xa8, 0x00, 0x07 }, +            } +        }, +    }; + +    for (const auto& setting : settings) { +        if (setting.model_id == dev->model->model_id && +            setting.scan_method == dev->settings.scan_method) +        { +            apply_reg_settings_to_device(*dev, set ? setting.regs_on : setting.regs_off); +            return; +        } +    } + +    // BUG: we're currently calling the function in shut down path of regular lamp +    if (set) { +        throw SaneException("Unexpected code path entered"); +    } + +    GenesysRegisterSettingSet regs = { +        { 0xa6, 0x40, 0x70 }, +    }; +    apply_reg_settings_to_device(*dev, regs); +    // TODO: throw exception when we're only calling this function in error return path +    // throw SaneException("Could not find XPA lamp settings"); +} + +// Send the low-level scan command +void CommandSetGl843::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set* reg, bool start_motor) const +{ +    DBG_HELPER(dbg); +    (void) sensor; + +  /* set up GPIO for scan */ +    switch(dev->model->gpio_id) { +      /* KV case */ +        case GpioId::KVSS080: +            dev->interface->write_register(REG_0xA9, 0x00); +            dev->interface->write_register(REG_0xA6, 0xf6); +            // blinking led +            dev->interface->write_register(0x7e, 0x04); +            break; +        case GpioId::G4050: +            dev->interface->write_register(REG_0xA7, 0xfe); +            dev->interface->write_register(REG_0xA8, 0x3e); +            dev->interface->write_register(REG_0xA9, 0x06); +            if ((reg->get8(0x05) & REG_0x05_DPIHW) == REG_0x05_DPIHW_600) { +                dev->interface->write_register(REG_0x6C, 0x20); +                dev->interface->write_register(REG_0xA6, 0x44); +            } else { +                dev->interface->write_register(REG_0x6C, 0x60); +                dev->interface->write_register(REG_0xA6, 0x46); +            } + +            if (reg->state.is_xpa_on && reg->state.is_lamp_on) { +                gl843_set_xpa_lamp_power(dev, true); +            } + +            if (reg->state.is_xpa_on) { +                gl843_set_xpa_motor_power(dev, *reg, true); +            } + +            // blinking led +            dev->interface->write_register(REG_0x7E, 0x01); +            break; +        case GpioId::CANON_8400F: +        case GpioId::CANON_8600F: +            if (reg->state.is_xpa_on && reg->state.is_lamp_on) { +                gl843_set_xpa_lamp_power(dev, true); +            } +            if (reg->state.is_xpa_on) { +                gl843_set_xpa_motor_power(dev, *reg, true); +            } +            break; +        case GpioId::PLUSTEK_OPTICFILM_7200I: +        case GpioId::PLUSTEK_OPTICFILM_7300: +        case GpioId::PLUSTEK_OPTICFILM_7500I: { +            if (reg->state.is_xpa_on && reg->state.is_lamp_on) { +                gl843_set_xpa_lamp_power(dev, true); +            } +            break; +        } +        case GpioId::CANON_4400F: +        default: +            break; +    } + +    // clear scan and feed count +    dev->interface->write_register(REG_0x0D, REG_0x0D_CLRLNCNT | REG_0x0D_CLRMCNT); + +    // enable scan and motor +    uint8_t val = dev->interface->read_register(REG_0x01); +    val |= REG_0x01_SCAN; +    dev->interface->write_register(REG_0x01, val); + +    scanner_start_action(*dev, start_motor); + +    if (reg->state.is_motor_on) { +        dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +    } +    if (reg->state.is_xpa_motor_on) { +        dev->advance_head_pos_by_session(ScanHeadId::SECONDARY); +    } +} + + +// Send the stop scan command +void CommandSetGl843::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg, +                               bool check_stop) const +{ +    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop); + +    // post scan gpio +    dev->interface->write_register(0x7e, 0x00); + +    // turn off XPA lamp if needed +    // BUG: the if condition below probably shouldn't be enabled when XPA is off +    if (reg->state.is_xpa_on || reg->state.is_lamp_on) { +        gl843_set_xpa_lamp_power(dev, false); +    } + +    if (!dev->model->is_sheetfed) { +        scanner_stop_action(*dev); +    } +} + +/** @brief Moves the slider to the home (top) position slowly + * */ +void CommandSetGl843::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    scanner_move_back_home(*dev, wait_until_home); +} + +// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi +// from very top of scanner +void CommandSetGl843::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  Genesys_Register_Set local_reg; + +  int pixels = 600; +  int dpi = 300; + +  local_reg = dev->reg; + +  /* sets for a 200 lines * 600 pixels */ +  /* normal scan with no shading */ + +    // FIXME: the current approach of doing search only for one resolution does not work on scanners +    // whith employ different sensors with potentially different settings. +    const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method); + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; // we should give a small offset here - ~60 steps +    session.params.pixels = 600; +    session.params.lines = dev->model->search_lines; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::GREEN; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::IGNORE_LINE_DISTANCE | +                            ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    // send to scanner +    dev->interface->write_registers(local_reg); + +    dev->cmd_set->begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_start_position"); +        end_scan(dev, &local_reg, true); +        dev->reg = local_reg; +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    Image image = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw); + +    scanner_stop_action_no_move(*dev, local_reg); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl843_search_position.pnm", image); +    } + +    dev->cmd_set->end_scan(dev, &local_reg, true); + +  /* update regs to copy ASIC internal state */ +  dev->reg = local_reg; + +    for (auto& sensor_update : +            sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method)) +    { +        sanei_genesys_search_reference_point(dev, sensor_update, image.get_row_ptr(0), 0, dpi, +                                             pixels, dev->model->search_lines); +    } +} + +// sets up register for coarse gain calibration +// todo: check it for scanners using it +void CommandSetGl843::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::SINGLE_LINE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { +        flags |= ScanFlag::USE_XPA; +    } + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel(); +    session.params.lines = 20; +    session.params.depth = 16; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +  DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__, +      sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres); + +    dev->interface->write_registers(regs); +} + +// init registers for shading calibration shading calibration is done at dpihw +void CommandSetGl843::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int move, resolution, dpihw, factor; + +  /* initial calibration reg values */ +  regs = dev->reg; + +  dev->calib_channels = 3; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        dev->calib_lines = dev->model->shading_ta_lines; +    } else { +        dev->calib_lines = dev->model->shading_lines; +    } + +    dpihw = sensor.get_logical_hwdpi(dev->settings.xres); +  factor=sensor.optical_res/dpihw; +  resolution=dpihw; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, dev->calib_channels, +                                                       dev->settings.scan_method); + +    if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY || +         dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) && +        dev->model->model_id == ModelId::CANON_8600F && +        dev->settings.xres == 4800) +    { +        float offset = static_cast<float>(dev->model->x_offset_ta); +        offset /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        offset = static_cast<float>((offset * calib_sensor.optical_res) / MM_PER_INCH); + +        float size = static_cast<float>(dev->model->x_size_ta); +        size /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        size = static_cast<float>((size * calib_sensor.optical_res) / MM_PER_INCH); + +        dev->calib_pixels_offset = static_cast<std::size_t>(offset); +        dev->calib_pixels = static_cast<std::size_t>(size); +    } +    else +    { +        dev->calib_pixels_offset = 0; +        dev->calib_pixels = calib_sensor.sensor_pixels / factor; +    } + +  dev->calib_resolution = resolution; + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::DISABLE_BUFFER_FULL_MOVE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        // note: move_to_ta() function has already been called and the sensor is at the +        // transparency adapter +        move = static_cast<int>(dev->model->y_offset_calib_white_ta - dev->model->y_offset_sensor_to_ta); +        flags |= ScanFlag::USE_XPA; +    } else { +        move = static_cast<int>(dev->model->y_offset_calib_white); +    } + +    move = static_cast<int>((move * resolution) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = dev->calib_pixels_offset; +    session.params.starty = move; +    session.params.pixels = dev->calib_pixels; +    session.params.lines = dev->calib_lines; +    session.params.depth = 16; +    session.params.channels = dev->calib_channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +     // the pixel number may be updated to conform to scanner constraints +    dev->calib_pixels = session.output_pixels; + +    dev->calib_session = session; +    dev->calib_total_bytes_to_read = session.output_total_bytes_raw; + +    dev->interface->write_registers(regs); +} + +/** @brief set up registers for the actual scan + */ +void CommandSetGl843::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  float move; +  int move_dpi; +  float start; + +    debug_dump(DBG_info, dev->settings); + +  move_dpi = dev->motor.base_ydpi; + +    ScanFlag flags = ScanFlag::NONE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        // note: move_to_ta() function has already been called and the sensor is at the +        // transparency adapter +        if (dev->ignore_offsets) { +            move = 0; +        } else { +            move = static_cast<float>(dev->model->y_offset_ta - dev->model->y_offset_sensor_to_ta); +        } +        flags |= ScanFlag::USE_XPA; +    } else { +        if (dev->ignore_offsets) { +            move = 0; +        } else { +            move = static_cast<float>(dev->model->y_offset); +        } +    } + +    move += static_cast<float>(dev->settings.tl_y); +    move = static_cast<float>((move * move_dpi) / MM_PER_INCH); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* start */ +    if (dev->settings.scan_method==ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        start = static_cast<float>(dev->model->x_offset_ta); +    } else { +        start = static_cast<float>(dev->model->x_offset); +    } + +    if (dev->model->model_id == ModelId::CANON_8400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        // FIXME: this is probably just an artifact of a bug elsewhere +        start /= sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres); +    } + +    start = static_cast<float>(start + dev->settings.tl_x); +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->settings.pixels; +    session.params.requested_pixels = dev->settings.requested_pixels; +    session.params.lines = dev->settings.lines; +    session.params.depth = dev->settings.depth; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); +} + +/** + * This function sends gamma tables to ASIC + */ +void CommandSetGl843::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  int size; +  int i; + +  size = 256; + +  /* allocate temporary gamma tables: 16 bits words, 3 channels */ +  std::vector<uint8_t> gamma(size * 2 * 3); + +    std::vector<uint16_t> rgamma = get_gamma_table(dev, sensor, GENESYS_RED); +    std::vector<uint16_t> ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN); +    std::vector<uint16_t> bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE); + +    // copy sensor specific's gamma tables +    for (i = 0; i < size; i++) { +        gamma[i * 2 + size * 0 + 0] = rgamma[i] & 0xff; +        gamma[i * 2 + size * 0 + 1] = (rgamma[i] >> 8) & 0xff; +        gamma[i * 2 + size * 2 + 0] = ggamma[i] & 0xff; +        gamma[i * 2 + size * 2 + 1] = (ggamma[i] >> 8) & 0xff; +        gamma[i * 2 + size * 4 + 0] = bgamma[i] & 0xff; +        gamma[i * 2 + size * 4 + 1] = (bgamma[i] >> 8) & 0xff; +    } + +    dev->interface->write_gamma(0x28, 0x0000, gamma.data(), size * 2 * 3, +                                ScannerInterface::FLAG_SWAP_REGISTERS); +} + +/* this function does the led calibration by scanning one line of the calibration +   area below scanner's top on white strip. + +-needs working coarse/gain +*/ +SensorExposure CommandSetGl843::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int avg[3], avga, avge; +  int turn; +  uint16_t expr, expg, expb; + +    // offset calibration is always done in color mode +    unsigned channels = 3; + +    // take a copy, as we're going to modify exposure +    auto calib_sensor = sanei_genesys_find_sensor(dev, sensor.optical_res, channels, +                                                  dev->settings.scan_method); + +    num_pixels = (calib_sensor.sensor_pixels * calib_sensor.optical_res) / calib_sensor.optical_res; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    ScanSession session; +    session.params.xres = calib_sensor.sensor_pixels; +    session.params.yres = dev->motor.base_ydpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::SINGLE_LINE | +                            ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); + +/* +   we try to get equal bright leds here: + +   loop: +     average per color +     adjust exposure times + */ + +  expr = calib_sensor.exposure.red; +  expg = calib_sensor.exposure.green; +  expb = calib_sensor.exposure.blue; + +  turn = 0; + +    bool acceptable = false; +  do +    { + +      calib_sensor.exposure.red = expr; +      calib_sensor.exposure.green = expg; +      calib_sensor.exposure.blue = expb; + +        regs_set_exposure(dev->model->asic_type, regs, calib_sensor.exposure); + +        dev->interface->write_registers(regs); + +      DBG(DBG_info, "%s: starting first line reading\n", __func__); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("led_calibration"); +            move_back_home(dev, true); +            return calib_sensor.exposure; +        } + +        auto image = read_unshuffled_image_from_scanner(dev, session, +                                                        session.output_total_bytes_raw); +        scanner_stop_action_no_move(*dev, regs); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[30]; +            std::snprintf(fn, 30, "gl843_led_%02d.pnm", turn); +            sanei_genesys_write_pnm_file(fn, image); +	} + +        acceptable = true; + +        for (unsigned ch = 0; ch < channels; ch++) { +            avg[ch] = 0; +            for (std::size_t x = 0; x < image.get_width(); x++) { +                avg[ch] += image.get_raw_channel(x, 0, ch); +            } +            avg[ch] /= image.get_width(); +        } + +      DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +        acceptable = true; + +      if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 || +	  avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 || +	  avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95) +        acceptable = false; + +      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 the resulting exposures below this value. +  too long exposure drives the ccd into saturation. +  we may fix this by relying on the fact that +  we get a striped scan without shading, by means of +  statistical calculation +*/ +	  avge = (expr + expg + expb) / 3; + +	  /* don't overflow max exposure */ +	  if (avge > 3000) +	    { +	      expr = (expr * 2000) / avge; +	      expg = (expg * 2000) / avge; +	      expb = (expb * 2000) / avge; +	    } +	  if (avge < 50) +	    { +	      expr = (expr * 50) / avge; +	      expg = (expg * 50) / avge; +	      expb = (expb * 50) / avge; +	    } + +	} +        scanner_stop_action(*dev); + +      turn++; + +    } +  while (!acceptable && turn < 100); + +  DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, expr, expg, expb); + +    move_back_home(dev, true); + +    return calib_sensor.exposure; +} + + + +/** + * average dark pixels of a 8 bits scan of a given channel + */ +static int dark_average_channel(const Image& image, unsigned black, unsigned channel) +{ +    auto channels = get_pixel_channels(image.get_format()); + +    unsigned avg[3]; + +    // computes average values on black margin +    for (unsigned ch = 0; ch < channels; ch++) { +        avg[ch] = 0; +        unsigned count = 0; +        // FIXME: start with the second line because the black pixels often have noise on the first +        // line; the cause is probably incorrectly cleaned up previous scan +        for (std::size_t y = 1; y < image.get_height(); y++) { +            for (unsigned j = 0; j < black; j++) { +                avg[ch] += image.get_raw_channel(j, y, ch); +                count++; +            } +        } +        if (count > 0) { +            avg[ch] /= count; +        } +        DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, ch, avg[ch]); +    } +    DBG(DBG_info, "%s: average = %d\n", __func__, avg[channel]); +    return avg[channel]; +} + +/** @brief calibrate AFE offset + * Iterate doing scans at target dpi until AFE offset if correct. One + * color line is scanned at a time. Scanning head doesn't move. + * @param dev device to calibrate + */ +void CommandSetGl843::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    if (dev->frontend.layout.type != FrontendType::WOLFSON) +        return; + +    unsigned channels; +    int pass, resolution, lines; +  int topavg[3], bottomavg[3], avg[3]; +  int top[3], bottom[3], black_pixels, pixels, factor, dpihw; + +  /* offset calibration is always done in color mode */ +  channels = 3; +  lines = 8; + +    // compute divider factor to compute final pixels number +    dpihw = sensor.get_logical_hwdpi(dev->settings.xres); +  factor = sensor.optical_res / dpihw; +  resolution = dpihw; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                       dev->settings.scan_method); + +  int target_pixels = calib_sensor.sensor_pixels / factor; +  int start_pixel = 0; +  black_pixels = calib_sensor.black_pixels / factor; + +    if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY || +         dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) && +        dev->model->model_id == ModelId::CANON_8600F && +        dev->settings.xres == 4800) +    { +        start_pixel = static_cast<int>(dev->model->x_offset_ta); +        start_pixel /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        start_pixel = static_cast<int>((start_pixel * calib_sensor.optical_res) / MM_PER_INCH); + +        target_pixels = static_cast<int>(dev->model->x_size_ta); +        target_pixels /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        target_pixels = static_cast<int>((target_pixels * calib_sensor.optical_res) / MM_PER_INCH); +    } + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::SINGLE_LINE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        flags |= ScanFlag::USE_XPA; +    } + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = start_pixel; +    session.params.starty = 0; +    session.params.pixels = target_pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = flags; +    compute_session(dev, session, calib_sensor); +    pixels = session.output_pixels; + +    DBG(DBG_io, "%s: dpihw       =%d\n", __func__, dpihw); +    DBG(DBG_io, "%s: factor      =%d\n", __func__, factor); +    DBG(DBG_io, "%s: resolution  =%d\n", __func__, resolution); +    DBG(DBG_io, "%s: pixels      =%d\n", __func__, pixels); +    DBG(DBG_io, "%s: black_pixels=%d\n", __func__, black_pixels); +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +    // init gain and offset +    for (unsigned ch = 0; ch < 3; ch++) +    { +        bottom[ch] = 10; +        dev->frontend.set_offset(ch, bottom[ch]); +        dev->frontend.set_gain(ch, 0); +    } +    dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +    // scan with bottom AFE settings +    dev->interface->write_registers(regs); +    DBG(DBG_info, "%s: starting first line reading\n", __func__); + +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("offset_calibration"); +        scanner_stop_action_no_move(*dev, regs); +        return; +    } + +    auto first_line = read_unshuffled_image_from_scanner(dev, session, +                                                         session.output_total_bytes_raw); +    scanner_stop_action_no_move(*dev, regs); + +  if (DBG_LEVEL >= DBG_data) +    { +      char fn[40]; +        std::snprintf(fn, 40, "gl843_bottom_offset_%03d_%03d_%03d.pnm", +                      bottom[0], bottom[1], bottom[2]); +        sanei_genesys_write_pnm_file(fn, first_line); +    } + +    for (unsigned ch = 0; ch < 3; ch++) { +        bottomavg[ch] = dark_average_channel(first_line, black_pixels, ch); +        DBG(DBG_io2, "%s: bottom avg %d=%d\n", __func__, ch, bottomavg[ch]); +    } + +    // now top value +    for (unsigned ch = 0; ch < 3; ch++) { +        top[ch] = 255; +        dev->frontend.set_offset(ch, top[ch]); +    } +    dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +    // scan with top AFE values +    dev->interface->write_registers(regs); +    DBG(DBG_info, "%s: starting second line reading\n", __func__); + +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); +    auto second_line = read_unshuffled_image_from_scanner(dev, session, +                                                          session.output_total_bytes_raw); +    scanner_stop_action_no_move(*dev, regs); + +    for (unsigned ch = 0; ch < 3; ch++){ +        topavg[ch] = dark_average_channel(second_line, black_pixels, ch); +        DBG(DBG_io2, "%s: top avg %d=%d\n", __func__, ch, topavg[ch]); +    } + +  pass = 0; + +  std::vector<uint8_t> debug_image; +  size_t debug_image_lines = 0; +  std::string debug_image_info; + +  /* loop until acceptable level */ +  while ((pass < 32) +	 && ((top[0] - bottom[0] > 1) +	     || (top[1] - bottom[1] > 1) || (top[2] - bottom[2] > 1))) +    { +      pass++; + +        // settings for new scan +        for (unsigned ch = 0; ch < 3; ch++) { +            if (top[ch] - bottom[ch] > 1) { +                dev->frontend.set_offset(ch, (top[ch] + bottom[ch]) / 2); +            } +        } +        dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +        // scan with no move +        dev->interface->write_registers(regs); +      DBG(DBG_info, "%s: starting second line reading\n", __func__); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); +        second_line = read_unshuffled_image_from_scanner(dev, session, +                                                         session.output_total_bytes_raw); +        scanner_stop_action_no_move(*dev, regs); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char title[100]; +          std::snprintf(title, 100, "lines: %d pixels_per_line: %d offsets[0..2]: %d %d %d\n", +                        lines, pixels, +                        dev->frontend.get_offset(0), +                        dev->frontend.get_offset(1), +                        dev->frontend.get_offset(2)); +          debug_image_info += title; +          std::copy(second_line.get_row_ptr(0), +                    second_line.get_row_ptr(0) + second_line.get_row_bytes() * second_line.get_height(), +                    std::back_inserter(debug_image)); +          debug_image_lines += lines; +	} + +        for (unsigned ch = 0; ch < 3; ch++) { +            avg[ch] = dark_average_channel(second_line, black_pixels, ch); +            DBG(DBG_info, "%s: avg[%d]=%d offset=%d\n", __func__, ch, avg[ch], +                dev->frontend.get_offset(ch)); +        } + +        // compute new boundaries +        for (unsigned ch = 0; ch < 3; ch++) { +            if (topavg[ch] >= avg[ch]) { +                topavg[ch] = avg[ch]; +                top[ch] = dev->frontend.get_offset(ch); +            } else { +                bottomavg[ch] = avg[ch]; +                bottom[ch] = dev->frontend.get_offset(ch); +            } +        } +    } + +  if (DBG_LEVEL >= DBG_data) +    { +      sanei_genesys_write_file("gl843_offset_all_desc.txt", +                               reinterpret_cast<const std::uint8_t*>(debug_image_info.data()), +                               debug_image_info.size()); +      sanei_genesys_write_pnm_file("gl843_offset_all.pnm", +                                   debug_image.data(), session.params.depth, channels, pixels, +                                   debug_image_lines); +    } + +  DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__, +      dev->frontend.get_offset(0), +      dev->frontend.get_offset(1), +      dev->frontend.get_offset(2)); +} + + +/* alternative coarse gain calibration +   this on uses the settings from offset_calibration and +   uses only one scanline + */ +/* +  with offset and coarse calibration we only want to get our input range into +  a reasonable shape. the fine calibration of the upper and lower bounds will +  be done with shading. + */ +void CommandSetGl843::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER_ARGS(dbg, "dpi = %d", dpi); +    int factor, dpihw; +  float coeff; +    int lines; +  int resolution; + +    if (dev->frontend.layout.type != FrontendType::WOLFSON) +        return; + +    dpihw = sensor.get_logical_hwdpi(dpi); +  factor=sensor.optical_res/dpihw; + +    // coarse gain calibration is always done in color mode +    unsigned channels = 3; + +  /* follow CKSEL */ +    if (dev->model->sensor_id == SensorId::CCD_KVSS080) { +      if(dev->settings.xres<sensor.optical_res) +        { +            coeff = 0.9f; +        } +      else +        { +          coeff=1.0; +        } +    } +  else +    { +      coeff=1.0; +    } +  resolution=dpihw; +  lines=10; +  int target_pixels = sensor.sensor_pixels / factor; + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::SINGLE_LINE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        flags |= ScanFlag::USE_XPA; +    } + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         dev->settings.scan_method); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = target_pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, calib_sensor); +    std::size_t pixels = session.output_pixels; + +    try { +        init_regs_for_scan_session(dev, calib_sensor, ®s, session); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); +        throw; +    } + +    sanei_genesys_set_motor_power(regs, false); + +    dev->interface->write_registers(regs); + +    dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("coarse_gain_calibration"); +        scanner_stop_action(*dev); +        move_back_home(dev, true); +        return; +    } + +    auto line = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw); +    scanner_stop_action_no_move(*dev, regs); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl843_gain.pnm", line); +    } + +    // average value on each channel +    for (unsigned ch = 0; ch < channels; ch++) { + +        std::vector<uint16_t> values; +        // FIXME: start from the second line because the first line often has artifacts. Probably +        // caused by unclean cleanup of previous scan +        for (std::size_t x = pixels / 4; x < (pixels * 3 / 4); x++) { +            values.push_back(line.get_raw_channel(x, 1, ch)); +        } + +        // pick target value at 95th percentile of all values. There may be a lot of black values +        // in transparency scans for example +        std::sort(values.begin(), values.end()); +        uint16_t curr_output = values[unsigned((values.size() - 1) * 0.95)]; +        float target_value = calib_sensor.gain_white_ref * coeff; + +        int code = compute_frontend_gain(curr_output, target_value, dev->frontend.layout.type); +      dev->frontend.set_gain(ch, code); + +        DBG(DBG_proc, "%s: channel %d, max=%d, target=%d, setting:%d\n", __func__, ch, curr_output, +            static_cast<int>(target_value), code); +    } + +    if (dev->model->is_cis) { +        uint8_t gain0 = dev->frontend.get_gain(0); +        if (gain0 > dev->frontend.get_gain(1)) { +            gain0 = dev->frontend.get_gain(1); +        } +        if (gain0 > dev->frontend.get_gain(2)) { +            gain0 = dev->frontend.get_gain(2); +        } +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); +    } + +    if (channels == 1) { +        dev->frontend.set_gain(0, dev->frontend.get_gain(1)); +        dev->frontend.set_gain(2, dev->frontend.get_gain(1)); +    } + +    scanner_stop_action(*dev); + +    move_back_home(dev, true); +} + +// wait for lamp warmup by scanning the same line until difference +// between 2 scans is below a threshold +void CommandSetGl843::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* reg, int* channels, +                                           int* total_size) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int dpihw; +  int resolution; +  int factor; + +  /* setup scan */ +  *channels=3; +  resolution=600; +    dpihw = sensor.get_logical_hwdpi(resolution); +  resolution=dpihw; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, *channels, +                                                       dev->settings.scan_method); +  factor = calib_sensor.optical_res/dpihw; +  num_pixels = calib_sensor.sensor_pixels/(factor*2); +  *total_size = num_pixels * 3 * 1; + +  *reg = dev->reg; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = num_pixels/2; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 8; +    session.params.channels = *channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::SINGLE_LINE | +                            ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, reg, session); + +  sanei_genesys_set_motor_power(*reg, false); +    dev->interface->write_registers(*reg); +} + +/** + * set up GPIO/GPOE for idle state +WRITE GPIO[17-21]= GPIO19 +WRITE GPOE[17-21]= GPOE21 GPOE20 GPOE19 GPOE18 +genesys_write_register(0xa8,0x3e) +GPIO(0xa8)=0x3e + */ +static void gl843_init_gpio(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    apply_registers_ordered(dev->gpo.regs, { 0x6e, 0x6f }, [&](const GenesysRegisterSetting& reg) +    { +        dev->interface->write_register(reg.address, reg.value); +    }); +} + + +/* * + * initialize ASIC from power on condition + */ +void CommandSetGl843::asic_boot(Genesys_Device* dev, bool cold) const +{ +    DBG_HELPER(dbg); +  uint8_t val; + +    if (cold) { +        dev->interface->write_register(0x0e, 0x01); +        dev->interface->write_register(0x0e, 0x00); +    } + +  if(dev->usb_mode == 1) +    { +      val = 0x14; +    } +  else +    { +      val = 0x11; +    } +    dev->interface->write_0x8c(0x0f, val); + +    // test CHKVER +    val = dev->interface->read_register(REG_0x40); +    if (val & REG_0x40_CHKVER) { +        val = dev->interface->read_register(0x00); +        DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val); +    } + +  /* Set default values for registers */ +  gl843_init_registers (dev); + +    if (dev->model->model_id == ModelId::CANON_8600F) { +        // turns on vref control for maximum current of the motor driver +        dev->interface->write_register(REG_0x6B, 0x72); +    } else { +        dev->interface->write_register(REG_0x6B, 0x02); +    } + +    // Write initial registers +    dev->interface->write_registers(dev->reg); + +  // Enable DRAM by setting a rising edge on bit 3 of reg 0x0b +    val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL; +    val = (val | REG_0x0B_ENBDRAM); +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->interface->write_0x8c(0x1e, 0x01); +        dev->interface->write_0x8c(0x10, 0xb4); +        dev->interface->write_0x8c(0x0f, 0x02); +    } +    else if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->interface->write_0x8c(0x10, 0xc8); +    } else if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +               dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->interface->write_0x8c(0x10, 0xd4); +    } else { +        dev->interface->write_0x8c(0x10, 0xb4); +    } + +  /* CLKSET */ +    int clock_freq = REG_0x0B_48MHZ; +    switch (dev->model->model_id) { +        case ModelId::CANON_8600F: +            clock_freq = REG_0x0B_60MHZ; +            break; +        case ModelId::PLUSTEK_OPTICFILM_7200I: +            clock_freq = REG_0x0B_30MHZ; +            break; +        case ModelId::PLUSTEK_OPTICFILM_7300: +        case ModelId::PLUSTEK_OPTICFILM_7500I: +            clock_freq = REG_0x0B_40MHZ; +            break; +        default: +            break; +    } + +    val = (dev->reg.find_reg(0x0b).value & ~REG_0x0B_CLKSET) | clock_freq; + +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +  /* prevent further writings by bulk write register */ +  dev->reg.remove_reg(0x0b); + +    if (dev->model->model_id != ModelId::CANON_8600F) { +      // set up end access +      // FIXME: this is overwritten in gl843_init_gpio +        dev->interface->write_register(REG_0xA7, 0x04); +        dev->interface->write_register(REG_0xA9, 0x00); +    } + +    // set RAM read address +    dev->interface->write_register(REG_0x29, 0x00); +    dev->interface->write_register(REG_0x2A, 0x00); +    dev->interface->write_register(REG_0x2B, 0x00); + +    // setup gpio +    gl843_init_gpio(dev); + +    scanner_move(*dev, dev->model->default_method, 300, Direction::FORWARD); +    dev->interface->sleep_ms(100); +} + +/* * + * initialize backend and ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + */ +void CommandSetGl843::init(Genesys_Device* dev) const +{ +  DBG_INIT (); +    DBG_HELPER(dbg); + +    sanei_genesys_asic_init(dev, 0); +} + +void CommandSetGl843::update_hardware_sensors(Genesys_Scanner* s) const +{ +    DBG_HELPER(dbg); +  /* do what is needed to get a new set of events, but try to not lose +     any of them. +   */ + +    uint8_t val = s->dev->interface->read_register(REG_0x6D); + +  switch (s->dev->model->gpio_id) +    { +        case GpioId::KVSS080: +            s->buttons[BUTTON_SCAN_SW].write((val & 0x04) == 0); +            break; +        case GpioId::G4050: +            s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0); +            s->buttons[BUTTON_FILE_SW].write((val & 0x02) == 0); +            s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0); +            s->buttons[BUTTON_COPY_SW].write((val & 0x08) == 0); +            break; +        case GpioId::CANON_4400F: +        case GpioId::CANON_8400F: +        default: +            break; +    } +} + +/** @brief move sensor to transparency adaptor + * Move sensor to the calibration of the transparency adapator (XPA). + * @param dev device to use + */ +void CommandSetGl843::move_to_ta(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); + +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->model->default_method); +    float resolution = resolution_settings.get_min_resolution_y(); + +    unsigned multiplier = 16; +    if (dev->model->model_id == ModelId::CANON_8400F) { +        multiplier = 4; +    } +    unsigned feed = static_cast<unsigned>(multiplier * (dev->model->y_offset_sensor_to_ta * resolution) / +                                          MM_PER_INCH); +    scanner_move(*dev, dev->model->default_method, feed, Direction::FORWARD); +} + + +/** @brief search for a full width black or white strip. + * This function searches for a black or white stripe across the scanning area. + * When searching backward, the searched area must completely be of the desired + * color since this area will be used for calibration which scans forward. + * @param dev scanner device + * @param forward true if searching forward, false if searching backward + * @param black true if searching for a black strip, false for a white strip + */ +void CommandSetGl843::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   bool forward, bool black) const +{ +    DBG_HELPER_ARGS(dbg, "%s %s",  black ? "black" : "white", forward ? "forward" : "reverse"); +  unsigned int pixels, lines, channels; +  Genesys_Register_Set local_reg; +    int dpi; +  unsigned int pass, count, found, x, y; + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); +    scanner_stop_action(*dev); + +  /* set up for a gray scan at lowest dpi */ +  dpi = sanei_genesys_get_lowest_dpi(dev); +  channels = 1; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, dpi, channels, +                                                       dev->settings.scan_method); + +  /* 10 MM */ +  /* lines = (10 * dpi) / MM_PER_INCH; */ +  /* shading calibation is done with dev->motor.base_ydpi */ +  lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi; +  pixels = (calib_sensor.sensor_pixels * dpi) / calib_sensor.optical_res; + +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +  local_reg = dev->reg; + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_SHADING; +    if (!forward) { +        session.params.flags = ScanFlag::REVERSE; +    } +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, &local_reg, session); + +    dev->interface->write_registers(local_reg); + +    dev->cmd_set->begin_scan(dev, calib_sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_strip"); +        scanner_stop_action(*dev); +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    auto data = read_unshuffled_image_from_scanner(dev, session, +                                                   session.output_total_bytes_raw); + +    scanner_stop_action(*dev); + +  pass = 0; +  if (DBG_LEVEL >= DBG_data) +    { +      char fn[40]; +        std::snprintf(fn, 40, "gl843_search_strip_%s_%s%02d.pnm", +                      black ? "black" : "white", forward ? "fwd" : "bwd", pass); +        sanei_genesys_write_pnm_file(fn, data); +    } + +  /* loop until strip is found or maximum pass number done */ +  found = 0; +  while (pass < 20 && !found) +    { +        dev->interface->write_registers(local_reg); + +        // now start scan +        dev->cmd_set->begin_scan(dev, calib_sensor, &local_reg, true); + +        wait_until_buffer_non_empty(dev); + +        // now we're on target, we can read data +        data = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw); + +        scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[40]; +            std::snprintf(fn, 40, "gl843_search_strip_%s_%s%02d.pnm", +                          black ? "black" : "white", forward ? "fwd" : "bwd", pass); +            sanei_genesys_write_pnm_file(fn, data); +	} + +      /* 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 < lines && !found; y++) +	    { +	      count = 0; +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +                    if (black && data.get_raw_channel(x, y, 0) > 90) { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +                    if (!black && data.get_raw_channel(x, y, 0) < 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) / pixels < 3) +		{ +		  found = 1; +		  DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__, +		      pass, y); +		} +	      else +		{ +		  DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		      (100 * count) / pixels); +		} +	    } +	} +      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 < lines; y++) +	    { +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +                    // when searching for black, detect white pixels +                    if (black && data.get_raw_channel(x, y, 0) > 90) { +		      count++; +		    } +                    // when searching for white, detect black pixels +                    if (!black && data.get_raw_channel(x, y, 0) < 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) / (pixels * lines) < 3) +	    { +	      found = 1; +	      DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); +	    } +	  else +	    { +	      DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		  (100 * count) / pixels); +	    } +	} +      pass++; +    } +  if (found) +    { +      DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white"); +    } +  else +    { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white"); +    } +} + +/** + * Send shading calibration data. The buffer is considered to always hold values + * for all the channels. + */ +void CommandSetGl843::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        uint8_t* data, int size) const +{ +    DBG_HELPER(dbg); +  uint32_t final_size, length, i; +  uint8_t *buffer; +  int count,offset; +  GenesysRegister *r; +    uint16_t strpixel, endpixel, startx; + +  offset=0; +  length=size; +    r = sanei_genesys_get_address(&dev->reg, REG_0x01); +    if (r->value & REG_0x01_SHDAREA) +    { +      /* recompute STRPIXEL used shading calibration so we can +       * compute offset within data for SHDAREA case */ + +        // FIXME: the following is likely incorrect +        // start coordinate in optical dpi coordinates +        startx = (sensor.dummy_pixel / sensor.ccd_pixels_per_system_pixel()) / dev->session.hwdpi_divisor; +        startx *= dev->session.pixel_count_multiplier; + +      /* current scan coordinates */ +        strpixel = dev->session.pixel_startx; +        endpixel = dev->session.pixel_endx; + +        if (dev->model->model_id == ModelId::CANON_4400F || +            dev->model->model_id == ModelId::CANON_8600F) +        { +            int half_ccd_factor = dev->session.optical_resolution / +                                  sensor.get_logical_hwdpi(dev->session.output_resolution); +            strpixel /= half_ccd_factor * sensor.ccd_pixels_per_system_pixel(); +            endpixel /= half_ccd_factor * sensor.ccd_pixels_per_system_pixel(); +        } + +      /* 16 bit words, 2 words per color, 3 color channels */ +      offset=(strpixel-startx)*2*2*3; +      length=(endpixel-strpixel)*2*2*3; +      DBG(DBG_info, "%s: STRPIXEL=%d, ENDPIXEL=%d, startx=%d\n", __func__, strpixel, endpixel, +          startx); +    } + +    dev->interface->record_key_value("shading_offset", std::to_string(offset)); +    dev->interface->record_key_value("shading_length", std::to_string(length)); + +  /* compute and allocate size for final data */ +  final_size = ((length+251) / 252) * 256; +  DBG(DBG_io, "%s: final shading size=%04x (length=%d)\n", __func__, final_size, length); +  std::vector<uint8_t> final_data(final_size, 0); + +  /* copy regular shading data to the expected layout */ +  buffer = final_data.data(); +  count = 0; + +  /* loop over calibration data */ +  for (i = 0; i < length; i++) +    { +      buffer[count] = data[offset+i]; +      count++; +      if ((count % (256*2)) == (252*2)) +	{ +	  count += 4*2; +	} +    } + +    dev->interface->write_buffer(0x3c, 0, final_data.data(), count, +                                 ScannerInterface::FLAG_SMALL_ADDRESS); +} + +bool CommandSetGl843::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    (void) dev; +    return true; +} + +void CommandSetGl843::wait_for_motor_stop(Genesys_Device* dev) const +{ +    (void) dev; +} + +std::unique_ptr<CommandSet> create_gl843_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl843{}); +} + +} // namespace gl843 +} // namespace genesys | 
