/* sane - Scanner Access Now Easy.

   Copyright (C) 2003 Oliver Rauch
   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
   Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de>
   Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
   Copyright (C) 2005 Philipp Schmid <philipp8288@web.de>
   Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
   Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
   Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de>
                 for Plustek Opticbook 3600 support


   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, see <https://www.gnu.org/licenses/>.
*/

#define DEBUG_DECLARE_ONLY

#include "gl841.h"
#include "gl841_registers.h"
#include "test_settings.h"

#include <vector>

namespace genesys {
namespace gl841 {


static int gl841_exposure_time(Genesys_Device *dev, const Genesys_Sensor& sensor,
                               const MotorProfile& profile,
                               float slope_dpi,
                               int start,
                               int used_pixels);

/*
 * Set all registers to default values
 * (function called only once at the beginning)
 */
static void
gl841_init_registers (Genesys_Device * dev)
{
    DBG_HELPER(dbg);

    dev->reg.init_reg(0x01, 0x20);
    if (dev->model->is_cis) {
        dev->reg.find_reg(0x01).value |= REG_0x01_CISSET;
    } else {
        dev->reg.find_reg(0x01).value &= ~REG_0x01_CISSET;
    }
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x01, 0x82);
    }

    dev->reg.init_reg(0x02, 0x38);
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x02, 0x10);
    }

    dev->reg.init_reg(0x03, 0x5f);
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x03, 0x50);
    }

    dev->reg.init_reg(0x04, 0x10);
    if (dev->model->model_id == ModelId::PLUSTEK_OPTICPRO_3600) {
        dev->reg.init_reg(0x04, 0x22);
    } else if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x04, 0x02);
    }

    const auto& sensor = sanei_genesys_find_sensor_any(dev);

    dev->reg.init_reg(0x05, 0x00); // disable gamma, 24 clocks/pixel

    sanei_genesys_set_dpihw(dev->reg, sensor.register_dpihw);

    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x05, 0x4c);
    }

    dev->reg.init_reg(0x06, 0x18);
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x06, 0x38);
    }
    if (dev->model->model_id == ModelId::VISIONEER_STROBE_XP300 ||
        dev->model->model_id == ModelId::SYSCAN_DOCKETPORT_485 ||
        dev->model->model_id == ModelId::DCT_DOCKETPORT_487 ||
        dev->model->model_id == ModelId::SYSCAN_DOCKETPORT_685 ||
        dev->model->model_id == ModelId::PLUSTEK_OPTICPRO_3600)
    {
        dev->reg.init_reg(0x06, 0xb8);
    }

    dev->reg.init_reg(0x07, 0x00);
    dev->reg.init_reg(0x08, 0x00);

    dev->reg.init_reg(0x09, 0x10);
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x09, 0x11);
    }
    if (dev->model->model_id == ModelId::VISIONEER_STROBE_XP300 ||
        dev->model->model_id == ModelId::SYSCAN_DOCKETPORT_485 ||
        dev->model->model_id == ModelId::DCT_DOCKETPORT_487 ||
        dev->model->model_id == ModelId::SYSCAN_DOCKETPORT_685 ||
        dev->model->model_id == ModelId::PLUSTEK_OPTICPRO_3600)
    {
        dev->reg.init_reg(0x09, 0x00);
    }
    dev->reg.init_reg(0x0a, 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
    dev->reg.init_reg(0x16, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x17, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x18, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x19, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x1a, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x1c, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x1d, 0x01); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x1e, 0xf0);
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x1e, 0x10);
    }
    dev->reg.init_reg(0x1f, 0x01);
    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x1f, 0x04);
    }
    dev->reg.init_reg(0x20, 0x20);
    dev->reg.init_reg(0x21, 0x01);
    dev->reg.init_reg(0x22, 0x01);
    dev->reg.init_reg(0x23, 0x01);
    dev->reg.init_reg(0x24, 0x01);
    dev->reg.init_reg(0x25, 0x00);
    dev->reg.init_reg(0x26, 0x00);
    dev->reg.init_reg(0x27, 0x00);
    dev->reg.init_reg(0x29, 0xff);

    dev->reg.init_reg(0x2c, 0x02); // DPISET: overwritten during scanner setup
    dev->reg.init_reg(0x2d, 0x58); // DPISET: overwritten during scanner setup
    dev->reg.init_reg(0x2e, 0x80);
    dev->reg.init_reg(0x2f, 0x80);

    dev->reg.init_reg(0x30, 0x00); // STRPIXEL: overwritten during scanner setup
    dev->reg.init_reg(0x31, 0x00); // STRPIXEL: overwritten during scanner setup
    dev->reg.init_reg(0x32, 0x00); // ENDPIXEL: overwritten during scanner setup
    dev->reg.init_reg(0x33, 0x00); // ENDPIXEL: overwritten during scanner setup
    dev->reg.init_reg(0x34, 0x00); // DUMMY: overwritten during scanner setup
    dev->reg.init_reg(0x35, 0x00); // MAXWD: overwritten during scanner setup
    dev->reg.init_reg(0x36, 0x00); // MAXWD: overwritten during scanner setup
    dev->reg.init_reg(0x37, 0x00); // MAXWD: overwritten during scanner setup
    dev->reg.init_reg(0x38, 0x4f); // LPERIOD: overwritten during scanner setup
    dev->reg.init_reg(0x39, 0xc1); // LPERIOD: overwritten during scanner setup

    dev->reg.init_reg(0x3d, 0x00);
    dev->reg.init_reg(0x3e, 0x00);
    dev->reg.init_reg(0x3f, 0x00);

    dev->reg.init_reg(0x52, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x53, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x54, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x55, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x56, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x57, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x58, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x59, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x5a, 0x00);  // SENSOR_DEF, overwritten in scanner_setup_sensor() below

    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x5d, 0x20);
        dev->reg.init_reg(0x5e, 0x41);
        dev->reg.init_reg(0x5f, 0x40);
        dev->reg.init_reg(0x60, 0x00);
        dev->reg.init_reg(0x61, 0x00);
        dev->reg.init_reg(0x62, 0x00);
        dev->reg.init_reg(0x63, 0x00);
        dev->reg.init_reg(0x64, 0x00);
        dev->reg.init_reg(0x65, 0x00);
        dev->reg.init_reg(0x66, 0x00);
        dev->reg.init_reg(0x67, 0x40);
        dev->reg.init_reg(0x68, 0x40);
        dev->reg.init_reg(0x69, 0x20);
        dev->reg.init_reg(0x6a, 0x20);
        dev->reg.init_reg(0x6c, 0x00);
        dev->reg.init_reg(0x6d, 0x00);
        dev->reg.init_reg(0x6e, 0x00);
        dev->reg.init_reg(0x6f, 0x00);
    } else {
        for (unsigned addr = 0x5d; addr <= 0x6f; addr++) {
            dev->reg.init_reg(addr, 0);
        }
        dev->reg.init_reg(0x5e, 0x02);
        if (dev->model->model_id == ModelId::CANON_LIDE_60) {
            dev->reg.init_reg(0x66, 0xff);
        }
    }

    dev->reg.init_reg(0x70, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x71, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x72, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below
    dev->reg.init_reg(0x73, 0x00); // SENSOR_DEF, overwritten in scanner_setup_sensor() below

    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        dev->reg.init_reg(0x74, 0x00);
        dev->reg.init_reg(0x75, 0x01);
        dev->reg.init_reg(0x76, 0xff);
        dev->reg.init_reg(0x77, 0x00);
        dev->reg.init_reg(0x78, 0x0f);
        dev->reg.init_reg(0x79, 0xf0);
        dev->reg.init_reg(0x7a, 0xf0);
        dev->reg.init_reg(0x7b, 0x00);
        dev->reg.init_reg(0x7c, 0x1e);
        dev->reg.init_reg(0x7d, 0x11);
        dev->reg.init_reg(0x7e, 0x00);
        dev->reg.init_reg(0x7f, 0x50);
        dev->reg.init_reg(0x80, 0x00);
        dev->reg.init_reg(0x81, 0x00);
        dev->reg.init_reg(0x82, 0x0f);
        dev->reg.init_reg(0x83, 0x00);
        dev->reg.init_reg(0x84, 0x0e);
        dev->reg.init_reg(0x85, 0x00);
        dev->reg.init_reg(0x86, 0x0d);
        dev->reg.init_reg(0x87, 0x02);
        dev->reg.init_reg(0x88, 0x00);
        dev->reg.init_reg(0x89, 0x00);
    } else {
        for (unsigned addr = 0x74; addr <= 0x87; addr++) {
            dev->reg.init_reg(addr, 0);
        }
    }

    scanner_setup_sensor(*dev, sensor, dev->reg);

    // set up GPIO
    for (const auto& reg : dev->gpo.regs) {
        dev->reg.set8(reg.address, reg.value);
    }

    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) {
        dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO18;
        dev->reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17;
    }

    if (dev->model->gpio_id == GpioId::XP300) {
        dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
    }

    if (dev->model->gpio_id == GpioId::DP685) {
      /* REG_0x6B_GPO18 lights on green led */
        dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17 | REG_0x6B_GPO18;
    }

    if (dev->model->model_id == ModelId::CANON_LIDE_80) {
        // specific scanner settings, clock and gpio first
        dev->interface->write_register(REG_0x6B, 0x0c);
        dev->interface->write_register(0x06, 0x10);
        dev->interface->write_register(REG_0x6E, 0x6d);
        dev->interface->write_register(REG_0x6F, 0x80);
        dev->interface->write_register(REG_0x6B, 0x0e);
        dev->interface->write_register(REG_0x6C, 0x00);
        dev->interface->write_register(REG_0x6D, 0x8f);
        dev->interface->write_register(REG_0x6B, 0x0e);
        dev->interface->write_register(REG_0x6B, 0x0e);
        dev->interface->write_register(REG_0x6B, 0x0a);
        dev->interface->write_register(REG_0x6B, 0x02);
        dev->interface->write_register(REG_0x6B, 0x06);

        dev->interface->write_0x8c(0x10, 0x94);
        dev->interface->write_register(0x09, 0x10);
    }
}

