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 |