/* 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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