static void gl841_set_lide80_fe(Genesys_Device* dev, std::uint8_t set)
{
    DBG_HELPER(dbg);

    if (set == AFE_INIT) {
        dev->frontend = dev->frontend_initial;

        // BUG: the following code does not make sense. The addresses are different than AFE_SET
        // case
        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
        dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x01));
        dev->interface->write_fe_register(0x06, dev->frontend.regs.get_value(0x02));
    }

  if (set == AFE_SET)
    {
        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
        dev->interface->write_fe_register(0x06, dev->frontend.regs.get_value(0x20));
        dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x28));
    }
}

// Set values of Analog Device type frontend
static void gl841_set_ad_fe(Genesys_Device* dev, std::uint8_t set)
{
    DBG_HELPER(dbg);
  int i;

    if (dev->model->adc_id==AdcId::CANON_LIDE_80) {
        gl841_set_lide80_fe(dev, set);
        return;
    }

    if (set == AFE_INIT) {
      dev->frontend = dev->frontend_initial;

        // write them to analog frontend
        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));

        dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));

        for (i = 0; i < 6; i++) {
            dev->interface->write_fe_register(0x02 + i, 0x00);
        }
    }
  if (set == AFE_SET)
    {
        // write them to analog frontend
        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));

        dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));

        // Write fe 0x02 (red gain)
        dev->interface->write_fe_register(0x02, dev->frontend.get_gain(0));

        // Write fe 0x03 (green gain)
        dev->interface->write_fe_register(0x03, dev->frontend.get_gain(1));

        // Write fe 0x04 (blue gain)
        dev->interface->write_fe_register(0x04, dev->frontend.get_gain(2));

        // Write fe 0x05 (red offset)
        dev->interface->write_fe_register(0x05, dev->frontend.get_offset(0));

        // Write fe 0x06 (green offset)
        dev->interface->write_fe_register(0x06, dev->frontend.get_offset(1));

        // Write fe 0x07 (blue offset)
        dev->interface->write_fe_register(0x07, dev->frontend.get_offset(2));
          }
}

// Set values of analog frontend
void CommandSetGl841::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor,
                             std::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;

  /* Analog Device type frontend */
    std::uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET;

    if (frontend_type == 0x02) {
        gl841_set_ad_fe(dev, set);
        return;
    }

    if (frontend_type != 0x00) {
        throw SaneException("unsupported frontend type %d", frontend_type);
    }

    if (set == AFE_INIT) {
        dev->frontend = dev->frontend_initial;

        // reset only done on init
        dev->interface->write_fe_register(0x04, 0x80);
    }


  if (set == AFE_POWER_SAVE)
    {
        dev->interface->write_fe_register(0x01, 0x02);
        return;
    }

  /* todo :  base this test on cfg reg3 or a CCD family flag to be created */
  /*if (dev->model->ccd_type!=SensorId::CCD_HP2300 && dev->model->ccd_type!=SensorId::CCD_HP2400) */
  {
        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
        dev->interface->write_fe_register(0x02, dev->frontend.regs.get_value(0x02));
  }

    dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
    dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x03));
    dev->interface->write_fe_register(0x06, dev->frontend.reg2[0]);
    dev->interface->write_fe_register(0x08, dev->frontend.reg2[1]);
    dev->interface->write_fe_register(0x09, dev->frontend.reg2[2]);

    for (unsigned i = 0; i < 3; i++) {
        dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i));
        dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i));
        dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i));
    }
}

// @brief turn off motor
static void gl841_init_motor_regs_off(Genesys_Register_Set* reg, unsigned int scan_lines)
{
    DBG_HELPER_ARGS(dbg, "scan_lines=%d", scan_lines);
    unsigned int feedl;

    feedl = 2;

    reg->set8(0x3d, (feedl >> 16) & 0xf);
    reg->set8(0x3e, (feedl >> 8) & 0xff);
    reg->set8(0x3f, feedl & 0xff);
    reg->find_reg(0x5e).value &= ~0xe0;

    reg->set8(0x25, (scan_lines >> 16) & 0xf);
    reg->set8(0x26, (scan_lines >> 8) & 0xff);
    reg->set8(0x27, scan_lines & 0xff);

    reg->set8(0x02, 0x00);

    reg->set8(0x67, 0x3f);
    reg->set8(0x68, 0x3f);

    reg->set8(REG_STEPNO, 1);
    reg->set8(REG_FASTNO, 1);

    reg->set8(0x69, 1);
    reg->set8(0x6a, 1);
    reg->set8(0x5f, 1);
}

/** @brief write motor table frequency
 * Write motor frequency data table.
 * @param dev device to set up motor
 * @param ydpi motor target resolution
 */
static void gl841_write_freq(Genesys_Device* dev, unsigned int ydpi)
{
    DBG_HELPER(dbg);
/**< fast table */
    std::uint8_t tdefault[] = {
        0x18, 0x36, 0x18, 0x36, 0x18, 0x36, 0x18, 0x36, 0x18, 0x36,
        0x18, 0x36, 0x18, 0x36, 0x18, 0x36, 0x18, 0x36, 0x18, 0x36,
        0x18, 0x36, 0x18, 0x36, 0x18, 0x36, 0x18, 0x36, 0x18, 0x36,
        0x18, 0x36, 0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6,
        0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6,
        0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6, 0x18, 0xb6,
        0x18, 0xb6, 0x18, 0xb6, 0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6,
        0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6,
        0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6,
        0x18, 0xf6, 0x18, 0xf6, 0x18, 0xf6, 0x18, 0x76, 0x18, 0x76,
        0x18, 0x76, 0x18, 0x76, 0x18, 0x76, 0x18, 0x76, 0x18, 0x76,
        0x18, 0x76, 0x18, 0x76, 0x18, 0x76, 0x18, 0x76, 0x18, 0x76,
        0x18, 0x76, 0x18, 0x76, 0x18, 0x76, 0x18, 0x76
    };
    std::uint8_t t1200[] = {
        0xc7, 0x31, 0xc7, 0x31, 0xc7, 0x31, 0xc7, 0x31, 0xc7, 0x31,
        0xc7, 0x31, 0xc7, 0x31, 0xc7, 0x31, 0xc0, 0x11, 0xc0, 0x11,
        0xc0, 0x11, 0xc0, 0x11, 0xc0, 0x11, 0xc0, 0x11, 0xc0, 0x11,
        0xc0, 0x11, 0xc7, 0xb1, 0xc7, 0xb1, 0xc7, 0xb1, 0xc7, 0xb1,
        0xc7, 0xb1, 0xc7, 0xb1, 0xc7, 0xb1, 0xc7, 0xb1, 0x07, 0xe0,
        0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0,
        0x07, 0xe0, 0x07, 0xe0, 0xc7, 0xf1, 0xc7, 0xf1, 0xc7, 0xf1,
        0xc7, 0xf1, 0xc7, 0xf1, 0xc7, 0xf1, 0xc7, 0xf1, 0xc7, 0xf1,
        0xc0, 0x51, 0xc0, 0x51, 0xc0, 0x51, 0xc0, 0x51, 0xc0, 0x51,
        0xc0, 0x51, 0xc0, 0x51, 0xc0, 0x51, 0xc7, 0x71, 0xc7, 0x71,
        0xc7, 0x71, 0xc7, 0x71, 0xc7, 0x71, 0xc7, 0x71, 0xc7, 0x71,
        0xc7, 0x71, 0x07, 0x20, 0x07, 0x20, 0x07, 0x20, 0x07, 0x20,
        0x07, 0x20, 0x07, 0x20, 0x07, 0x20, 0x07, 0x20
    };
    std::uint8_t t300[] = {
        0x08, 0x32, 0x08, 0x32, 0x08, 0x32, 0x08, 0x32, 0x08, 0x32,
        0x08, 0x32, 0x08, 0x32, 0x08, 0x32, 0x00, 0x13, 0x00, 0x13,
        0x00, 0x13, 0x00, 0x13, 0x00, 0x13, 0x00, 0x13, 0x00, 0x13,
        0x00, 0x13, 0x08, 0xb2, 0x08, 0xb2, 0x08, 0xb2, 0x08, 0xb2,
        0x08, 0xb2, 0x08, 0xb2, 0x08, 0xb2, 0x08, 0xb2, 0x0c, 0xa0,
        0x0c, 0xa0, 0x0c, 0xa0, 0x0c, 0xa0, 0x0c, 0xa0, 0x0c, 0xa0,
        0x0c, 0xa0, 0x0c, 0xa0, 0x08, 0xf2, 0x08, 0xf2, 0x08, 0xf2,
        0x08, 0xf2, 0x08, 0xf2, 0x08, 0xf2, 0x08, 0xf2, 0x08, 0xf2,
        0x00, 0xd3, 0x00, 0xd3, 0x00, 0xd3, 0x00, 0xd3, 0x00, 0xd3,
        0x00, 0xd3, 0x00, 0xd3, 0x00, 0xd3, 0x08, 0x72, 0x08, 0x72,
        0x08, 0x72, 0x08, 0x72, 0x08, 0x72, 0x08, 0x72, 0x08, 0x72,
        0x08, 0x72, 0x0c, 0x60, 0x0c, 0x60, 0x0c, 0x60, 0x0c, 0x60,
        0x0c, 0x60, 0x0c, 0x60, 0x0c, 0x60, 0x0c, 0x60
    };
    std::uint8_t t150[] = {
        0x0c, 0x33, 0xcf, 0x33, 0xcf, 0x33, 0xcf, 0x33, 0xcf, 0x33,
        0xcf, 0x33, 0xcf, 0x33, 0xcf, 0x33, 0x40, 0x14, 0x80, 0x15,
        0x80, 0x15, 0x80, 0x15, 0x80, 0x15, 0x80, 0x15, 0x80, 0x15,
        0x80, 0x15, 0x0c, 0xb3, 0xcf, 0xb3, 0xcf, 0xb3, 0xcf, 0xb3,
        0xcf, 0xb3, 0xcf, 0xb3, 0xcf, 0xb3, 0xcf, 0xb3, 0x11, 0xa0,
        0x16, 0xa0, 0x16, 0xa0, 0x16, 0xa0, 0x16, 0xa0, 0x16, 0xa0,
        0x16, 0xa0, 0x16, 0xa0, 0x0c, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3,
        0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3,
        0x40, 0xd4, 0x80, 0xd5, 0x80, 0xd5, 0x80, 0xd5, 0x80, 0xd5,
        0x80, 0xd5, 0x80, 0xd5, 0x80, 0xd5, 0x0c, 0x73, 0xcf, 0x73,
        0xcf, 0x73, 0xcf, 0x73, 0xcf, 0x73, 0xcf, 0x73, 0xcf, 0x73,
        0xcf, 0x73, 0x11, 0x60, 0x16, 0x60, 0x16, 0x60, 0x16, 0x60,
        0x16, 0x60, 0x16, 0x60, 0x16, 0x60, 0x16, 0x60
    };

    std::uint8_t *table;

    if(dev->model->motor_id == MotorId::CANON_LIDE_80) {
      switch(ydpi)
        {
          case 3600:
          case 1200:
            table=t1200;
            break;
          case 900:
          case 300:
            table=t300;
            break;
          case 450:
          case 150:
            table=t150;
            break;
          default:
            table=tdefault;
        }
        dev->interface->write_register(0x66, 0x00);
        dev->interface->write_gamma(0x28, 0xc000, table, 128);
        dev->interface->write_register(0x5b, 0x00);
        dev->interface->write_register(0x5c, 0x00);
    }
}

static void gl841_init_motor_regs_feed(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                       Genesys_Register_Set* reg, unsigned int feed_steps,/*1/base_ydpi*/
                                       ScanFlag flags)
{
    DBG_HELPER_ARGS(dbg, "feed_steps=%d, flags=%x", feed_steps, static_cast<unsigned>(flags));
    unsigned step_multiplier = 2;
    unsigned int feedl;
/*number of scan lines to add in a scan_lines line*/

    {
        std::vector<std::uint16_t> table;
        table.resize(256, 0xffff);

        scanner_send_slope_table(dev, sensor, 0, table);
        scanner_send_slope_table(dev, sensor, 1, table);
        scanner_send_slope_table(dev, sensor, 2, table);
        scanner_send_slope_table(dev, sensor, 3, table);
        scanner_send_slope_table(dev, sensor, 4, table);
    }

    gl841_write_freq(dev, dev->motor.base_ydpi / 4);

    // FIXME: use proper scan session
    ScanSession session;
    session.params.yres = dev->motor.base_ydpi;
    session.params.scan_method = dev->model->default_method;

    const auto* fast_profile = get_motor_profile_ptr(dev->motor.fast_profiles, 0, session);
    if (fast_profile == nullptr) {
        fast_profile = get_motor_profile_ptr(dev->motor.profiles, 0, session);
    }
    auto fast_table = create_slope_table_fastest(dev->model->asic_type, step_multiplier,
                                                 *fast_profile);

    // BUG: fast table is counted in base_ydpi / 4
    feedl = feed_steps - fast_table.table.size() * 2;

    reg->set8(0x3d, (feedl >> 16) & 0xf);
    reg->set8(0x3e, (feedl >> 8) & 0xff);
    reg->set8(0x3f, feedl & 0xff);
    reg->find_reg(0x5e).value &= ~0xe0;

    reg->set8(0x25, 0);
    reg->set8(0x26, 0);
    reg->set8(0x27, 0);

    reg->find_reg(0x02).value &= ~0x01; /*LONGCURV OFF*/
    reg->find_reg(0x02).value &= ~0x80; /*NOT_HOME OFF*/

    reg->find_reg(0x02).value |= REG_0x02_MTRPWR;
    reg->find_reg(0x02).value &= ~0x08;

    if (has_flag(flags, ScanFlag::AUTO_GO_HOME)) {
        reg->find_reg(0x02).value |= 0x20;
    } else {
        reg->find_reg(0x02).value &= ~0x20;
    }

    reg->find_reg(0x02).value &= ~0x40;

    if (has_flag(flags, ScanFlag::REVERSE)) {
        reg->find_reg(0x02).value |= REG_0x02_MTRREV;
    } else {
        reg->find_reg(0x02).value &= ~REG_0x02_MTRREV;
    }

    scanner_send_slope_table(dev, sensor, 3, fast_table.table);

    reg->set8(0x67, 0x3f);
    reg->set8(0x68, 0x3f);
    reg->set8(REG_STEPNO, 1);
    reg->set8(REG_FASTNO, 1);
    reg->set8(0x69, 1);
    reg->set8(0x6a, fast_table.table.size() / step_multiplier);
    reg->set8(0x5f, 1);
}

static void gl841_init_motor_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                       const ScanSession& session,
                                       Genesys_Register_Set* reg, const MotorProfile& motor_profile,
                                       unsigned int scan_exposure_time,/*pixel*/
                                       unsigned scan_yres, // dpi, motor resolution
                                       unsigned int scan_lines,/*lines, scan resolution*/
                                       unsigned int scan_dummy,
                                       // number of scan lines to add in a scan_lines line
                                       unsigned int feed_steps,/*1/base_ydpi*/
                                       // maybe float for half/quarter step resolution?
                                       ScanFlag flags)
{
    DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, scan_yres=%d, scan_step_type=%d, scan_lines=%d,"
                         " scan_dummy=%d, feed_steps=%d, flags=%x",
                    scan_exposure_time, scan_yres, static_cast<unsigned>(motor_profile.step_type),
                    scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags));

    unsigned step_multiplier = 2;

    unsigned int feedl;
    unsigned int min_restep = 0x20;

/*
  we calculate both tables for SCAN. the fast slope step count depends on
  how many steps we need for slow acceleration and how much steps we are
  allowed to use.
 */

    // At least in LiDE 50, 60 the fast movement table is counted in full steps.
    const auto* fast_profile = get_motor_profile_ptr(dev->motor.fast_profiles, 0, session);
    if (fast_profile == nullptr) {
        fast_profile = &motor_profile;
    }

    auto slow_table = create_slope_table(dev->model->asic_type, dev->motor, scan_yres,
                                         scan_exposure_time, step_multiplier, motor_profile);

    if (feed_steps < (slow_table.table.size() >> static_cast<unsigned>(motor_profile.step_type))) {
	/*TODO: what should we do here?? go back to exposure calculation?*/
        feed_steps = slow_table.table.size() >> static_cast<unsigned>(motor_profile.step_type);
    }

    auto fast_table = create_slope_table_fastest(dev->model->asic_type, step_multiplier,
                                                 *fast_profile);

    unsigned max_fast_slope_steps_count = step_multiplier;
    if (feed_steps > (slow_table.table.size() >> static_cast<unsigned>(motor_profile.step_type)) + 2) {
        max_fast_slope_steps_count = (feed_steps -
            (slow_table.table.size() >> static_cast<unsigned>(motor_profile.step_type))) / 2;
    }

    if (fast_table.table.size() > max_fast_slope_steps_count) {
        fast_table.slice_steps(max_fast_slope_steps_count, step_multiplier);
    }

    if ((feed_steps << static_cast<unsigned>(motor_profile.step_type)) < slow_table.table.size()) {
        feedl = 0;
    } else {
        feedl = (feed_steps << static_cast<unsigned>(motor_profile.step_type)) - slow_table.table.size();
    }

    reg->set8(0x3d, (feedl >> 16) & 0xf);
    reg->set8(0x3e, (feedl >> 8) & 0xff);
    reg->set8(0x3f, feedl & 0xff);
    reg->find_reg(0x5e).value &= ~0xe0;
    reg->set8(0x25, (scan_lines >> 16) & 0xf);
    reg->set8(0x26, (scan_lines >> 8) & 0xff);
    reg->set8(0x27, scan_lines & 0xff);
    reg->find_reg(0x02).value = REG_0x02_MTRPWR;

    if (has_flag(flags, ScanFlag::REVERSE)) {
        reg->find_reg(0x02).value |= REG_0x02_MTRREV;
    } else {
        reg->find_reg(0x02).value &= ~REG_0x02_MTRREV;
    }

    reg->find_reg(0x02).value &= ~0x08;

    if (has_flag(flags, ScanFlag::AUTO_GO_HOME))
    reg->find_reg(0x02).value |= 0x20;
    else
    reg->find_reg(0x02).value &= ~0x20;

    if (has_flag(flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) {
        reg->find_reg(0x02).value |= 0x40;
    } else {
        reg->find_reg(0x02).value &= ~0x40;
    }

    scanner_send_slope_table(dev, sensor, 0, slow_table.table);
    scanner_send_slope_table(dev, sensor, 1, slow_table.table);
    scanner_send_slope_table(dev, sensor, 2, slow_table.table);
    scanner_send_slope_table(dev, sensor, 3, fast_table.table);
    scanner_send_slope_table(dev, sensor, 4, fast_table.table);

    gl841_write_freq(dev, scan_yres);

/* now reg 0x21 and 0x24 are available, we can calculate reg 0x22 and 0x23,
   reg 0x60-0x62 and reg 0x63-0x65
   rule:
   2*STEPNO+FWDSTEP=2*FASTNO+BWDSTEP
*/
/* steps of table 0*/
    if (min_restep < slow_table.table.size() * 2 + 2) {
        min_restep = slow_table.table.size() * 2 + 2;
    }
/* steps of table 1*/
    if (min_restep < slow_table.table.size() * 2 + 2) {
        min_restep = slow_table.table.size() * 2 + 2;
    }
/* steps of table 0*/
    reg->set8(REG_FWDSTEP, min_restep - slow_table.table.size()*2);

/* steps of table 1*/
    reg->set8(REG_BWDSTEP, min_restep - slow_table.table.size()*2);

/*
  for z1/z2:
  in dokumentation mentioned variables a-d:
  a = time needed for acceleration, table 1
  b = time needed for reg 0x1f... wouldn't that be reg0x1f*exposure_time?
  c = time needed for acceleration, table 1
  d = time needed for reg 0x22... wouldn't that be reg0x22*exposure_time?
  z1 = (c+d-1) % exposure_time
  z2 = (a+b-1) % exposure_time
*/
/* i don't see any effect of this. i can only guess that this will enhance
   sub-pixel accuracy
   z1 = (slope_0_time-1) % exposure_time;
   z2 = (slope_0_time-1) % exposure_time;
*/
    reg->set24(REG_0x60, 0);
    reg->set24(REG_0x63, 0);
    reg->find_reg(REG_0x1E).value &= REG_0x1E_WDTIME;
    reg->find_reg(REG_0x1E).value |= scan_dummy;
    reg->set8(0x67, 0x3f | (static_cast<unsigned>(motor_profile.step_type) << 6));
    reg->set8(0x68, 0x3f | (static_cast<unsigned>(fast_profile->step_type) << 6));
    reg->set8(REG_STEPNO, slow_table.table.size() / step_multiplier);
    reg->set8(REG_FASTNO, slow_table.table.size() / step_multiplier);
    reg->set8(0x69, slow_table.table.size() / step_multiplier);
    reg->set8(0x6a, fast_table.table.size() / step_multiplier);
    reg->set8(0x5f, fast_table.table.size() / step_multiplier);
}

static void gl841_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                         Genesys_Register_Set* reg, unsigned int exposure_time,
                                         const ScanSession& session)
{
    DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time);

    dev->cmd_set->set_fe(dev, sensor, AFE_SET);

    /* gpio part.*/
    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) {
        if (session.params.xres <= 600) {
            reg->find_reg(REG_0x6C).value &= ~0x80;
        } else {
            reg->find_reg(REG_0x6C).value |= 0x80;
        }
      }
    if (dev->model->gpio_id == GpioId::CANON_LIDE_80) {
        if (session.params.xres <= 600) {
            reg->find_reg(REG_0x6C).value &= ~0x40;
            reg->find_reg(REG_0x6C).value |= 0x20;
        } else {
            reg->find_reg(REG_0x6C).value &= ~0x20;
            reg->find_reg(REG_0x6C).value |= 0x40;
        }
    }

    /* enable shading */
    reg->find_reg(0x01).value |= REG_0x01_SCAN;
    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) ||
        has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION)) {
        reg->find_reg(0x01).value &= ~REG_0x01_DVDSET;
    } else {
        reg->find_reg(0x01).value |= REG_0x01_DVDSET;
    }

    /* average looks better than deletion, and we are already set up to
       use  one of the average enabled resolutions
    */
    reg->find_reg(0x03).value |= REG_0x03_AVEENB;
    sanei_genesys_set_lamp_power(dev, sensor, *reg,
                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP));

    /* BW threshold */
    reg->set8(0x2e, 0x7f);
    reg->set8(0x2f, 0x7f);


    /* monochrome / color scan */
    switch (session.params.depth) {
	case 8:
            reg->find_reg(0x04).value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
	    break;
	case 16:
            reg->find_reg(0x04).value &= ~REG_0x04_LINEART;
            reg->find_reg(0x04).value |= REG_0x04_BITSET;
	    break;
    }

    /* AFEMOD should depend on FESET, and we should set these
     * bits separately */
    reg->find_reg(0x04).value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD);
    if (session.params.channels == 1) {
    switch (session.params.color_filter)
	  {
            case ColorFilter::RED:
                reg->find_reg(0x04).value |= 0x14;
                break;
            case ColorFilter::GREEN:
                reg->find_reg(0x04).value |= 0x18;
                break;
            case ColorFilter::BLUE:
                reg->find_reg(0x04).value |= 0x1c;
                break;
            default:
                reg->find_reg(0x04).value |= 0x10;
                break;
	  }
      }
    else
      {
        if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
            reg->find_reg(0x04).value |= 0x22;	/* slow color pixel by pixel */
          }
	else
          {
        reg->find_reg(0x04).value |= 0x10;	/* color pixel by pixel */
          }
      }

    /* CIS scanners can do true gray by setting LEDADD */
    reg->find_reg(0x87).value &= ~REG_0x87_LEDADD;

    // enable gamma tables
    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;
    }

    /* sensor parameters */
    scanner_setup_sensor(*dev, sensor, dev->reg);
    reg->set8(0x29, 255); /*<<<"magic" number, only suitable for cis*/
    reg->set16(REG_DPISET, sensor.register_dpiset);
    reg->set16(REG_STRPIXEL, session.pixel_startx);
    reg->set16(REG_ENDPIXEL, session.pixel_endx);
    reg->set24(REG_MAXWD, session.output_line_bytes);
    reg->set16(REG_LPERIOD, exposure_time);
    reg->set8(0x34, sensor.dummy_pixel);
}

/** @brief compute exposure time
 * Compute exposure time for the device and the given scan resolution
 */
static int gl841_exposure_time(Genesys_Device *dev, const Genesys_Sensor& sensor,
                               const MotorProfile& profile, float slope_dpi,
                               int start,
                               int used_pixels)
{
    int led_exposure = 0;
    if (dev->model->is_cis) {
        unsigned dummy = dev->reg.find_reg(0x19).value;
        unsigned max_sensor_exposure = std::max({sensor.exposure.red, sensor.exposure.green,
                                                 sensor.exposure.blue});
        led_exposure = dummy + max_sensor_exposure;
    }
    return sanei_genesys_exposure_time2(dev, profile, slope_dpi,
                                        start + used_pixels,/*+tgtime? currently done in sanei_genesys_exposure_time2 with tgtime = 32 pixel*/
                                        led_exposure);
}

void CommandSetGl841::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 move;
  int exposure_time;

  int slope_dpi = 0;
  int dummy = 0;

/* dummy */
  /* dummy lines: may not be useful, for instance 250 dpi works with 0 or 1
     dummy line. Maybe the dummy line adds correctness since the motor runs
     slower (higher dpi)
  */
/* for cis this creates better aligned color lines:
dummy \ scanned lines
   0: R           G           B           R ...
   1: R        G        B        -        R ...
   2: R      G      B       -      -      R ...
   3: R     G     B     -     -     -     R ...
   4: R    G    B     -   -     -    -    R ...
   5: R    G   B    -   -   -    -   -    R ...
   6: R   G   B   -   -   -   -   -   -   R ...
   7: R   G  B   -  -   -   -  -   -  -   R ...
   8: R  G  B   -  -  -   -  -  -   -  -  R ...
   9: R  G  B  -  -  -  -  -  -  -  -  -  R ...
  10: R  G B  -  -  -  - -  -  -  -  - -  R ...
  11: R  G B  - -  - -  -  - -  - -  - -  R ...
  12: R G  B - -  - -  - -  - -  - - -  - R ...
  13: R G B  - - - -  - - -  - - - -  - - R ...
  14: R G B - - -  - - - - - -  - - - - - R ...
  15: R G B - - - - - - - - - - - - - - - R ...
 -- pierre
 */
  dummy = 0;

/* 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);

    const auto& motor_profile = get_motor_profile(dev->motor.profiles, 0, session);

    exposure_time = gl841_exposure_time(dev, sensor, motor_profile, slope_dpi,
                                        session.pixel_startx, session.optical_pixels);

    gl841_init_optical_regs_scan(dev, sensor, reg, exposure_time, session);

    move = session.params.starty;

  /* subtract current head position */
    move -= (dev->head_pos(ScanHeadId::PRIMARY) * session.params.yres) / dev->motor.base_ydpi;

  if (move < 0)
      move = 0;

  /* round it */
/* the move is not affected by dummy -- pierre */
/*  move = ((move + dummy) / (dummy + 1)) * (dummy + 1);*/

    if (has_flag(session.params.flags, ScanFlag::SINGLE_LINE)) {
        gl841_init_motor_regs_off(reg, session.optical_line_count);
    } else {
        gl841_init_motor_regs_scan(dev, sensor, session, reg, motor_profile, exposure_time,
                                   slope_dpi, session.optical_line_count, dummy, move,
                                   session.params.flags);
  }

    setup_image_pipeline(*dev, session);

    dev->read_active = true;

    dev->session = session;

    dev->total_bytes_read = 0;
    dev->total_bytes_to_read = (size_t)session.output_line_bytes_requested * (size_t)session.params.lines;
    if (session.use_host_side_gray) {
        dev->total_bytes_to_read /= 3;
    }

    DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read);
}

ScanSession CommandSetGl841::calculate_scan_session(const Genesys_Device* dev,
                                                    const Genesys_Sensor& sensor,
                                                    const Genesys_Settings& settings) const
{
    DBG_HELPER(dbg);
    debug_dump(DBG_info, settings);

    /* steps to move to reach scanning area:
       - first we move to physical start of scanning
       either by a fixed steps amount from the black strip
       or by a fixed amount from parking position,
       minus the steps done during shading calibration
       - then we move by the needed offset whitin physical
       scanning area

       assumption: steps are expressed at maximum motor resolution

       we need:
       float y_offset;
       float y_size;
       float y_offset_calib;
       mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH
    */
    float move = dev->model->y_offset;
    move += dev->settings.tl_y;

    int move_dpi = dev->motor.base_ydpi;
    move = static_cast<float>((move * move_dpi) / MM_PER_INCH);

    float start = dev->model->x_offset;
    start += dev->settings.tl_x;
    start = static_cast<float>((start * dev->settings.xres) / 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.contrast_adjustment = dev->settings.contrast;
    session.params.brightness_adjustment = dev->settings.brightness;
    session.params.flags = ScanFlag::NONE;
    compute_session(dev, session, sensor);

    return session;
}

// for fast power saving methods only, like disabling certain amplifiers
void CommandSetGl841::save_power(Genesys_Device* dev, bool enable) const
{
    DBG_HELPER_ARGS(dbg, "enable = %d", enable);

    const auto& sensor = sanei_genesys_find_sensor_any(dev);

    if (enable)
    {
    if (dev->model->gpio_id == GpioId::CANON_LIDE_35)
	{
/* expect GPIO17 to be enabled, and GPIO9 to be disabled,
   while GPIO8 is disabled*/
/* final state: GPIO8 disabled, GPIO9 enabled, GPIO17 disabled,
   GPIO18 disabled*/

            std::uint8_t val = dev->interface->read_register(REG_0x6D);
            dev->interface->write_register(REG_0x6D, val | 0x80);

            dev->interface->sleep_ms(1);

	    /*enable GPIO9*/
            val = dev->interface->read_register(REG_0x6C);
            dev->interface->write_register(REG_0x6C, val | 0x01);

	    /*disable GPO17*/
            val = dev->interface->read_register(REG_0x6B);
            dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO17);

	    /*disable GPO18*/
            val = dev->interface->read_register(REG_0x6B);
            dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO18);

            dev->interface->sleep_ms(1);

            val = dev->interface->read_register(REG_0x6D);
            dev->interface->write_register(REG_0x6D, val & ~0x80);

	}
    if (dev->model->gpio_id == GpioId::DP685)
	  {
            std::uint8_t val = dev->interface->read_register(REG_0x6B);
            dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO17);
            dev->reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17;
            dev->initial_regs.find_reg(0x6b).value &= ~REG_0x6B_GPO17;
	  }

        set_fe(dev, sensor, AFE_POWER_SAVE);

    }
    else
    {
    if (dev->model->gpio_id == GpioId::CANON_LIDE_35)
	{
/* expect GPIO17 to be enabled, and GPIO9 to be disabled,
   while GPIO8 is disabled*/
/* final state: GPIO8 enabled, GPIO9 disabled, GPIO17 enabled,
   GPIO18 enabled*/

            std::uint8_t val = dev->interface->read_register(REG_0x6D);
            dev->interface->write_register(REG_0x6D, val | 0x80);

            dev->interface->sleep_ms(10);

	    /*disable GPIO9*/
            val = dev->interface->read_register(REG_0x6C);
            dev->interface->write_register(REG_0x6C, val & ~0x01);

	    /*enable GPIO10*/
            val = dev->interface->read_register(REG_0x6C);
            dev->interface->write_register(REG_0x6C, val | 0x02);

	    /*enable GPO17*/
            val = dev->interface->read_register(REG_0x6B);
            dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO17);
            dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
            dev->initial_regs.find_reg(0x6b).value |= REG_0x6B_GPO17;

	    /*enable GPO18*/
            val = dev->interface->read_register(REG_0x6B);
            dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO18);
            dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO18;
            dev->initial_regs.find_reg(0x6b).value |= REG_0x6B_GPO18;

	}
    if (dev->model->gpio_id == GpioId::DP665
            || dev->model->gpio_id == GpioId::DP685)
	  {
            std::uint8_t val = dev->interface->read_register(REG_0x6B);
            dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO17);
            dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
            dev->initial_regs.find_reg(0x6b).value |= REG_0x6B_GPO17;
	  }

    }
}

void CommandSetGl841::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
{
    DBG_HELPER_ARGS(dbg, "delay = %d", delay);
  // FIXME: SEQUENTIAL not really needed in this case
  Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL);
  int rate, exposure_time, tgtime, time;

    local_reg.init_reg(0x01, dev->reg.get8(0x01));	/* disable fastmode */
    local_reg.init_reg(0x03, dev->reg.get8(0x03));	/* Lamp power control */
    local_reg.init_reg(0x05, dev->reg.get8(0x05)); /*& ~REG_0x05_BASESEL*/;	/* 24 clocks/pixel */
    local_reg.init_reg(0x18, 0x00); // Set CCD type
    local_reg.init_reg(0x38, 0x00);
    local_reg.init_reg(0x39, 0x00);

    // period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE
    local_reg.init_reg(0x1c, dev->reg.get8(0x05) & ~REG_0x1C_TGTIME);

    if (!delay) {
        local_reg.find_reg(0x03).value = local_reg.find_reg(0x03).value & 0xf0;	/* disable lampdog and set lamptime = 0 */
    } else if (delay < 20) {
        local_reg.find_reg(0x03).value = (local_reg.find_reg(0x03).value & 0xf0) | 0x09;	/* enable lampdog and set lamptime = 1 */
    } else {
        local_reg.find_reg(0x03).value = (local_reg.find_reg(0x03).value & 0xf0) | 0x0f;	/* enable lampdog and set lamptime = 7 */
    }

  time = delay * 1000 * 60;	/* -> msec */
  exposure_time = static_cast<std::uint32_t>(time * 32000.0 /
                 (24.0 * 64.0 * (local_reg.find_reg(0x03).value & REG_0x03_LAMPTIM) *
		  1024.0) + 0.5);
  /* 32000 = system clock, 24 = clocks per pixel */
  rate = (exposure_time + 65536) / 65536;
  if (rate > 4)
    {
      rate = 8;
      tgtime = 3;
    }
  else if (rate > 2)
    {
      rate = 4;
      tgtime = 2;
    }
  else if (rate > 1)
    {
      rate = 2;
      tgtime = 1;
    }
  else
    {
      rate = 1;
      tgtime = 0;
    }

  local_reg.find_reg(0x1c).value |= tgtime;
  exposure_time /= rate;

  if (exposure_time > 65535)
    exposure_time = 65535;

  local_reg.set8(0x38, exposure_time >> 8);
  local_reg.set8(0x39, exposure_time & 255);	/* lowbyte */

    dev->interface->write_registers(local_reg);
}

static bool gl841_get_paper_sensor(Genesys_Device* dev)
{
    DBG_HELPER(dbg);

    std::uint8_t val = dev->interface->read_register(REG_0x6D);

    return (val & 0x1) == 0;
}

void CommandSetGl841::eject_document(Genesys_Device* dev) const
{
    DBG_HELPER(dbg);
  Genesys_Register_Set local_reg;
  unsigned int init_steps;
  float feed_mm;
  int loop;

    if (!dev->model->is_sheetfed) {
      DBG(DBG_proc, "%s: there is no \"eject sheet\"-concept for non sheet fed\n", __func__);
      return;
    }


  local_reg.clear();

    // FIXME: unused result
    scanner_read_status(*dev);
    scanner_stop_action(*dev);

  local_reg = dev->reg;

    regs_set_optical_off(dev->model->asic_type, local_reg);

  const auto& sensor = sanei_genesys_find_sensor_any(dev);
    gl841_init_motor_regs_feed(dev, sensor, &local_reg, 65536, ScanFlag::NONE);

    dev->interface->write_registers(local_reg);

    try {
        scanner_start_action(*dev, true);
    } catch (...) {
        catch_all_exceptions(__func__, [&]() { scanner_stop_action(*dev); });
        // restore original registers
        catch_all_exceptions(__func__, [&]()
        {
            dev->interface->write_registers(dev->reg);
        });
        throw;
    }

    if (is_testing_mode()) {
        dev->interface->test_checkpoint("eject_document");
        scanner_stop_action(*dev);
        return;
    }

    if (gl841_get_paper_sensor(dev)) {
      DBG(DBG_info, "%s: paper still loaded\n", __func__);
      /* force document TRUE, because it is definitely present */
        dev->document = true;
        dev->set_head_pos_zero(ScanHeadId::PRIMARY);

      loop = 300;
      while (loop > 0)		/* do not wait longer then 30 seconds */
	{

            if (!gl841_get_paper_sensor(dev)) {
                DBG(DBG_info, "%s: reached home position\n", __func__);
                break;
            }
          dev->interface->sleep_ms(100);
	  --loop;
	}

      if (loop == 0)
	{
          // when we come here then the scanner needed too much time for this, so we better stop
          // the motor
          catch_all_exceptions(__func__, [&](){ scanner_stop_action(*dev); });
          throw SaneException(SANE_STATUS_IO_ERROR,
                              "timeout while waiting for scanhead to go home");
	}
    }

    feed_mm = dev->model->eject_feed;
    if (dev->document) {
        feed_mm += dev->model->post_scan;
    }

        sanei_genesys_read_feed_steps(dev, &init_steps);

  /* now feed for extra <number> steps */
  loop = 0;
  while (loop < 300)		/* do not wait longer then 30 seconds */
    {
      unsigned int steps;

        sanei_genesys_read_feed_steps(dev, &steps);

      DBG(DBG_info, "%s: init_steps: %d, steps: %d\n", __func__, init_steps, steps);

      if (steps > init_steps + (feed_mm * dev->motor.base_ydpi) / MM_PER_INCH)
	{
	  break;
	}

        dev->interface->sleep_ms(100);
      ++loop;
    }

    scanner_stop_action(*dev);

    dev->document = false;
}

void CommandSetGl841::update_home_sensor_gpio(Genesys_Device& dev) const
{
    if (dev.model->gpio_id == GpioId::CANON_LIDE_35) {
        dev.interface->read_register(REG_0x6C);
        dev.interface->write_register(REG_0x6C, dev.gpo.regs.get_value(0x6c));
    }
    if (dev.model->gpio_id == GpioId::CANON_LIDE_80) {
        dev.interface->read_register(REG_0x6B);
        dev.interface->write_register(REG_0x6B, REG_0x6B_GPO18 | REG_0x6B_GPO17);
    }
}

void CommandSetGl841::load_document(Genesys_Device* dev) const
{
    DBG_HELPER(dbg);
  int loop = 300;
  while (loop > 0)		/* do not wait longer then 30 seconds */
    {
        if (gl841_get_paper_sensor(dev)) {
	  DBG(DBG_info, "%s: document inserted\n", __func__);

	  /* when loading OK, document is here */
        dev->document = true;

          // give user some time to place document correctly
          dev->interface->sleep_ms(1000);
	  break;
	}
        dev->interface->sleep_ms(100);
      --loop;
    }

  if (loop == 0)
    {
        // when we come here then the user needed to much time for this
        throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for document");
    }
}

/**
 * detects end of document and adjust current scan
 * to take it into account
 * used by sheetfed scanners
 */
void CommandSetGl841::detect_document_end(Genesys_Device* dev) const
{
    DBG_HELPER(dbg);
    bool paper_loaded = gl841_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;

      /* we can't rely on total_bytes_to_read since the frontend
       * might have been slow to read data, so we re-evaluate the
       * amount of data to scan form the hardware settings
       */
        unsigned scanned_lines = 0;
        try {
            sanei_genesys_read_scancnt(dev, &scanned_lines);
        } catch (...) {
            dev->total_bytes_to_read = dev->total_bytes_read;
            throw;
        }

        if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS && dev->model->is_cis) {
            scanned_lines /= 3;
        }

        std::size_t output_lines = dev->session.output_line_count;

        std::size_t offset_lines = static_cast<std::size_t>(
                (dev->model->post_scan / MM_PER_INCH) * dev->settings.yres);

        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) {
                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;
            }
        }
    }
}

// Send the low-level scan command
// todo : is this that useful ?
void CommandSetGl841::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                 Genesys_Register_Set* reg, bool start_motor) const
{
    DBG_HELPER(dbg);
    (void) sensor;
  // FIXME: SEQUENTIAL not really needed in this case
  Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL);
    std::uint8_t val;

    if (dev->model->gpio_id == GpioId::CANON_LIDE_80) {
        val = dev->interface->read_register(REG_0x6B);
        val = REG_0x6B_GPO18;
        dev->interface->write_register(REG_0x6B, val);
    }

    if (dev->model->model_id == ModelId::CANON_LIDE_50 ||
        dev->model->model_id == ModelId::CANON_LIDE_60)
    {
        if (dev->session.params.yres >= 1200) {
            dev->interface->write_register(REG_0x6C, 0x82);
        } else {
            dev->interface->write_register(REG_0x6C, 0x02);
        }
        if (dev->session.params.yres >= 600) {
            dev->interface->write_register(REG_0x6B, 0x01);
        } else {
            dev->interface->write_register(REG_0x6B, 0x03);
        }
    }

    if (dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
        local_reg.init_reg(0x03, reg->get8(0x03) | REG_0x03_LAMPPWR);
    } else {
        // TODO PLUSTEK_3600: why ??
        local_reg.init_reg(0x03, reg->get8(0x03));
    }

    local_reg.init_reg(0x01, reg->get8(0x01) | REG_0x01_SCAN);
    local_reg.init_reg(0x0d, 0x01);

    // scanner_start_action(dev, start_motor)
    if (start_motor) {
        local_reg.init_reg(0x0f, 0x01);
    } else {
        // do not start motor yet
        local_reg.init_reg(0x0f, 0x00);
    }

    dev->interface->write_registers(local_reg);

    dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
}


// Send the stop scan command
void CommandSetGl841::end_scan(Genesys_Device* dev, Genesys_Register_Set __sane_unused__* reg,
                               bool check_stop) const
{
    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop);

    if (!dev->model->is_sheetfed) {
        scanner_stop_action(*dev);
    }
}

// Moves the slider to the home (top) position slowly
void CommandSetGl841::move_back_home(Genesys_Device* dev, bool wait_until_home) const
{
    scanner_move_back_home(*dev, wait_until_home);
}

// init registers for shading calibration
void CommandSetGl841::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                            Genesys_Register_Set& regs) const
{
    DBG_HELPER(dbg);

    unsigned channels = 3;

    unsigned resolution = sensor.shading_resolution;
    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
                                                         dev->settings.scan_method);

    unsigned calib_lines =
            static_cast<unsigned>(dev->model->y_size_calib_dark_white_mm * resolution / MM_PER_INCH);
    unsigned starty =
            static_cast<unsigned>(dev->model->y_offset_calib_dark_white_mm * dev->motor.base_ydpi / MM_PER_INCH);
    ScanSession session;
    session.params.xres = resolution;
    session.params.yres = resolution;
    session.params.startx = 0;
    session.params.starty = starty;
    session.params.pixels = dev->model->x_size_calib_mm * resolution / MM_PER_INCH;
    session.params.lines = calib_lines;
    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.contrast_adjustment = dev->settings.contrast;
    session.params.brightness_adjustment = dev->settings.brightness;
    session.params.flags = ScanFlag::DISABLE_SHADING |
                           ScanFlag::DISABLE_GAMMA;
    compute_session(dev, session, calib_sensor);

    init_regs_for_scan_session(dev, calib_sensor, &regs, session);

    dev->calib_session = session;
}

// this function sends generic gamma table (ie linear ones) or the Sensor specific one if provided
void CommandSetGl841::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
{
    DBG_HELPER(dbg);
  int size;

  size = 256;

    auto gamma = generate_gamma_buffer(dev, sensor, 16, 65535, size);

    dev->interface->write_gamma(0x28, 0x0000, gamma.data(), size * 2 * 3);
}


/* 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 CommandSetGl841::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                                Genesys_Register_Set& regs) const
{
    return scanner_led_calibration(*dev, sensor, regs);
}

/** @brief calibration for AD frontend devices
 * offset calibration assumes that the scanning head is on a black area
 * For LiDE80 analog frontend
 * 0x0003 : is gain and belongs to [0..63]
 * 0x0006 : is offset
 * We scan a line with no gain until average offset reaches the target
 */
static void ad_fe_offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                     Genesys_Register_Set& regs)
{
    DBG_HELPER(dbg);
  int average;
  int turn;
  int top;
  int bottom;
  int target;

  /* don't impact 3600 behavior since we can't test it */
    if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
      return;
    }

    unsigned resolution = sensor.shading_resolution;

    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3,
                                                              dev->settings.scan_method);

    unsigned num_pixels = dev->model->x_size_calib_mm * resolution / MM_PER_INCH;
    ScanSession session;
    session.params.xres = resolution;
    session.params.yres = dev->settings.yres;
    session.params.startx = 0;
    session.params.starty = 0;
    session.params.pixels = num_pixels;
    session.params.lines = 1;
    session.params.depth = 8;
    session.params.channels = 3;
    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.contrast_adjustment = dev->settings.contrast;
    session.params.brightness_adjustment = dev->settings.brightness;
    session.params.flags = ScanFlag::DISABLE_SHADING |
                           ScanFlag::DISABLE_GAMMA |
                           ScanFlag::SINGLE_LINE |
                           ScanFlag::IGNORE_STAGGER_OFFSET |
                           ScanFlag::IGNORE_COLOR_OFFSET;
    compute_session(dev, session, calib_sensor);

    dev->cmd_set->init_regs_for_scan_session(dev, calib_sensor, &regs, session);

    // FIXME: we're reading twice as much data for no reason
    std::size_t total_size = session.output_line_bytes * 2;
    std::vector<std::uint8_t> line(total_size);

  dev->frontend.set_gain(0, 0);
  dev->frontend.set_gain(1, 0);
  dev->frontend.set_gain(2, 0);

  /* loop on scan until target offset is reached */
  turn=0;
  target=24;
  bottom=0;
  top=255;
  do {
      /* set up offset mid range */
      dev->frontend.set_offset(0, (top + bottom) / 2);
      dev->frontend.set_offset(1, (top + bottom) / 2);
      dev->frontend.set_offset(2, (top + bottom) / 2);

      /* scan line */
      DBG(DBG_info, "%s: starting line reading\n", __func__);
        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("ad_fe_offset_calibration");
            scanner_stop_action(*dev);
            return;
        }

      sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
      scanner_stop_action(*dev);
      if (dbg_log_image_data()) {
          char fn[30];
          std::snprintf(fn, 30, "gl841_offset_%02d.tiff", turn);
          write_tiff_file(fn, line.data(), 8, 3, num_pixels, 1);
      }

      /* search for minimal value */
      average=0;
        for (std::size_t i = 0; i < total_size; i++)
        {
            average += line[i];
        }
      average/=total_size;
      DBG(DBG_data, "%s: average=%d\n", __func__, average);

      /* if min value is above target, the current value becomes the new top
       * else it is the new bottom */
      if(average>target)
        {
          top=(top+bottom)/2;
        }
      else
        {
          bottom=(top+bottom)/2;
        }
      turn++;
  } while ((top-bottom)>1 && turn < 100);

    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));
}

/* this function does the offset calibration by scanning one line of the calibration
   area below scanner's top. There is a black margin and the remaining is white.

this function expects the slider to be where?
*/
void CommandSetGl841::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                         Genesys_Register_Set& regs) const
{
    DBG_HELPER(dbg);
  int off[3],offh[3],offl[3],off1[3],off2[3];
  int min1[3],min2[3];
    unsigned cmin[3],cmax[3];
  int turn;
  int mintgt = 0x400;

  /* Analog Device fronted have a different calibration */
    if ((dev->reg.find_reg(0x04).value & REG_0x04_FESET) == 0x02) {
        ad_fe_offset_calibration(dev, sensor, regs);
        return;
    }

  /* offset calibration is always done in color mode */
    unsigned channels = 3;

    unsigned resolution = sensor.shading_resolution;

    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
                                                         dev->settings.scan_method);

    ScanSession session;
    session.params.xres = resolution;
    session.params.yres = dev->settings.yres;
    session.params.startx = 0;
    session.params.starty = 0;
    session.params.pixels = dev->model->x_size_calib_mm * resolution / MM_PER_INCH;
    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.contrast_adjustment = dev->settings.contrast;
    session.params.brightness_adjustment = dev->settings.brightness;
    session.params.flags = ScanFlag::DISABLE_SHADING |
                           ScanFlag::DISABLE_GAMMA |
                           ScanFlag::SINGLE_LINE |
                           ScanFlag::IGNORE_STAGGER_OFFSET |
                           ScanFlag::IGNORE_COLOR_OFFSET |
                           ScanFlag::DISABLE_LAMP;
    compute_session(dev, session, calib_sensor);

    init_regs_for_scan_session(dev, calib_sensor, &regs, session);

  /* scan first line of data with no offset nor gain */
/*WM8199: gain=0.73; offset=-260mV*/
/*okay. the sensor black level is now at -260mV. we only get 0 from AFE...*/
/* we should probably do real calibration here:
 * -detect acceptable offset with binary search
 * -calculate offset from this last version
 *
 * acceptable offset means
 *   - few completely black pixels(<10%?)
 *   - few completely white pixels(<10%?)
 *
 * final offset should map the minimum not completely black
 * pixel to 0(16 bits)
 *
 * this does account for dummy pixels at the end of ccd
 * this assumes slider is at black strip(which is not quite as black as "no
 * signal").
 *
 */
  dev->frontend.set_gain(0, 0);
  dev->frontend.set_gain(1, 0);
  dev->frontend.set_gain(2, 0);
  offh[0] = 0xff;
  offh[1] = 0xff;
  offh[2] = 0xff;
  offl[0] = 0x00;
  offl[1] = 0x00;
  offl[2] = 0x00;
  turn = 0;

    Image first_line;

    bool acceptable = false;
  do {

        dev->interface->write_registers(regs);

        for (unsigned j = 0; j < channels; j++) {
	  off[j] = (offh[j]+offl[j])/2;
          dev->frontend.set_offset(j, off[j]);
      }

        dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);

      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");
            return;
        }

        first_line = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes);

        if (dbg_log_image_data()) {
            char fn[30];
            std::snprintf(fn, 30, "gl841_offset1_%02d.tiff", turn);
            write_tiff_file(fn, first_line);
        }

        acceptable = true;

        for (unsigned ch = 0; ch < channels; ch++) {
            cmin[ch] = 0;
            cmax[ch] = 0;

            for (std::size_t x = 0; x < first_line.get_width(); x++) {
                auto value = first_line.get_raw_channel(x, 0, ch);
                if (value < 10) {
                    cmin[ch]++;
                }
                if (value > 65525) {
                    cmax[ch]++;
                }
            }

          /* TODO the DP685 has a black strip in the middle of the sensor
           * should be handled in a more elegant way , could be a bug */
            if (dev->model->sensor_id == SensorId::CCD_DP685) {
                cmin[ch] -= 20;
            }

            if (cmin[ch] > first_line.get_width() / 100) {
          acceptable = false;
	      if (dev->model->is_cis)
		  offl[0] = off[0];
	      else
          offl[ch] = off[ch];
            }
            if (cmax[ch] > first_line.get_width() / 100) {
          acceptable = false;
	      if (dev->model->is_cis)
		  offh[0] = off[0];
	      else
          offh[ch] = off[ch];
            }
        }

      DBG(DBG_info,"%s: black/white pixels: %d/%d,%d/%d,%d/%d\n", __func__, cmin[0], cmax[0],
          cmin[1], cmax[1], cmin[2], cmax[2]);

      if (dev->model->is_cis) {
	  offh[2] = offh[1] = offh[0];
	  offl[2] = offl[1] = offl[0];
      }

        scanner_stop_action(*dev);

      turn++;
  } while (!acceptable && turn < 100);

  DBG(DBG_info,"%s: acceptable offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]);


    for (unsigned ch = 0; ch < channels; ch++) {
        off1[ch] = off[ch];

        min1[ch] = 65536;

        for (std::size_t x = 0; x < first_line.get_width(); x++) {
            auto value = first_line.get_raw_channel(x, 0, ch);

            if (min1[ch] > value && value >= 10) {
                min1[ch] = value;
            }
        }
    }


  offl[0] = off[0];
  offl[1] = off[0];
  offl[2] = off[0];
  turn = 0;

    Image second_line;
  do {

        for (unsigned j=0; j < channels; j++) {
	  off[j] = (offh[j]+offl[j])/2;
          dev->frontend.set_offset(j, off[j]);
        }

        dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);

      DBG(DBG_info, "%s: starting second line reading\n", __func__);
        dev->interface->write_registers(regs);
        dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
        second_line = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes);

        if (dbg_log_image_data()) {
            char fn[30];
            std::snprintf(fn, 30, "gl841_offset2_%02d.tiff", turn);
            write_tiff_file(fn, second_line);
        }

        acceptable = true;

        for (unsigned ch = 0; ch < channels; ch++) {
            cmin[ch] = 0;
            cmax[ch] = 0;

            for (std::size_t x = 0; x < second_line.get_width(); x++) {
                auto value = second_line.get_raw_channel(x, 0, ch);

                if (value < 10) {
                    cmin[ch]++;
                }
                if (value > 65525) {
                    cmax[ch]++;
                }
            }

            if (cmin[ch] > second_line.get_width() / 100) {
            acceptable = false;
	      if (dev->model->is_cis)
		  offl[0] = off[0];
	      else
                    offl[ch] = off[ch];
            }
            if (cmax[ch] > second_line.get_width() / 100) {
            acceptable = false;
	      if (dev->model->is_cis)
		  offh[0] = off[0];
	      else
                offh[ch] = off[ch];
            }
        }

      DBG(DBG_info, "%s: black/white pixels: %d/%d,%d/%d,%d/%d\n", __func__, cmin[0], cmax[0],
          cmin[1], cmax[1], cmin[2], cmax[2]);

      if (dev->model->is_cis) {
	  offh[2] = offh[1] = offh[0];
	  offl[2] = offl[1] = offl[0];
      }

        scanner_stop_action(*dev);

      turn++;

  } while (!acceptable && turn < 100);

  DBG(DBG_info, "%s: acceptable offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]);


    for (unsigned ch = 0; ch < channels; ch++) {
        off2[ch] = off[ch];

        min2[ch] = 65536;

        for (std::size_t x = 0; x < second_line.get_width(); x++) {
            auto value = second_line.get_raw_channel(x, 0, ch);

            if (min2[ch] > value && value != 0) {
                min2[ch] = value;
            }
        }
    }

  DBG(DBG_info, "%s: first set: %d/%d,%d/%d,%d/%d\n", __func__, off1[0], min1[0], off1[1], min1[1],
      off1[2], min1[2]);

  DBG(DBG_info, "%s: second set: %d/%d,%d/%d,%d/%d\n", __func__, off2[0], min2[0], off2[1], min2[1],
      off2[2], min2[2]);

/*
  calculate offset for each channel
  based on minimal pixel value min1 at offset off1 and minimal pixel value min2
  at offset off2

  to get min at off, values are linearly interpolated:
  min=real+off*fact
  min1=real+off1*fact
  min2=real+off2*fact

  fact=(min1-min2)/(off1-off2)
  real=min1-off1*(min1-min2)/(off1-off2)

  off=(min-min1+off1*(min1-min2)/(off1-off2))/((min1-min2)/(off1-off2))

  off=(min*(off1-off2)+min1*off2-off1*min2)/(min1-min2)

 */
    for (unsigned ch = 0; ch < channels; ch++) {
        if (min2[ch] - min1[ch] == 0) {
/*TODO: try to avoid this*/
	  DBG(DBG_warn, "%s: difference too small\n", __func__);
            if (mintgt * (off1[ch] - off2[ch]) + min1[ch] * off2[ch] - min2[ch] * off1[ch] >= 0) {
                off[ch] = 0x0000;
            } else {
                off[ch] = 0xffff;
            }
        } else {
            off[ch] = (mintgt * (off1[ch] - off2[ch]) + min1[ch] * off2[ch] - min2[ch] * off1[ch])/(min1[ch]-min2[ch]);
        }
        if (off[ch] > 255) {
            off[ch] = 255;
        }
        if (off[ch] < 0) {
            off[ch] = 0;
        }
      dev->frontend.set_offset(ch, off[ch]);
  }

  DBG(DBG_info, "%s: final offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]);

  if (dev->model->is_cis) {
      if (off[0] < off[1])
	  off[0] = off[1];
      if (off[0] < off[2])
	  off[0] = off[2];
      dev->frontend.set_offset(0, off[0]);
      dev->frontend.set_offset(1, off[0]);
      dev->frontend.set_offset(2, off[0]);
  }

  if (channels == 1)
    {
      dev->frontend.set_offset(1, dev->frontend.get_offset(0));
      dev->frontend.set_offset(2, dev->frontend.get_offset(0));
    }
}


/* 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 CommandSetGl841::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                              Genesys_Register_Set& regs, int dpi) const
{
    scanner_coarse_gain_calibration(*dev, sensor, regs, dpi);
}

// wait for lamp warmup by scanning the same line until difference
// between 2 scans is below a threshold
void CommandSetGl841::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                           Genesys_Register_Set* local_reg) const
{
    DBG_HELPER(dbg);
    int num_pixels = 4 * 300;
  *local_reg = dev->reg;

/* okay.. these should be defaults stored somewhere */
  dev->frontend.set_gain(0, 0);
  dev->frontend.set_gain(1, 0);
  dev->frontend.set_gain(2, 0);
  dev->frontend.set_offset(0, 0x80);
  dev->frontend.set_offset(1, 0x80);
  dev->frontend.set_offset(2, 0x80);

    auto flags = ScanFlag::DISABLE_SHADING |
                 ScanFlag::DISABLE_GAMMA |
                 ScanFlag::SINGLE_LINE |
                 ScanFlag::IGNORE_STAGGER_OFFSET |
                 ScanFlag::IGNORE_COLOR_OFFSET;
    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
    {
        flags |= ScanFlag::USE_XPA;
    }

    ScanSession session;
    session.params.xres = sensor.full_resolution;
    session.params.yres = dev->settings.yres;
    session.params.startx = sensor.dummy_pixel;
    session.params.starty = 0;
    session.params.pixels = num_pixels;
    session.params.lines = 1;
    session.params.depth = dev->model->bpp_color_values.front();
    session.params.channels = 3;
    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.contrast_adjustment = 0;
    session.params.brightness_adjustment = 0;
    session.params.flags = flags;

    compute_session(dev, session, sensor);

    init_regs_for_scan_session(dev, sensor, local_reg, session);
}

/*
 * initialize ASIC : registers, motor tables, and gamma tables
 * then ensure scanner's head is at home
 */
void CommandSetGl841::init(Genesys_Device* dev) const
{
    DBG_INIT();
    DBG_HELPER(dbg);
    sanei_genesys_asic_init(dev);
}

void CommandSetGl841::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.
    std::uint8_t val;

    if (s->dev->model->gpio_id == GpioId::CANON_LIDE_35
        || s->dev->model->gpio_id == GpioId::CANON_LIDE_80)
    {
        val = s->dev->interface->read_register(REG_0x6D);
        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);
    }

    if (s->dev->model->gpio_id == GpioId::XP300 ||
        s->dev->model->gpio_id == GpioId::DP665 ||
        s->dev->model->gpio_id == GpioId::DP685)
    {
        val = s->dev->interface->read_register(REG_0x6D);

        s->buttons[BUTTON_PAGE_LOADED_SW].write((val & 0x01) == 0);
        s->buttons[BUTTON_SCAN_SW].write((val & 0x02) == 0);
    }
}

/**
 * Send shading calibration data. The buffer is considered to always hold values
 * for all the channels.
 */
void CommandSetGl841::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
                                        std::uint8_t* data, int size) const
{
    DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size);
    std::uint32_t length, x, pixels, i;

  /* old method if no SHDAREA */
    if ((dev->reg.find_reg(0x01).value & REG_0x01_SHDAREA) == 0) {
        // Note that this requires the sensor pixel offset to be exactly the same as to start
        // reading from dummy_pixel + 1 position.
        dev->interface->write_buffer(0x3c, 0x0000, data, size);
        return;
    }

  /* data is whole line, we extract only the part for the scanned area */
    length = static_cast<std::uint32_t>(size / 3);

    // turn pixel value into bytes 2x16 bits words
    pixels = dev->session.pixel_endx - dev->session.pixel_startx;
    pixels *= 4;

    // shading pixel begin is start pixel minus start pixel during shading
    // calibration. Currently only cases handled are full and half ccd resolution.
    unsigned beginpixel = dev->session.params.startx * dev->session.optical_resolution /
            dev->session.params.xres;
    beginpixel *= 4;
    beginpixel /= sensor.shading_factor;

    dev->interface->record_key_value("shading_offset", std::to_string(beginpixel));
    dev->interface->record_key_value("shading_pixels", std::to_string(pixels));
    dev->interface->record_key_value("shading_length", std::to_string(length));

  DBG(DBG_io2, "%s: using chunks of %d bytes (%d shading data pixels)\n", __func__, length,
      length/4);
    std::vector<std::uint8_t> buffer(pixels, 0);

  /* write actual shading data contigously
   * channel by channel, starting at addr 0x0000
   * */
  for(i=0;i<3;i++)
    {
      /* copy data to work buffer and process it */
          /* coefficient destination */
        std::uint8_t* ptr = buffer.data();

      /* iterate on both sensor segment, data has been averaged,
       * so is in the right order and we only have to copy it */
      for(x=0;x<pixels;x+=4)
        {
          /* coefficient source */
            std::uint8_t* src = data + x + beginpixel + i * length;
          ptr[0]=src[0];
          ptr[1]=src[1];
          ptr[2]=src[2];
          ptr[3]=src[3];

          /* next shading coefficient */
          ptr+=4;
        }

        // 0x5400 alignment for LIDE80 internal memory
        dev->interface->write_buffer(0x3c, 0x5400 * i, buffer.data(), pixels);
    }
}

bool CommandSetGl841::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
{
    (void) dev;
    return true;
}

void CommandSetGl841::wait_for_motor_stop(Genesys_Device* dev) const
{
    (void) dev;
}

void CommandSetGl841::asic_boot(Genesys_Device *dev, bool cold) const
{
    // reset ASIC in case of cold boot
    if (cold) {
        dev->interface->write_register(0x0e, 0x01);
        dev->interface->write_register(0x0e, 0x00);
    }

    gl841_init_registers(dev);

    // Write initial registers
    dev->interface->write_registers(dev->reg);

    // FIXME: 0x0b is not set, but on all other backends we do set it
    // dev->reg.remove_reg(0x0b);

    if (dev->model->model_id == ModelId::CANON_LIDE_60) {
        dev->interface->write_0x8c(0x10, 0xa4);
    }

    // FIXME: we probably don't need this
    const auto& sensor = sanei_genesys_find_sensor_any(dev);
    dev->cmd_set->set_fe(dev, sensor, AFE_INIT);
}

} // namespace gl841
} // namespace genesys
