diff options
Diffstat (limited to 'backend/genesys/gl646.cpp')
| -rw-r--r-- | backend/genesys/gl646.cpp | 3436 | 
1 files changed, 3436 insertions, 0 deletions
| diff --git a/backend/genesys/gl646.cpp b/backend/genesys/gl646.cpp new file mode 100644 index 0000000..04ee85e --- /dev/null +++ b/backend/genesys/gl646.cpp @@ -0,0 +1,3436 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003 Oliver Rauch +   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de> +   Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr> +   Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> +   Copyright (C) 2007 Luke <iceyfor@gmail.com> +   Copyright (C) 2011 Alexey Osipov <simba@lerlan.ru> for HP2400 description +                      and tuning + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "gl646.h" +#include "gl646_registers.h" +#include "test_settings.h" + +#include <vector> + +namespace genesys { +namespace gl646 { + +namespace { +constexpr unsigned CALIBRATION_LINES = 10; +} // namespace + +static void gl646_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps); + +/** + * reads value from gpio endpoint + */ +static void gl646_gpio_read(IUsbDevice& usb_dev, uint8_t* value) +{ +    DBG_HELPER(dbg); +    usb_dev.control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, GPIO_READ, INDEX, 1, value); +} + +/** + * writes the given value to gpio endpoint + */ +static void gl646_gpio_write(IUsbDevice& usb_dev, uint8_t value) +{ +    DBG_HELPER_ARGS(dbg, "(0x%02x)", value); +    usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, GPIO_WRITE, INDEX, 1, &value); +} + +/** + * writes the given value to gpio output enable endpoint + */ +static void gl646_gpio_output_enable(IUsbDevice& usb_dev, uint8_t value) +{ +    DBG_HELPER_ARGS(dbg, "(0x%02x)", value); +    usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, GPIO_OUTPUT_ENABLE, INDEX, 1, &value); +} + +/** + * stop scanner's motor + * @param dev scanner's device + */ +static void gl646_stop_motor(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    dev->interface->write_register(0x0f, 0x00); +} + +/** + * find the closest match in mode tables for the given resolution and scan mode. + * @param sensor id of the sensor + * @param required required resolution + * @param color true is color mode + * @return the closest resolution for the sensor and mode + */ +static unsigned get_closest_resolution(SensorId sensor_id, int required, unsigned channels) +{ +    unsigned best_res = 0; +    unsigned best_diff = 9600; + +    for (const auto& sensor : *s_sensors) { +        if (sensor_id != sensor.sensor_id) +            continue; + +        // exit on perfect match +        if (sensor.resolutions.matches(required) && sensor.matches_channel_count(channels)) { +            DBG(DBG_info, "%s: match found for %d\n", __func__, required); +            return required; +        } + +        // computes distance and keep mode if it is closer than previous +        if (sensor.matches_channel_count(channels)) { +            for (auto res : sensor.resolutions.resolutions()) { +                unsigned curr_diff = std::abs(static_cast<int>(res) - static_cast<int>(required)); +                if (curr_diff < best_diff) { +                    best_res = res; +                    best_diff = curr_diff; +                } +            } +        } +    } + +    DBG(DBG_info, "%s: closest match for %d is %d\n", __func__, required, best_res); +    return best_res; +} + +/** + * Returns the cksel values used by the required scan mode. + * @param sensor id of the sensor + * @param required required resolution + * @param color true is color mode + * @return cksel value for mode + */ +static int get_cksel(SensorId sensor_id, int required, unsigned channels) +{ +    for (const auto& sensor : *s_sensors) { +        // exit on perfect match +        if (sensor.sensor_id == sensor_id && sensor.resolutions.matches(required) && +            sensor.matches_channel_count(channels)) +        { +            unsigned cksel = sensor.ccd_pixels_per_system_pixel(); +            DBG(DBG_io, "%s: match found for %d (cksel=%d)\n", __func__, required, cksel); +            return cksel; +        } +    } +  DBG(DBG_error, "%s: failed to find match for %d dpi\n", __func__, required); +  /* fail safe fallback */ +  return 1; +} + +void CommandSetGl646::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* regs, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +    debug_dump(DBG_info, sensor); + +    uint32_t move = session.params.starty; + +  int i, nb; +  Motor_Master *motor = nullptr; +  uint32_t z1, z2; +  int feedl; + + +  /* for the given resolution, search for master +   * motor mode setting */ +  i = 0; +  nb = sizeof (motor_master) / sizeof (Motor_Master); +  while (i < nb) +    { +      if (dev->model->motor_id == motor_master[i].motor_id +          && motor_master[i].dpi == session.params.yres +          && motor_master[i].channels == session.params.channels) +	{ +	  motor = &motor_master[i]; +	} +      i++; +    } +  if (motor == nullptr) +    { +        throw SaneException("unable to find settings for motor %d at %d dpi, color=%d", +                            static_cast<unsigned>(dev->model->motor_id), +                            session.params.yres, session.params.channels); +    } + +  /* now we can search for the specific sensor settings */ +  i = 0; + +    // now apply values from settings to registers +    regs->set16(REG_EXPR, sensor.exposure.red); +    regs->set16(REG_EXPG, sensor.exposure.green); +    regs->set16(REG_EXPB, sensor.exposure.blue); + +    for (const auto& reg : sensor.custom_regs) { +        regs->set8(reg.address, reg.value); +    } + +  /* now generate slope tables : we are not using generate_slope_table3 yet */ +    auto slope_table1 = create_slope_table(motor->slope1, motor->slope1.max_speed_w, StepType::FULL, +                                           1, 4, get_slope_table_max_size(AsicType::GL646)); +    auto slope_table2 = create_slope_table(motor->slope2, motor->slope2.max_speed_w, StepType::FULL, +                                           1, 4, get_slope_table_max_size(AsicType::GL646)); + +  /* R01 */ +  /* now setup other registers for final scan (ie with shading enabled) */ +  /* watch dog + shading + scan enable */ +    regs->find_reg(0x01).value |= REG_0x01_DOGENB | REG_0x01_DVDSET | REG_0x01_SCAN; +    if (dev->model->is_cis) { +        regs->find_reg(0x01).value |= REG_0x01_CISSET; +    } else { +        regs->find_reg(0x01).value &= ~REG_0x01_CISSET; +    } + +  /* if device has no calibration, don't enable shading correction */ +  if (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION) +    { +        regs->find_reg(0x01).value &= ~REG_0x01_DVDSET; +    } + +    regs->find_reg(0x01).value &= ~REG_0x01_FASTMOD; +    if (motor->fastmod) { +        regs->find_reg(0x01).value |= REG_0x01_FASTMOD; +    } + +  /* R02 */ +  /* allow moving when buffer full by default */ +    if (!dev->model->is_sheetfed) { +        dev->reg.find_reg(0x02).value &= ~REG_0x02_ACDCDIS; +    } else { +        dev->reg.find_reg(0x02).value |= REG_0x02_ACDCDIS; +    } + +  /* setup motor power and direction */ +  sanei_genesys_set_motor_power(*regs, true); + +    if (has_flag(session.params.flags, ScanFlag::REVERSE)) { +        regs->find_reg(0x02).value |= REG_0x02_MTRREV; +    } else { +        regs->find_reg(0x02).value &= ~REG_0x02_MTRREV; +    } + +  /* fastfed enabled (2 motor slope tables) */ +    if (motor->fastfed) { +        regs->find_reg(0x02).value |= REG_0x02_FASTFED; +    } else { +        regs->find_reg(0x02).value &= ~REG_0x02_FASTFED; +    } + +  /* step type */ +    regs->find_reg(0x02).value &= ~REG_0x02_STEPSEL; +  switch (motor->steptype) +    { +    case StepType::FULL: +      break; +    case StepType::HALF: +      regs->find_reg(0x02).value |= 1; +      break; +    case StepType::QUARTER: +      regs->find_reg(0x02).value |= 2; +      break; +    default: +      regs->find_reg(0x02).value |= 3; +      break; +    } + +    if (dev->model->is_sheetfed) { +        regs->find_reg(0x02).value &= ~REG_0x02_AGOHOME; +    } else { +        regs->find_reg(0x02).value |= REG_0x02_AGOHOME; +    } + +  /* R03 */ +    regs->find_reg(0x03).value &= ~REG_0x03_AVEENB; +    // regs->find_reg(0x03).value |= REG_0x03_AVEENB; +    regs->find_reg(0x03).value &= ~REG_0x03_LAMPDOG; + +  /* select XPA */ +    regs->find_reg(0x03).value &= ~REG_0x03_XPASEL; +    if ((session.params.flags & ScanFlag::USE_XPA) != ScanFlag::NONE) { +        regs->find_reg(0x03).value |= REG_0x03_XPASEL; +    } +    regs->state.is_xpa_on = (session.params.flags & ScanFlag::USE_XPA) != ScanFlag::NONE; + +  /* R04 */ +  /* monochrome / color scan */ +    switch (session.params.depth) { +    case 8: +            regs->find_reg(0x04).value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +            break; +    case 16: +            regs->find_reg(0x04).value &= ~REG_0x04_LINEART; +            regs->find_reg(0x04).value |= REG_0x04_BITSET; +            break; +    } + +    sanei_genesys_set_dpihw(*regs, sensor, sensor.optical_res); + +  /* gamma enable for scans */ +    if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) { +        regs->find_reg(0x05).value |= REG_0x05_GMM14BIT; +    } + +    regs->find_reg(0x05).value &= ~REG_0x05_GMMENB; + +  /* true CIS gray if needed */ +    if (dev->model->is_cis && session.params.channels == 1 && dev->settings.true_gray) { +        regs->find_reg(0x05).value |= REG_0x05_LEDADD; +    } else { +        regs->find_reg(0x05).value &= ~REG_0x05_LEDADD; +    } + +  /* HP2400 1200dpi mode tuning */ + +    if (dev->model->sensor_id == SensorId::CCD_HP2400) { +      /* reset count of dummy lines to zero */ +        regs->find_reg(0x1e).value &= ~REG_0x1E_LINESEL; +        if (session.params.xres >= 1200) { +          /* there must be one dummy line */ +            regs->find_reg(0x1e).value |= 1 & REG_0x1E_LINESEL; + +          /* GPO12 need to be set to zero */ +          regs->find_reg(0x66).value &= ~0x20; +        } +        else +        { +          /* set GPO12 back to one */ +          regs->find_reg(0x66).value |= 0x20; +        } +    } + +  /* motor steps used */ +    unsigned forward_steps = motor->fwdbwd; +    unsigned backward_steps = motor->fwdbwd; + +    // the steps count must be different by at most 128, otherwise it's impossible to construct +    // a proper backtracking curve. We're using slightly lower limit to allow at least a minimum +    // distance between accelerations (forward_steps, backward_steps) +    if (slope_table1.steps_count > slope_table2.steps_count + 100) { +        slope_table2.steps_count += slope_table1.steps_count - 100; +    } +    if (slope_table2.steps_count > slope_table1.steps_count + 100) { +        slope_table1.steps_count += slope_table2.steps_count - 100; +    } + +    if (slope_table1.steps_count >= slope_table2.steps_count) { +        backward_steps += (slope_table1.steps_count - slope_table2.steps_count) * 2; +    } else { +        forward_steps += (slope_table2.steps_count - slope_table1.steps_count) * 2; +    } + +    if (forward_steps > 255) { +        if (backward_steps < (forward_steps - 255)) { +            throw SaneException("Can't set backtracking parameters without skipping image"); +        } +        backward_steps -= forward_steps - 255; +    } +    if (backward_steps > 255) { +        if (forward_steps < (backward_steps - 255)) { +            throw SaneException("Can't set backtracking parameters without skipping image"); +        } +        forward_steps -= backward_steps - 255; +    } + +    regs->find_reg(0x21).value = slope_table1.steps_count; +    regs->find_reg(0x24).value = slope_table2.steps_count; +    regs->find_reg(0x22).value = forward_steps; +    regs->find_reg(0x23).value = backward_steps; + +  /* CIS scanners read one line per color channel +   * since gray mode use 'add' we also read 3 channels even not in +   * color mode */ +    if (dev->model->is_cis) { +        regs->set24(REG_LINCNT, session.output_line_count * 3); +    } else { +        regs->set24(REG_LINCNT, session.output_line_count); +    } + +    regs->set16(REG_STRPIXEL, session.pixel_startx); +    regs->set16(REG_ENDPIXEL, session.pixel_endx); + +    regs->set24(REG_MAXWD, session.output_line_bytes); + +    regs->set16(REG_DPISET, session.output_resolution * session.ccd_size_divisor * +                            sensor.ccd_pixels_per_system_pixel()); +    regs->set16(REG_LPERIOD, sensor.exposure_lperiod); + +  /* move distance must be adjusted to take into account the extra lines +   * read to reorder data */ +  feedl = move; + +    if (session.num_staggered_lines + session.max_color_shift_lines > 0 && feedl != 0) { +        int feed_offset = ((session.max_color_shift_lines + session.num_staggered_lines) * dev->motor.optical_ydpi) / +                motor->dpi; +        if (feedl > feed_offset) { +            feedl = feedl - feed_offset; +        } +    } + +  /* we assume all scans are done with 2 tables */ +  /* +     feedl = feed_steps - fast_slope_steps*2 - +     (slow_slope_steps >> scan_step_type); */ +  /* but head has moved due to shading calibration => dev->scanhead_position_primary */ +  if (feedl > 0) +    { +      DBG(DBG_info, "%s: initial move=%d\n", __func__, feedl); + +      /* TODO clean up this when I'll fully understand. +       * for now, special casing each motor */ +        switch (dev->model->motor_id) { +            case MotorId::MD_5345: +                    switch (motor->dpi) { +	    case 200: +	      feedl -= 70; +	      break; +	    case 300: +	      feedl -= 70; +	      break; +	    case 400: +	      feedl += 130; +	      break; +	    case 600: +	      feedl += 160; +	      break; +	    case 1200: +	      feedl += 160; +	      break; +	    case 2400: +	      feedl += 180; +	      break; +	    default: +	      break; +	    } +	  break; +            case MotorId::HP2300: +                    switch (motor->dpi) { +	    case 75: +	      feedl -= 180; +	      break; +	    case 150: +	      feedl += 0; +	      break; +	    case 300: +	      feedl += 30; +	      break; +	    case 600: +	      feedl += 35; +	      break; +	    case 1200: +	      feedl += 45; +	      break; +	    default: +	      break; +	    } +	  break; +            case MotorId::HP2400: +                    switch (motor->dpi) { +	    case 150: +	      feedl += 150; +	      break; +	    case 300: +	      feedl += 220; +	      break; +	    case 600: +	      feedl += 260; +	      break; +	    case 1200: +	      feedl += 280; /* 300 */ +	      break; +	    case 50: +	      feedl += 0; +	      break; +	    case 100: +	      feedl += 100; +	      break; +	    default: +	      break; +	    } +	  break; + +	  /* theorical value */ +        default: { +            unsigned step_shift = static_cast<unsigned>(motor->steptype); + +	  if (motor->fastfed) +        { +                feedl = feedl - 2 * slope_table2.steps_count - +                        (slope_table1.steps_count >> step_shift); +	    } +	  else +	    { +                feedl = feedl - (slope_table1.steps_count >> step_shift); +	    } +	  break; +        } +	} +      /* security */ +      if (feedl < 0) +	feedl = 0; +    } + +  DBG(DBG_info, "%s: final move=%d\n", __func__, feedl); +    regs->set24(REG_FEEDL, feedl); + +  regs->find_reg(0x65).value = motor->mtrpwm; + +    sanei_genesys_calculate_zmod(regs->find_reg(0x02).value & REG_0x02_FASTFED, +                                 sensor.exposure_lperiod, +                                 slope_table1.table, +                                 slope_table1.steps_count, +                                  move, motor->fwdbwd, &z1, &z2); + +  /* no z1/z2 for sheetfed scanners */ +    if (dev->model->is_sheetfed) { +      z1 = 0; +      z2 = 0; +    } +    regs->set16(REG_Z1MOD, z1); +    regs->set16(REG_Z2MOD, z2); +    regs->find_reg(0x6b).value = slope_table2.steps_count; +  regs->find_reg(0x6c).value = +    (regs->find_reg(0x6c).value & REG_0x6C_TGTIME) | ((z1 >> 13) & 0x38) | ((z2 >> 16) +								   & 0x07); + +    write_control(dev, sensor, session.output_resolution); + +    // setup analog frontend +    gl646_set_fe(dev, sensor, AFE_SET, session.output_resolution); + +    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; + +    /* select color filter based on settings */ +    regs->find_reg(0x04).value &= ~REG_0x04_FILTER; +    if (session.params.channels == 1) { +        switch (session.params.color_filter) { +            case ColorFilter::RED: +                regs->find_reg(0x04).value |= 0x04; +                break; +            case ColorFilter::GREEN: +                regs->find_reg(0x04).value |= 0x08; +                break; +            case ColorFilter::BLUE: +                regs->find_reg(0x04).value |= 0x0c; +                break; +            default: +                break; +        } +    } + +    gl646_send_slope_table(dev, 0, slope_table1.table, regs->get8(0x21)); +    gl646_send_slope_table(dev, 1, slope_table2.table, regs->get8(0x6b)); +} + + +/** copy sensor specific settings */ +/* *dev  : device infos +   *regs : regiters to be set +   extended : do extended set up +   ccd_size_divisor: set up for half ccd resolution +   all registers 08-0B, 10-1D, 52-5E are set up. They shouldn't +   appear anywhere else but in register init +*/ +static void +gl646_setup_sensor (Genesys_Device * dev, const Genesys_Sensor& sensor, Genesys_Register_Set * regs) +{ +    (void) dev; +    DBG(DBG_proc, "%s: start\n", __func__); + +    for (const auto& reg_setting : sensor.custom_base_regs) { +        regs->set8(reg_setting.address, reg_setting.value); +    } +    // FIXME: all other drivers don't set exposure here +    regs_set_exposure(AsicType::GL646, *regs, sensor.exposure); + +    DBG(DBG_proc, "%s: end\n", __func__); +} + +/** + * Set all registers to default values after init + * @param dev scannerr's device to set + */ +static void +gl646_init_regs (Genesys_Device * dev) +{ +  int addr; + +  DBG(DBG_proc, "%s\n", __func__); + +    dev->reg.clear(); + +    for (addr = 1; addr <= 0x0b; addr++) +        dev->reg.init_reg(addr, 0); +    for (addr = 0x10; addr <= 0x29; addr++) +        dev->reg.init_reg(addr, 0); +    for (addr = 0x2c; addr <= 0x39; addr++) +        dev->reg.init_reg(addr, 0); +    for (addr = 0x3d; addr <= 0x3f; addr++) +        dev->reg.init_reg(addr, 0); +    for (addr = 0x52; addr <= 0x5e; addr++) +        dev->reg.init_reg(addr, 0); +    for (addr = 0x60; addr <= 0x6d; addr++) +        dev->reg.init_reg(addr, 0); + +  dev->reg.find_reg(0x01).value = 0x20 /*0x22 */ ;	/* enable shading, CCD, color, 1M */ +  dev->reg.find_reg(0x02).value = 0x30 /*0x38 */ ;	/* auto home, one-table-move, full step */ +    if (dev->model->motor_id == MotorId::MD_5345) { +        dev->reg.find_reg(0x02).value |= 0x01; // half-step +    } +    switch (dev->model->motor_id) { +        case MotorId::MD_5345: +      dev->reg.find_reg(0x02).value |= 0x01;	/* half-step */ +      break; +        case MotorId::XP200: +      /* for this sheetfed scanner, no AGOHOME, nor backtracking */ +      dev->reg.find_reg(0x02).value = 0x50; +      break; +        default: +      break; +    } +  dev->reg.find_reg(0x03).value = 0x1f /*0x17 */ ;	/* lamp on */ +  dev->reg.find_reg(0x04).value = 0x13 /*0x03 */ ;	/* 8 bits data, 16 bits A/D, color, Wolfson fe *//* todo: according to spec, 0x0 is reserved? */ +  switch (dev->model->adc_id) +    { +    case AdcId::AD_XP200: +      dev->reg.find_reg(0x04).value = 0x12; +      break; +    default: +      /* Wolfson frontend */ +      dev->reg.find_reg(0x04).value = 0x13; +      break; +    } + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +  dev->reg.find_reg(0x05).value = 0x00;	/* 12 bits gamma, disable gamma, 24 clocks/pixel */ +    sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res); + +    if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) { +        dev->reg.find_reg(0x05).value |= REG_0x05_GMM14BIT; +    } +    if (dev->model->adc_id == AdcId::AD_XP200) { +        dev->reg.find_reg(0x05).value |= 0x01;	/* 12 clocks/pixel */ +    } + +    if (dev->model->sensor_id == SensorId::CCD_HP2300) { +        dev->reg.find_reg(0x06).value = 0x00; // PWRBIT off, shading gain=4, normal AFE image capture +    } else { +        dev->reg.find_reg(0x06).value = 0x18; // PWRBIT on, shading gain=8, normal AFE image capture +    } + + +  gl646_setup_sensor(dev, sensor, &dev->reg); + +  dev->reg.find_reg(0x1e).value = 0xf0;	/* watch-dog time */ + +  switch (dev->model->sensor_id) +    { +    case SensorId::CCD_HP2300: +      dev->reg.find_reg(0x1e).value = 0xf0; +      dev->reg.find_reg(0x1f).value = 0x10; +      dev->reg.find_reg(0x20).value = 0x20; +      break; +    case SensorId::CCD_HP2400: +      dev->reg.find_reg(0x1e).value = 0x80; +      dev->reg.find_reg(0x1f).value = 0x10; +      dev->reg.find_reg(0x20).value = 0x20; +      break; +    case SensorId::CCD_HP3670: +      dev->reg.find_reg(0x19).value = 0x2a; +      dev->reg.find_reg(0x1e).value = 0x80; +      dev->reg.find_reg(0x1f).value = 0x10; +      dev->reg.find_reg(0x20).value = 0x20; +      break; +    case SensorId::CIS_XP200: +      dev->reg.find_reg(0x1e).value = 0x10; +      dev->reg.find_reg(0x1f).value = 0x01; +      dev->reg.find_reg(0x20).value = 0x50; +      break; +    default: +      dev->reg.find_reg(0x1f).value = 0x01; +      dev->reg.find_reg(0x20).value = 0x50; +      break; +    } + +  dev->reg.find_reg(0x21).value = 0x08 /*0x20 */ ;	/* table one steps number for forward slope curve of the acc/dec */ +  dev->reg.find_reg(0x22).value = 0x10 /*0x08 */ ;	/* steps number of the forward steps for start/stop */ +  dev->reg.find_reg(0x23).value = 0x10 /*0x08 */ ;	/* steps number of the backward steps for start/stop */ +  dev->reg.find_reg(0x24).value = 0x08 /*0x20 */ ;	/* table one steps number backward slope curve of the acc/dec */ +  dev->reg.find_reg(0x25).value = 0x00;	/* scan line numbers (7000) */ +  dev->reg.find_reg(0x26).value = 0x00 /*0x1b */ ; +  dev->reg.find_reg(0x27).value = 0xd4 /*0x58 */ ; +  dev->reg.find_reg(0x28).value = 0x01;	/* PWM duty for lamp control */ +  dev->reg.find_reg(0x29).value = 0xff; + +  dev->reg.find_reg(0x2c).value = 0x02;	/* set resolution (600 DPI) */ +  dev->reg.find_reg(0x2d).value = 0x58; +  dev->reg.find_reg(0x2e).value = 0x78;	/* set black&white threshold high level */ +  dev->reg.find_reg(0x2f).value = 0x7f;	/* set black&white threshold low level */ + +  dev->reg.find_reg(0x30).value = 0x00;	/* begin pixel position (16) */ +  dev->reg.find_reg(0x31).value = sensor.dummy_pixel /*0x10 */ ;	/* TGW + 2*TG_SHLD + x  */ +  dev->reg.find_reg(0x32).value = 0x2a /*0x15 */ ;	/* end pixel position (5390) */ +  dev->reg.find_reg(0x33).value = 0xf8 /*0x0e */ ;	/* TGW + 2*TG_SHLD + y   */ +  dev->reg.find_reg(0x34).value = sensor.dummy_pixel; +  dev->reg.find_reg(0x35).value = 0x01 /*0x00 */ ;	/* set maximum word size per line, for buffer full control (10800) */ +  dev->reg.find_reg(0x36).value = 0x00 /*0x2a */ ; +  dev->reg.find_reg(0x37).value = 0x00 /*0x30 */ ; +  dev->reg.find_reg(0x38).value = 0x2a; // line period (exposure time = 11000 pixels) */ +  dev->reg.find_reg(0x39).value = 0xf8; +  dev->reg.find_reg(0x3d).value = 0x00;	/* set feed steps number of motor move */ +  dev->reg.find_reg(0x3e).value = 0x00; +  dev->reg.find_reg(0x3f).value = 0x01 /*0x00 */ ; + +  dev->reg.find_reg(0x60).value = 0x00;	/* Z1MOD, 60h:61h:(6D b5:b3), remainder for start/stop */ +  dev->reg.find_reg(0x61).value = 0x00;	/* (21h+22h)/LPeriod */ +  dev->reg.find_reg(0x62).value = 0x00;	/* Z2MODE, 62h:63h:(6D b2:b0), remainder for start scan */ +  dev->reg.find_reg(0x63).value = 0x00;	/* (3Dh+3Eh+3Fh)/LPeriod for one-table mode,(21h+1Fh)/LPeriod */ +  dev->reg.find_reg(0x64).value = 0x00;	/* motor PWM frequency */ +  dev->reg.find_reg(0x65).value = 0x00;	/* PWM duty cycle for table one motor phase (63 = max) */ +    if (dev->model->motor_id == MotorId::MD_5345) { +        // PWM duty cycle for table one motor phase (63 = max) +        dev->reg.find_reg(0x65).value = 0x02; +    } + +    for (const auto& reg : dev->gpo.regs) { +        dev->reg.set8(reg.address, reg.value); +    } + +    switch (dev->model->motor_id) { +        case MotorId::HP2300: +        case MotorId::HP2400: +      dev->reg.find_reg(0x6a).value = 0x7f;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6b).value = 0x78;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6d).value = 0x7f; +      break; +        case MotorId::MD_5345: +      dev->reg.find_reg(0x6a).value = 0x42;	/* table two fast moving step type, PWM duty for table two */ +      dev->reg.find_reg(0x6b).value = 0xff;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6d).value = 0x41;	/* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */ +      break; +        case MotorId::XP200: +      dev->reg.find_reg(0x6a).value = 0x7f;	/* table two fast moving step type, PWM duty for table two */ +      dev->reg.find_reg(0x6b).value = 0x08;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6d).value = 0x01;	/* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */ +      break; +        case MotorId::HP3670: +      dev->reg.find_reg(0x6a).value = 0x41;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6b).value = 0xc8;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6d).value = 0x7f; +      break; +        default: +      dev->reg.find_reg(0x6a).value = 0x40;	/* table two fast moving step type, PWM duty for table two */ +      dev->reg.find_reg(0x6b).value = 0xff;	/* table two steps number for acc/dec */ +      dev->reg.find_reg(0x6d).value = 0x01;	/* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */ +      break; +    } +  dev->reg.find_reg(0x6c).value = 0x00;	/* peroid times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE (one period time) */ +} + + +// Send slope table for motor movement slope_table in machine byte order +static void gl646_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)=%d .. %d", table_nr, steps, slope_table[0], +                    slope_table[steps - 1]); +  int dpihw; +  int start_address; + +  dpihw = dev->reg.find_reg(0x05).value >> 6; + +  if (dpihw == 0)		/* 600 dpi */ +    start_address = 0x08000; +  else if (dpihw == 1)		/* 1200 dpi */ +    start_address = 0x10000; +  else if (dpihw == 2)		/* 2400 dpi */ +    start_address = 0x1f800; +    else { +        throw SaneException("Unexpected dpihw"); +    } + +  std::vector<uint8_t> table(steps * 2); +  for (int i = 0; i < steps; i++) +    { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } +    dev->interface->write_buffer(0x3c, start_address + table_nr * 0x100, table.data(), steps * 2); +} + +// Set values of Analog Device type frontend +static void gl646_set_ad_fe(Genesys_Device* dev, uint8_t set) +{ +    DBG_HELPER(dbg); +  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; + +        // 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)); +    } +  if (set == AFE_SET) +    { +        for (i = 0; i < 3; i++) { +            dev->interface->write_fe_register(0x02 + i, dev->frontend.get_gain(i)); +        } +        for (i = 0; i < 3; i++) { +            dev->interface->write_fe_register(0x05 + i, dev->frontend.get_offset(i)); +        } +    } +  /* +     if (set == AFE_POWER_SAVE) +     { +        dev->interface->write_fe_register(0x00, dev->frontend.reg[0] | 0x04); +     } */ +} + +/** set up analog frontend + * set up analog frontend + * @param dev device to set up + * @param set action from AFE_SET, AFE_INIT and AFE_POWERSAVE + * @param dpi resolution of the scan since it affects settings + */ +static void gl646_wm_hp3670(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set, +                            unsigned dpi) +{ +    DBG_HELPER(dbg); +  int i; + +  switch (set) +    { +    case AFE_INIT: +        dev->interface->write_fe_register(0x04, 0x80); +        dev->interface->sleep_ms(200); +    dev->interface->write_register(0x50, 0x00); +      dev->frontend = dev->frontend_initial; +        dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01)); +        dev->interface->write_fe_register(0x02, dev->frontend.regs.get_value(0x02)); +        gl646_gpio_output_enable(dev->interface->get_usb_device(), 0x07); +      break; +    case AFE_POWER_SAVE: +        dev->interface->write_fe_register(0x01, 0x06); +        dev->interface->write_fe_register(0x06, 0x0f); +            return; +      break; +    default:			/* AFE_SET */ +      /* mode setup */ +      i = dev->frontend.regs.get_value(0x03); +      if (dpi > sensor.optical_res / 2) +	{ +      /* fe_reg_0x03 must be 0x12 for 1200 dpi in WOLFSON_HP3670. +       * WOLFSON_HP2400 in 1200 dpi mode works well with +	   * fe_reg_0x03 set to 0x32 or 0x12 but not to 0x02 */ +	  i = 0x12; +	} +        dev->interface->write_fe_register(0x03, i); +      /* offset and sign (or msb/lsb ?) */ +        for (i = 0; i < 3; i++) { +            dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i)); +            dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i)); +        } + +        // gain +        for (i = 0; i < 3; i++) { +            dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i)); +        } +    } +} + +/** Set values of analog frontend + * @param dev device to set + * @param set action to execute + * @param dpi dpi to setup the AFE + */ +static void gl646_set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set, int dpi) +{ +    DBG_HELPER_ARGS(dbg, "%s,%d", set == AFE_INIT ? "init" : +                                  set == AFE_SET ? "set" : +                                  set == AFE_POWER_SAVE ? "powersave" : "huh?", dpi); +  int i; +  uint8_t val; + +  /* Analog Device type frontend */ +    uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET; +    if (frontend_type == 0x02) { +        gl646_set_ad_fe(dev, set); +        return; +    } + +  /* Wolfson type frontend */ +    if (frontend_type != 0x03) { +        throw SaneException("unsupported frontend type %d", frontend_type); +    } + +  /* per frontend function to keep code clean */ +  switch (dev->model->adc_id) +    { +    case AdcId::WOLFSON_HP3670: +    case AdcId::WOLFSON_HP2400: +            gl646_wm_hp3670(dev, sensor, set, dpi); +            return; +    default: +      DBG(DBG_proc, "%s(): using old method\n", __func__); +      break; +    } + +  /* initialize analog frontend */ +  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; + +        // reset only done on init +        dev->interface->write_fe_register(0x04, 0x80); + +      /* enable GPIO for some models */ +        if (dev->model->sensor_id == SensorId::CCD_HP2300) { +	  val = 0x07; +            gl646_gpio_output_enable(dev->interface->get_usb_device(), val); +	} +        return; +    } + +    // set fontend to power saving mode +    if (set == AFE_POWER_SAVE) { +        dev->interface->write_fe_register(0x01, 0x02); +        return; +    } + +  /* here starts AFE_SET */ +  /* 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_HP3670 +     && 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)); +  } + +    // start with reg3 +    dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x03)); + +  switch (dev->model->sensor_id) +    { +    default: +            for (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)); +            } +      break; +      /* just can't have it to work .... +         case SensorId::CCD_HP2300: +         case SensorId::CCD_HP2400: +         case SensorId::CCD_HP3670: + +        dev->interface->write_fe_register(0x23, dev->frontend.get_offset(1)); +        dev->interface->write_fe_register(0x28, dev->frontend.get_gain(1)); +         break; */ +    } + +    // end with reg1 +    dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01)); +} + +/** Set values of analog frontend + * this this the public interface, the gl646 as to use one more + * parameter to work effectively, hence the redirection + * @param dev device to set + * @param set action to execute + */ +void CommandSetGl646::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    gl646_set_fe(dev, sensor, set, dev->settings.yres); +} + +/** + * enters or leaves power saving mode + * limited to AFE for now. + * @param dev scanner's device + * @param enable true to enable power saving, false to leave it + */ +void CommandSetGl646::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) +    { +        // gl646_set_fe(dev, sensor, AFE_POWER_SAVE); +    } +  else +    { +      gl646_set_fe(dev, sensor, AFE_INIT, 0); +    } +} + +void CommandSetGl646::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    DBG_HELPER_ARGS(dbg, "delay = %d", delay); +  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(0x38, 0x00); // line period low +  local_reg.init_reg(0x39, 0x00); //line period high +  local_reg.init_reg(0x6c, 0x00); // period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE + +  if (!delay) +    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.get8(0x03) & 0xf0) | 0x09;	/* enable lampdog and set lamptime = 1 */ +  else +    local_reg.find_reg(0x03).value = (local_reg.get8(0x03) & 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.get8(0x03) & 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(0x6c).value |= tgtime << 6; +  exposure_time /= rate; + +  if (exposure_time > 65535) +    exposure_time = 65535; + +  local_reg.find_reg(0x38).value = exposure_time / 256; +  local_reg.find_reg(0x39).value = exposure_time & 255; + +    dev->interface->write_registers(local_reg); +} + + +/** + * loads document into scanner + * currently only used by XP200 + * bit2 (0x04) of gpio is paper event (document in/out) on XP200 + * HOMESNR is set if no document in front of sensor, the sequence of events is + * paper event -> document is in the sheet feeder + * HOMESNR becomes 0 -> document reach sensor + * HOMESNR becomes 1 ->document left sensor + * paper event -> document is out + */ +void CommandSetGl646::load_document(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); + +  // FIXME: sequential not really needed in this case +  Genesys_Register_Set regs(Genesys_Register_Set::SEQUENTIAL); +    unsigned count; + +  /* no need to load document is flatbed scanner */ +    if (!dev->model->is_sheetfed) { +      DBG(DBG_proc, "%s: nothing to load\n", __func__); +      DBG(DBG_proc, "%s: end\n", __func__); +      return; +    } + +    auto status = scanner_read_status(*dev); + +    // home sensor is set if a document is inserted +    if (status.is_at_home) { +      /* if no document, waits for a paper event to start loading */ +      /* with a 60 seconde minutes timeout                        */ +      count = 0; +        std::uint8_t val = 0; +        do { +            gl646_gpio_read(dev->interface->get_usb_device(), &val); + +	  DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, val); +	  if ((val & 0x04) != 0x04) +	    { +              DBG(DBG_warn, "%s: no paper detected\n", __func__); +	    } +            dev->interface->sleep_ms(200); +            count++; +        } +      while (((val & 0x04) != 0x04) && (count < 300));	/* 1 min time out */ +      if (count == 300) +	{ +        throw SaneException(SANE_STATUS_NO_DOCS, "timeout waiting for document"); +    } +    } + +  /* set up to fast move before scan then move until document is detected */ +  regs.init_reg(0x01, 0x90); + +  /* AGOME, 2 slopes motor moving */ +  regs.init_reg(0x02, 0x79); + +  /* motor feeding steps to 0 */ +  regs.init_reg(0x3d, 0); +  regs.init_reg(0x3e, 0); +  regs.init_reg(0x3f, 0); + +  /* 50 fast moving steps */ +  regs.init_reg(0x6b, 50); + +  /* set GPO */ +  regs.init_reg(0x66, 0x30); + +  /* stesp NO */ +  regs.init_reg(0x21, 4); +  regs.init_reg(0x22, 1); +  regs.init_reg(0x23, 1); +  regs.init_reg(0x24, 4); + +  /* generate slope table 2 */ +    auto slope_table = create_slope_table(MotorSlope::create_from_steps(6000, 2400, 50), 2400, +                                          StepType::FULL, 1, 4, +                                          get_slope_table_max_size(AsicType::GL646)); +    // document loading: +    // send regs +    // start motor +    // wait e1 status to become e0 +    gl646_send_slope_table(dev, 1, slope_table.table, slope_table.steps_count); + +    dev->interface->write_registers(regs); + +    scanner_start_action(*dev, true); + +  count = 0; +  do +    { +        status = scanner_read_status(*dev); +        dev->interface->sleep_ms(200); +      count++; +    } while (status.is_motor_enabled && (count < 300)); + +  if (count == 300) +    { +      throw SaneException(SANE_STATUS_JAMMED, "can't load document"); +    } + +  /* when loading OK, document is here */ +    dev->document = true; + +  /* set up to idle */ +  regs.set8(0x02, 0x71); +  regs.set8(0x3f, 1); +  regs.set8(0x6b, 8); +    dev->interface->write_registers(regs); +} + +/** + * detects end of document and adjust current scan + * to take it into account + * used by sheetfed scanners + */ +void CommandSetGl646::detect_document_end(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +    std::uint8_t gpio; +    unsigned int bytes_left; + +    // test for document presence +    scanner_read_print_status(*dev); + +    gl646_gpio_read(dev->interface->get_usb_device(), &gpio); +  DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, gpio); + +  /* detect document event. There one event when the document go in, +   * then another when it leaves */ +    if (dev->document && (gpio & 0x04) && (dev->total_bytes_read > 0)) { +      DBG(DBG_info, "%s: no more document\n", __func__); +        dev->document = false; + +      /* adjust number of bytes to read: +       * total_bytes_to_read is the number of byte to send to frontend +       * total_bytes_read is the number of bytes sent to frontend +       * read_bytes_left is the number of bytes to read from the scanner +       */ +      DBG(DBG_io, "%s: total_bytes_to_read=%zu\n", __func__, dev->total_bytes_to_read); +      DBG(DBG_io, "%s: total_bytes_read   =%zu\n", __func__, dev->total_bytes_read); + +        // amount of data available from scanner is what to scan +        sanei_genesys_read_valid_words(dev, &bytes_left); + +        unsigned lines_in_buffer = bytes_left / dev->session.output_line_bytes_raw; + +        // we add the number of lines needed to read the last part of the document in +        unsigned lines_offset = static_cast<unsigned>( +                (dev->model->y_offset * dev->session.params.yres) / MM_PER_INCH); + +        unsigned remaining_lines = lines_in_buffer + lines_offset; + +        bytes_left = remaining_lines * dev->session.output_line_bytes_raw; + +        if (bytes_left < dev->get_pipeline_source().remaining_bytes()) { +            dev->get_pipeline_source().set_remaining_bytes(bytes_left); +            dev->total_bytes_to_read = dev->total_bytes_read + bytes_left; +        } +      DBG(DBG_io, "%s: total_bytes_to_read=%zu\n", __func__, dev->total_bytes_to_read); +      DBG(DBG_io, "%s: total_bytes_read   =%zu\n", __func__, dev->total_bytes_read); +    } +} + +/** + * eject document from the feeder + * currently only used by XP200 + * TODO we currently rely on AGOHOME not being set for sheetfed scanners, + * maybe check this flag in eject to let the document being eject automaticaly + */ +void CommandSetGl646::eject_document(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); + +  // FIXME: SEQUENTIAL not really needed in this case +  Genesys_Register_Set regs((Genesys_Register_Set::SEQUENTIAL)); +    unsigned count; +    std::uint8_t gpio; + +  /* at the end there will be noe more document */ +    dev->document = false; + +    // first check for document event +    gl646_gpio_read(dev->interface->get_usb_device(), &gpio); + +  DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, gpio); + +    // test status : paper event + HOMESNR -> no more doc ? +    auto status = scanner_read_status(*dev); + +    // home sensor is set when document is inserted +    if (status.is_at_home) { +        dev->document = false; +      DBG(DBG_info, "%s: no more document to eject\n", __func__); +      DBG(DBG_proc, "%s: end\n", __func__); +      return; +    } + +    // there is a document inserted, eject it +    dev->interface->write_register(0x01, 0xb0); + +  /* wait for motor to stop */ +    do { +        dev->interface->sleep_ms(200); +        status = scanner_read_status(*dev); +    } +    while (status.is_motor_enabled); + +  /* set up to fast move before scan then move until document is detected */ +  regs.init_reg(0x01, 0xb0); + +  /* AGOME, 2 slopes motor moving , eject 'backward' */ +  regs.init_reg(0x02, 0x5d); + +  /* motor feeding steps to 119880 */ +  regs.init_reg(0x3d, 1); +  regs.init_reg(0x3e, 0xd4); +  regs.init_reg(0x3f, 0x48); + +  /* 60 fast moving steps */ +  regs.init_reg(0x6b, 60); + +  /* set GPO */ +  regs.init_reg(0x66, 0x30); + +  /* stesp NO */ +  regs.init_reg(0x21, 4); +  regs.init_reg(0x22, 1); +  regs.init_reg(0x23, 1); +  regs.init_reg(0x24, 4); + +  /* generate slope table 2 */ +    auto slope_table = create_slope_table(MotorSlope::create_from_steps(10000, 1600, 60), 1600, +                                          StepType::FULL, 1, 4, +                                          get_slope_table_max_size(AsicType::GL646)); +    // document eject: +    // send regs +    // start motor +    // wait c1 status to become c8 : HOMESNR and ~MOTFLAG +    gl646_send_slope_table(dev, 1, slope_table.table, slope_table.steps_count); + +    dev->interface->write_registers(regs); + +    scanner_start_action(*dev, true); + +  /* loop until paper sensor tells paper is out, and till motor is running */ +  /* use a 30 timeout */ +  count = 0; +    do { +        status = scanner_read_status(*dev); + +        dev->interface->sleep_ms(200); +      count++; +    } while (!status.is_at_home && (count < 150)); + +    // read GPIO on exit +    gl646_gpio_read(dev->interface->get_usb_device(), &gpio); + +  DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, gpio); +} + +// Send the low-level scan command +void CommandSetGl646::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); + +    local_reg.init_reg(0x03, reg->get8(0x03)); +    local_reg.init_reg(0x01, reg->get8(0x01) | REG_0x01_SCAN); + +    if (start_motor) { +        local_reg.init_reg(0x0f, 0x01); +    } else { +        local_reg.init_reg(0x0f, 0x00); // do not start motor yet +    } + +    dev->interface->write_registers(local_reg); + +    dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +} + + +// Send the stop scan command +static void end_scan_impl(Genesys_Device* dev, Genesys_Register_Set* reg, bool check_stop, +                          bool eject) +{ +    DBG_HELPER_ARGS(dbg, "check_stop = %d, eject = %d", check_stop, eject); + +    scanner_stop_action_no_move(*dev, *reg); + +    unsigned wait_limit_seconds = 30; + +  /* for sheetfed scanners, we may have to eject document */ +    if (dev->model->is_sheetfed) { +        if (eject && dev->document) { +            dev->cmd_set->eject_document(dev); +        } +        wait_limit_seconds = 3; +    } + +    if (is_testing_mode()) { +        return; +    } + +    dev->interface->sleep_ms(100); + +    if (check_stop) { +        for (unsigned i = 0; i < wait_limit_seconds * 10; i++) { +            if (scanner_is_motor_stopped(*dev)) { +                return; +            } + +            dev->interface->sleep_ms(100); +        } +        throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor"); +    } +} + +// Send the stop scan command +void CommandSetGl646::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg, +                               bool check_stop) const +{ +    end_scan_impl(dev, reg, check_stop, false); +} + +/** + * parks head + * @param dev scanner's device + * @param wait_until_home true if the function waits until head parked + */ +void CommandSetGl646::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    DBG_HELPER_ARGS(dbg, "wait_until_home = %d\n", wait_until_home); +  int i; +  int loop = 0; + +    auto status = scanner_read_status(*dev); + +    if (status.is_at_home) { +      DBG(DBG_info, "%s: end since already at home\n", __func__); +        dev->set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +  /* stop motor if needed */ +    if (status.is_motor_enabled) { +        gl646_stop_motor(dev); +        dev->interface->sleep_ms(200); +    } + +  /* when scanhead is moving then wait until scanhead stops or timeout */ +  DBG(DBG_info, "%s: ensuring that motor is off\n", __func__); +    for (i = 400; i > 0; i--) { +        // do not wait longer than 40 seconds, count down to get i = 0 when busy + +        status = scanner_read_status(*dev); + +        if (!status.is_motor_enabled && status.is_at_home) { +            DBG(DBG_info, "%s: already at home and not moving\n", __func__); +            dev->set_head_pos_zero(ScanHeadId::PRIMARY); +            return; +        } +        if (!status.is_motor_enabled) { +            break; +        } + +        dev->interface->sleep_ms(100); +    } + +  if (!i)			/* the loop counted down to 0, scanner still is busy */ +    { +        dev->set_head_pos_unknown(); +        throw SaneException(SANE_STATUS_DEVICE_BUSY, "motor is still on: device busy"); +    } + +    // setup for a backward scan of 65535 steps, with no actual data reading +    auto resolution = sanei_genesys_get_lowest_dpi(dev); + +    const auto& sensor = sanei_genesys_find_sensor(dev, resolution, 3, +                                                   dev->model->default_method); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = 65535; +    session.params.pixels = 600; +    session.params.requested_pixels = 600; +    session.params.lines = 1; +    session.params.depth = 8; +    session.params.channels = 3; +    session.params.scan_method = dev->model->default_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::USE_XCORRECTION | +                            ScanFlag::REVERSE; +    if (dev->model->default_method == ScanMethod::TRANSPARENCY) { +        session.params.flags |= ScanFlag::USE_XPA; +    } +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); + +  /* backward , no actual data scanned TODO more setup flags to avoid this register manipulations ? */ +    regs_set_optical_off(dev->model->asic_type, dev->reg); + +    // sets frontend +    gl646_set_fe(dev, sensor, AFE_SET, resolution); + +  /* write scan registers */ +    try { +        dev->interface->write_registers(dev->reg); +    } catch (...) { +        DBG(DBG_error, "%s: failed to bulk write registers\n", __func__); +    } + +  /* registers are restored to an iddl state, give up if no head to park */ +    if (dev->model->is_sheetfed) { +      DBG(DBG_proc, "%s: end \n", __func__); +      return; +    } + +    // starts scan +    { +        // this is effectively the same as dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true); +        // except that we don't modify the head position calculations + +        // FIXME: SEQUENTIAL not really needed in this case +        Genesys_Register_Set scan_local_reg(Genesys_Register_Set::SEQUENTIAL); + +        scan_local_reg.init_reg(0x03, dev->reg.get8(0x03)); +        scan_local_reg.init_reg(0x01, dev->reg.get8(0x01) | REG_0x01_SCAN); +        scan_local_reg.init_reg(0x0f, 0x01); + +        dev->interface->write_registers(scan_local_reg); +    } + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("move_back_home"); +        dev->set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +  /* loop until head parked */ +  if (wait_until_home) +    { +      while (loop < 300)		/* do not wait longer then 30 seconds */ +	{ +            auto status = scanner_read_status(*dev); + +            if (status.is_at_home) { +	      DBG(DBG_info, "%s: reached home position\n", __func__); +	      DBG(DBG_proc, "%s: end\n", __func__); +                dev->interface->sleep_ms(500); +                dev->set_head_pos_zero(ScanHeadId::PRIMARY); +                return; +            } +            dev->interface->sleep_ms(100); +            ++loop; +        } + +        // when we come here then the scanner needed too much time for this, so we better +        // stop the motor +        catch_all_exceptions(__func__, [&](){ gl646_stop_motor (dev); }); +        catch_all_exceptions(__func__, [&](){ end_scan_impl(dev, &dev->reg, true, false); }); +        dev->set_head_pos_unknown(); +        throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home"); +    } + + +  DBG(DBG_info, "%s: scanhead is still moving\n", __func__); +} + +/** + * Automatically set top-left edge of the scan area by scanning an + * area at 300 dpi from very top of scanner + * @param dev  device stucture describing the scanner + */ +void CommandSetGl646::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  Genesys_Settings settings; +  unsigned int resolution, x, y; + +  /* we scan at 300 dpi */ +  resolution = get_closest_resolution(dev->model->sensor_id, 300, 1); + +    // 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, resolution, 1, +                                                   dev->model->default_method); + +  /* fill settings for a gray level scan */ +  settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::GRAY; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_x = 0; +  settings.tl_y = 0; +  settings.pixels = 600; +    settings.requested_pixels = settings.pixels; +  settings.lines = dev->model->search_lines; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +    // scan the desired area +    std::vector<uint8_t> data; +    simple_scan(dev, sensor, settings, true, true, false, data, "search_start_position"); + +    // handle stagger case : reorder gray data and thus loose some lines +    auto staggered_lines = dev->session.num_staggered_lines; +    if (staggered_lines > 0) { +        DBG(DBG_proc, "%s: 'un-staggering'\n", __func__); +        for (y = 0; y < settings.lines - staggered_lines; y++) { +            /* one point out of 2 is 'unaligned' */ +            for (x = 0; x < settings.pixels; x += 2) +        { +                data[y * settings.pixels + x] = data[(y + staggered_lines) * settings.pixels + x]; +        } +          } +        /* correct line number */ +        settings.lines -= staggered_lines; +    } + +    if (DBG_LEVEL >= DBG_data) +      { +        sanei_genesys_write_pnm_file("gl646_search_position.pnm", data.data(), settings.depth, 1, +                                     settings.pixels, settings.lines); +      } + +    // now search reference points on the data +    for (auto& sensor_update : +            sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method)) +    { +        sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, +                                             resolution, settings.pixels, settings.lines); +    } +} + +/** + * internally overriden during effective calibration + * sets up register for coarse gain calibration + */ +void CommandSetGl646::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    (void) dev; +    (void) sensor; +    (void) regs; +} + + +/** + * init registers for shading calibration + * we assume that scanner's head is on an area suiting shading calibration. + * We scan a full scan width area by the shading line number for the device + * at either at full sensor's resolution or half depending upon ccd_size_divisor + * @param dev scanner's device + */ +void CommandSetGl646::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    (void) regs; +  Genesys_Settings settings; +  int cksel = 1; + +  /* fill settings for scan : always a color scan */ +  int channels = 3; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, dev->settings.xres, channels, +                                                         dev->settings.scan_method); + +    unsigned ccd_size_divisor = calib_sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres); + +  settings.scan_method = dev->settings.scan_method; +  settings.scan_mode = dev->settings.scan_mode; +    if (!dev->model->is_cis) { +      // FIXME: always a color scan, but why don't we set scan_mode to COLOR_SINGLE_PASS always? +      settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    } +  settings.xres = sensor.optical_res / ccd_size_divisor; +  cksel = get_cksel(dev->model->sensor_id, dev->settings.xres, channels); +  settings.xres = settings.xres / cksel; +  settings.yres = settings.xres; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = (calib_sensor.sensor_pixels * settings.xres) / calib_sensor.optical_res; +    settings.requested_pixels = settings.pixels; +  dev->calib_lines = dev->model->shading_lines; +  settings.lines = dev->calib_lines * (3 - ccd_size_divisor); +  settings.depth = 16; +  settings.color_filter = dev->settings.color_filter; + +  settings.disable_interpolation = dev->settings.disable_interpolation; +  settings.threshold = dev->settings.threshold; + +    // we don't want top offset, but we need right margin to be the same than the one for the final +    // scan +    setup_for_scan(dev, calib_sensor, &dev->reg, settings, true, false, false, false); + +  /* used when sending shading calibration data */ +  dev->calib_pixels = settings.pixels; +    dev->calib_channels = dev->session.params.channels; +    if (!dev->model->is_cis) { +      dev->calib_channels = 3; +    } + +  /* no shading */ +    dev->reg.find_reg(0x01).value &= ~REG_0x01_DVDSET; +    dev->reg.find_reg(0x02).value |= REG_0x02_ACDCDIS;	/* ease backtracking */ +    dev->reg.find_reg(0x02).value &= ~(REG_0x02_FASTFED | REG_0x02_AGOHOME); +    dev->reg.find_reg(0x05).value &= ~REG_0x05_GMMENB; +  sanei_genesys_set_motor_power(dev->reg, false); + +  /* TODO another flag to setup regs ? */ +  /* enforce needed LINCNT, getting rid of extra lines for color reordering */ +    if (!dev->model->is_cis) { +        dev->reg.set24(REG_LINCNT, dev->calib_lines); +    } else { +        dev->reg.set24(REG_LINCNT, dev->calib_lines * 3); +    } + +  /* copy reg to calib_reg */ +  dev->calib_reg = dev->reg; + +  DBG(DBG_info, "%s:\n\tdev->settings.xres=%d\n\tdev->settings.yres=%d\n", __func__, +      dev->settings.xres, dev->settings.yres); +} + +bool CommandSetGl646::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    return dev->is_head_pos_known(ScanHeadId::PRIMARY) && +            dev->head_pos(ScanHeadId::PRIMARY) && +            dev->settings.scan_method == ScanMethod::FLATBED; +} + +/** + * set up registers for the actual scan. The scan's parameters are given + * through the device settings. It allocates the scan buffers. + */ +void CommandSetGl646::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); + +    debug_dump(DBG_info, dev->settings); + +    ScanSession session = calculate_scan_session(dev, sensor, dev->settings); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); + +  /* gamma is only enabled at final scan time */ +    if (dev->settings.depth < 16) { +        dev->reg.find_reg(0x05).value |= REG_0x05_GMMENB; +    } +} + +/** + * set up registers for the actual scan. The scan's parameters are given + * through the device settings. It allocates the scan buffers. + * @param dev scanner's device + * @param regs     registers to set up + * @param settings settings of scan + * @param split true if move to scan area is split from scan, false is + *              scan first moves to area + * @param xcorrection take x geometry correction into account (fixed and detected offsets) + * @param ycorrection take y geometry correction into account + */ +static void setup_for_scan(Genesys_Device* dev, +                           const Genesys_Sensor& sensor, +                           Genesys_Register_Set*regs, +                           Genesys_Settings settings, +                           bool split, +                           bool xcorrection, +                           bool ycorrection, +                           bool reverse) +{ +    DBG_HELPER(dbg); + +    debug_dump(DBG_info, dev->settings); + +    // compute distance to move +    float move = 0; +    // XXX STEF XXX MD5345 -> optical_ydpi, other base_ydpi => half/full step ? */ +    if (!split) { +        if (!dev->model->is_sheetfed) { +            if (ycorrection) { +                move = static_cast<float>(dev->model->y_offset); +            } + +            // add tl_y to base movement +        } +        move += static_cast<float>(settings.tl_y); + +        if (move < 0) { +            DBG(DBG_error, "%s: overriding negative move value %f\n", __func__, move); +            move = 0; +        } +    } +    move = static_cast<float>((move * dev->motor.optical_ydpi) / MM_PER_INCH); +    DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +    float start = static_cast<float>(settings.tl_x); +    if (xcorrection) { +        if (settings.scan_method == ScanMethod::FLATBED) { +            start += static_cast<float>(dev->model->x_offset); +        } else { +            start += static_cast<float>(dev->model->x_offset_ta); +        } +    } +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    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 = dev->settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; +    if (settings.scan_method == ScanMethod::TRANSPARENCY) { +        session.params.flags |= ScanFlag::USE_XPA; +    } +    if (xcorrection) { +        session.params.flags |= ScanFlag::USE_XCORRECTION; +    } +    if (reverse) { +        session.params.flags |= ScanFlag::REVERSE; +    } +    compute_session(dev, session, sensor); + +    dev->cmd_set->init_regs_for_scan_session(dev, sensor, regs, session); +} + +/** + * this function send gamma table to ASIC + */ +void CommandSetGl646::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  int size; +  int address; +  int bits; + +  /* gamma table size */ +  if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) +    { +      size = 16384; +      bits = 14; +    } +  else +    { +      size = 4096; +      bits = 12; +    } + +  /* allocate temporary gamma tables: 16 bits words, 3 channels */ +  std::vector<uint8_t> gamma(size * 2 * 3); + +    sanei_genesys_generate_gamma_buffer(dev, sensor, bits, size-1, size, gamma.data()); + +  /* table address */ +  switch (dev->reg.find_reg(0x05).value >> 6) +    { +    case 0:			/* 600 dpi */ +      address = 0x09000; +      break; +    case 1:			/* 1200 dpi */ +      address = 0x11000; +      break; +    case 2:			/* 2400 dpi */ +      address = 0x20000; +      break; +    default: +            throw SaneException("invalid dpi"); +    } + +    dev->interface->write_buffer(0x3c, address, gamma.data(), size * 2 * 3); +} + +/** @brief this function does the led calibration. + * this function does the led calibration by scanning one line of the calibration + * area below scanner's top on white strip. The scope of this function is + * currently limited to the XP200 + */ +SensorExposure CommandSetGl646::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    (void) regs; +  int total_size; +  unsigned int i, j; +  int val; +  int avg[3], avga, avge; +  int turn; +  uint16_t expr, expg, expb; +  Genesys_Settings settings; +  SANE_Int resolution; + +    unsigned channels = dev->settings.get_channels(); + +  /* get led calibration resolution */ +  if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +    { +      settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    } +  else +    { +      settings.scan_mode = ScanColorMode::GRAY; +    } +  resolution = get_closest_resolution(dev->model->sensor_id, sensor.optical_res, channels); + +  /* offset calibration is always done in color mode */ +    settings.scan_method = dev->model->default_method; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = (sensor.sensor_pixels * resolution) / sensor.optical_res; +    settings.requested_pixels = settings.pixels; +  settings.lines = 1; +  settings.depth = 16; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  /* colors * bytes_per_color * scan lines */ +  total_size = settings.pixels * channels * 2 * 1; + +  std::vector<uint8_t> line(total_size); + +/* +   we try to get equal bright leds here: + +   loop: +     average per color +     adjust exposure times + */ +  expr = sensor.exposure.red; +  expg = sensor.exposure.green; +  expb = sensor.exposure.blue; + +  turn = 0; + +    auto calib_sensor = sensor; + +    bool acceptable = false; +    do { +        calib_sensor.exposure.red = expr; +        calib_sensor.exposure.green = expg; +        calib_sensor.exposure.blue = expb; + +      DBG(DBG_info, "%s: starting first line reading\n", __func__); + +        simple_scan(dev, calib_sensor, settings, false, true, false, line, "led_calibration"); + +        if (is_testing_mode()) { +            return calib_sensor.exposure; +        } + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[30]; +            std::snprintf(fn, 30, "gl646_led_%02d.pnm", turn); +          sanei_genesys_write_pnm_file(fn, line.data(), 16, channels, settings.pixels, 1); +	} + +        acceptable = true; + +      for (j = 0; j < channels; j++) +	{ +	  avg[j] = 0; +	  for (i = 0; i < settings.pixels; i++) +	    { +	      if (dev->model->is_cis) +		val = +		  line[i * 2 + j * 2 * settings.pixels + 1] * 256 + +		  line[i * 2 + j * 2 * settings.pixels]; +	      else +		val = +		  line[i * 2 * channels + 2 * j + 1] * 256 + +		  line[i * 2 * channels + 2 * j]; +	      avg[j] += val; +	    } + +	  avg[j] /= settings.pixels; +	} + +      DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +        acceptable = true; + +      if (!acceptable) +	{ +	  avga = (avg[0] + avg[1] + avg[2]) / 3; +	  expr = (expr * avga) / avg[0]; +	  expg = (expg * avga) / avg[1]; +	  expb = (expb * avga) / avg[2]; + +	  /* keep exposure time in a working window */ +	  avge = (expr + expg + expb) / 3; +	  if (avge > 0x2000) +	    { +	      expr = (expr * 0x2000) / avge; +	      expg = (expg * 0x2000) / avge; +	      expb = (expb * 0x2000) / avge; +	    } +	  if (avge < 0x400) +	    { +	      expr = (expr * 0x400) / avge; +	      expg = (expg * 0x400) / avge; +	      expb = (expb * 0x400) / avge; +	    } +	} + +      turn++; + +    } +  while (!acceptable && turn < 100); + +  DBG(DBG_info,"%s: acceptable exposure: 0x%04x,0x%04x,0x%04x\n", __func__, expr, expg, expb); +    // BUG: we don't store the result of the last iteration to the sensor +    return calib_sensor.exposure; +} + +/** + * average dark pixels of a scan + */ +static int +dark_average (uint8_t * data, unsigned int pixels, unsigned int lines, +	      unsigned int channels, unsigned int black) +{ +  unsigned int i, j, k, average, count; +  unsigned int avg[3]; +  uint8_t val; + +  /* computes average value on black margin */ +  for (k = 0; k < channels; k++) +    { +      avg[k] = 0; +      count = 0; +      for (i = 0; i < lines; i++) +	{ +	  for (j = 0; j < black; j++) +	    { +	      val = data[i * channels * pixels + j + k]; +	      avg[k] += val; +	      count++; +	    } +	} +      if (count) +	avg[k] /= count; +      DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, k, avg[k]); +    } +  average = 0; +  for (i = 0; i < channels; i++) +    average += avg[i]; +  average /= channels; +  DBG(DBG_info, "%s: average = %d\n", __func__, average); +  return average; +} + + +/** @brief calibration for AD frontend devices + * we do simple scan until all black_pixels are higher than 0, + * raising offset at each turn. + */ +static void ad_fe_offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    (void) sensor; + +  unsigned int channels; +  int pass = 0; +  SANE_Int resolution; +  Genesys_Settings settings; +  unsigned int x, y, adr, min; +  unsigned int bottom, black_pixels; + +  channels = 3; +  resolution = get_closest_resolution(dev->model->sensor_id, sensor.optical_res, channels); +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, ScanMethod::FLATBED); +    black_pixels = (calib_sensor.black_pixels * resolution) / calib_sensor.optical_res; +  DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels); + +    settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res; +    settings.requested_pixels = settings.pixels; +  settings.lines = CALIBRATION_LINES; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  /* scan first line of data with no gain */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); + +  std::vector<uint8_t> line; + +  /* scan with no move */ +  bottom = 1; +  do +    { +      pass++; +      dev->frontend.set_offset(0, bottom); +      dev->frontend.set_offset(1, bottom); +      dev->frontend.set_offset(2, bottom); +        simple_scan(dev, calib_sensor, settings, false, true, false, line, +                    "ad_fe_offset_calibration"); + +        if (is_testing_mode()) { +            return; +        } + +      if (DBG_LEVEL >= DBG_data) +	{ +          char title[30]; +          std::snprintf(title, 30, "gl646_offset%03d.pnm", static_cast<int>(bottom)); +          sanei_genesys_write_pnm_file (title, line.data(), 8, channels, +					settings.pixels, settings.lines); +	} + +      min = 0; +      for (y = 0; y < settings.lines; y++) +	{ +	  for (x = 0; x < black_pixels; x++) +	    { +	      adr = (x + y * settings.pixels) * channels; +	      if (line[adr] > min) +		min = line[adr]; +	      if (line[adr + 1] > min) +		min = line[adr + 1]; +	      if (line[adr + 2] > min) +		min = line[adr + 2]; +	    } +	} + +      DBG(DBG_io2, "%s: pass=%d, min=%d\n", __func__, pass, min); +      bottom++; +    } +  while (pass < 128 && min == 0); +  if (pass == 128) +    { +        throw SaneException(SANE_STATUS_INVAL, "failed to find correct offset"); +    } + +  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. + * genesys_search_start() must have been called so that the offsets and margins + * are already known. + * @param dev scanner's device +*/ +void CommandSetGl646::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    (void) regs; + +  unsigned int channels; +  int pass = 0, avg; +  Genesys_Settings settings; +  int topavg, bottomavg; +  int top, bottom, black_pixels; + +    if (dev->model->adc_id == AdcId::AD_XP200) { +        ad_fe_offset_calibration(dev, sensor); +        return; +    } + +  DBG(DBG_proc, "%s: start\n", __func__); // TODO + +  /* setup for a RGB scan, one full sensor's width line */ +  /* resolution is the one from the final scan          */ +    channels = 3; +    int resolution = get_closest_resolution(dev->model->sensor_id, dev->settings.xres, channels); + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, ScanMethod::FLATBED); +    black_pixels = (calib_sensor.black_pixels * resolution) / calib_sensor.optical_res; + +  DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels); + +    settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res; +    settings.requested_pixels = settings.pixels; +  settings.lines = CALIBRATION_LINES; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  /* scan first line of data with no gain, but with offset from +   * last calibration */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); + +  /* scan with no move */ +  bottom = 90; +  dev->frontend.set_offset(0, bottom); +  dev->frontend.set_offset(1, bottom); +  dev->frontend.set_offset(2, bottom); + +  std::vector<uint8_t> first_line, second_line; + +    simple_scan(dev, calib_sensor, settings, false, true, false, first_line, +                "offset_first_line"); + +  if (DBG_LEVEL >= DBG_data) +    { +      char title[30]; +        std::snprintf(title, 30, "gl646_offset%03d.pnm", bottom); +      sanei_genesys_write_pnm_file(title, first_line.data(), 8, channels, +                                   settings.pixels, settings.lines); +    } +  bottomavg = dark_average(first_line.data(), settings.pixels, settings.lines, channels, +                           black_pixels); +  DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg); + +  /* now top value */ +  top = 231; +  dev->frontend.set_offset(0, top); +  dev->frontend.set_offset(1, top); +  dev->frontend.set_offset(2, top); +    simple_scan(dev, calib_sensor, settings, false, true, false, second_line, +                "offset_second_line"); + +  if (DBG_LEVEL >= DBG_data) +    { +      char title[30]; +        std::snprintf(title, 30, "gl646_offset%03d.pnm", top); +      sanei_genesys_write_pnm_file (title, second_line.data(), 8, channels, +				    settings.pixels, settings.lines); +    } +  topavg = dark_average(second_line.data(), settings.pixels, settings.lines, channels, +                        black_pixels); +  DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg); + +    if (is_testing_mode()) { +        return; +    } + +  /* loop until acceptable level */ +  while ((pass < 32) && (top - bottom > 1)) +    { +      pass++; + +      /* settings for new scan */ +      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 with no move +        simple_scan(dev, calib_sensor, settings, false, true, false, second_line, +                    "offset_calibration_i"); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char title[30]; +            std::snprintf(title, 30, "gl646_offset%03d.pnm", dev->frontend.get_offset(1)); +          sanei_genesys_write_pnm_file (title, second_line.data(), 8, channels, +					settings.pixels, settings.lines); +	} + +      avg = +        dark_average (second_line.data(), settings.pixels, settings.lines, channels, +		      black_pixels); +      DBG(DBG_info, "%s: avg=%d offset=%d\n", __func__, avg, dev->frontend.get_offset(1)); + +      /* compute new boundaries */ +      if (topavg == avg) +	{ +	  topavg = avg; +          top = dev->frontend.get_offset(1); +	} +      else +	{ +	  bottomavg = avg; +          bottom = dev->frontend.get_offset(1); +	} +    } + +  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)); +} + +/** @brief gain calibration for Analog Device frontends + * Alternative coarse gain calibration + */ +static void ad_fe_coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs, int dpi) +{ +    DBG_HELPER(dbg); +    (void) sensor; +    (void) regs; + +  unsigned int i, channels, val; +  unsigned int size, count, resolution, pass; +  float average; +  Genesys_Settings settings; +  char title[32]; + +  /* setup for a RGB scan, one full sensor's width line */ +  /* resolution is the one from the final scan          */ +  channels = 3; +  resolution = get_closest_resolution(dev->model->sensor_id, dpi, channels); + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, ScanMethod::FLATBED); + +  settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; + +    settings.scan_method = dev->model->default_method; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res; +    settings.requested_pixels = settings.pixels; +  settings.lines = CALIBRATION_LINES; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  size = channels * settings.pixels * settings.lines; + +  /* start gain value */ +  dev->frontend.set_gain(0, 1); +  dev->frontend.set_gain(1, 1); +  dev->frontend.set_gain(2, 1); + +  average = 0; +  pass = 0; + +  std::vector<uint8_t> line; + +    // loop until each channel raises to acceptable level +    while ((average < calib_sensor.gain_white_ref) && (pass < 30)) { +        // scan with no move +        simple_scan(dev, calib_sensor, settings, false, true, false, line, +                    "ad_fe_coarse_gain_calibration"); + +      /* log scanning data */ +      if (DBG_LEVEL >= DBG_data) +	{ +            std::sprintf(title, "gl646_alternative_gain%02d.pnm", pass); +          sanei_genesys_write_pnm_file(title, line.data(), 8, channels, settings.pixels, +                                       settings.lines); +	} +      pass++; + +      /* computes white average */ +      average = 0; +      count = 0; +      for (i = 0; i < size; i++) +	{ +	  val = line[i]; +	  average += val; +	  count++; +	} +      average = average / count; + +        uint8_t gain0 = dev->frontend.get_gain(0); +        // adjusts gain for the channel +        if (average < calib_sensor.gain_white_ref) { +            gain0 += 1; +        } + +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); + +      DBG(DBG_proc, "%s: average = %.2f, gain = %d\n", __func__, average, gain0); +    } + +  DBG(DBG_info, "%s: gains=(%d,%d,%d)\n", __func__, +      dev->frontend.get_gain(0), +      dev->frontend.get_gain(1), +      dev->frontend.get_gain(2)); +} + +/** + * Alternative coarse gain calibration + * this on uses the settings from offset_calibration. First scan moves so + * we can go to calibration area for XPA. + * @param dev device for scan + * @param dpi resolutnio to calibrate at + */ +void CommandSetGl646::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER(dbg); +    (void) dpi; + +  unsigned int i, j, k, channels, val, maximum, idx; +  unsigned int count, resolution, pass; +  float average[3]; +  Genesys_Settings settings; +  char title[32]; + +    if (dev->model->sensor_id == SensorId::CIS_XP200) { +      return ad_fe_coarse_gain_calibration(dev, sensor, regs, sensor.optical_res); +    } + +  /* setup for a RGB scan, one full sensor's width line */ +  /* resolution is the one from the final scan          */ +  channels = 3; + +  /* we are searching a sensor resolution */ +    resolution = get_closest_resolution(dev->model->sensor_id, dev->settings.xres, channels); + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         ScanMethod::FLATBED); + +  settings.scan_method = dev->settings.scan_method; +  settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_y = 0; +  if (settings.scan_method == ScanMethod::FLATBED) +    { +      settings.tl_x = 0; +        settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res; +    } +  else +    { +        settings.tl_x = dev->model->x_offset_ta; +        settings.pixels = static_cast<unsigned>((dev->model->x_size_ta * resolution) / MM_PER_INCH); +    } +    settings.requested_pixels = settings.pixels; +  settings.lines = CALIBRATION_LINES; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  /* start gain value */ +  dev->frontend.set_gain(0, 1); +  dev->frontend.set_gain(1, 1); +  dev->frontend.set_gain(2, 1); + +  if (channels > 1) +    { +      average[0] = 0; +      average[1] = 0; +      average[2] = 0; +      idx = 0; +    } +  else +    { +      average[0] = 255; +      average[1] = 255; +      average[2] = 255; +        switch (dev->settings.color_filter) { +            case ColorFilter::RED: idx = 0; break; +            case ColorFilter::GREEN: idx = 1; break; +            case ColorFilter::BLUE: idx = 2; break; +            default: idx = 0; break; // should not happen +        } +      average[idx] = 0; +    } +  pass = 0; + +  std::vector<uint8_t> line; + +  /* loop until each channel raises to acceptable level */ +    while (((average[0] < calib_sensor.gain_white_ref) || +            (average[1] < calib_sensor.gain_white_ref) || +            (average[2] < calib_sensor.gain_white_ref)) && (pass < 30)) +    { +        // scan with no move +        simple_scan(dev, calib_sensor, settings, false, true, false, line, +                    "coarse_gain_calibration"); + +      /* log scanning data */ +      if (DBG_LEVEL >= DBG_data) +	{ +          std::sprintf(title, "gl646_gain%02d.pnm", pass); +          sanei_genesys_write_pnm_file(title, line.data(), 8, channels, settings.pixels, +                                       settings.lines); +	} +      pass++; + +      /* average high level for each channel and compute gain +         to reach the target code +         we only use the central half of the CCD data         */ +      for (k = idx; k < idx + channels; k++) +	{ +	  /* we find the maximum white value, so we can deduce a threshold +	     to average white values */ +	  maximum = 0; +	  for (i = 0; i < settings.lines; i++) +	    { +	      for (j = 0; j < settings.pixels; j++) +		{ +		  val = line[i * channels * settings.pixels + j + k]; +		  if (val > maximum) +		    maximum = val; +		} +	    } + +	  /* threshold */ +            maximum = static_cast<int>(maximum * 0.9); + +	  /* computes white average */ +	  average[k] = 0; +	  count = 0; +	  for (i = 0; i < settings.lines; i++) +	    { +	      for (j = 0; j < settings.pixels; j++) +		{ +		  /* averaging only white points allow us not to care about dark margins */ +		  val = line[i * channels * settings.pixels + j + k]; +		  if (val > maximum) +		    { +		      average[k] += val; +		      count++; +		    } +		} +	    } +	  average[k] = average[k] / count; + +	  /* adjusts gain for the channel */ +          if (average[k] < calib_sensor.gain_white_ref) +            dev->frontend.set_gain(k, dev->frontend.get_gain(k) + 1); + +	  DBG(DBG_proc, "%s: channel %d, average = %.2f, gain = %d\n", __func__, k, average[k], +              dev->frontend.get_gain(k)); +	} +    } + +    if (channels < 3) { +        dev->frontend.set_gain(1, dev->frontend.get_gain(0)); +        dev->frontend.set_gain(2, dev->frontend.get_gain(0)); +    } + +  DBG(DBG_info, "%s: gains=(%d,%d,%d)\n", __func__, +      dev->frontend.get_gain(0), +      dev->frontend.get_gain(1), +      dev->frontend.get_gain(2)); +} + +/** + * sets up the scanner's register for warming up. We scan 2 lines without moving. + * + */ +void CommandSetGl646::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* local_reg, int* channels, +                                           int* total_size) const +{ +    DBG_HELPER(dbg); +    (void) sensor; + +  Genesys_Settings settings; +  int resolution, lines; + +  dev->frontend = dev->frontend_initial; + +  resolution = get_closest_resolution(dev->model->sensor_id, 300, 1); + +    const auto& local_sensor = sanei_genesys_find_sensor(dev, resolution, 1, +                                                         dev->settings.scan_method); + +  /* set up for a half width 2 lines gray scan without moving */ +    settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::GRAY; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = (local_sensor.sensor_pixels * resolution) / local_sensor.optical_res; +    settings.requested_pixels = settings.pixels; +  settings.lines = 2; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +    // setup for scan +    setup_for_scan(dev, local_sensor, &dev->reg, settings, true, false, false, false); + +  /* we are not going to move, so clear these bits */ +    dev->reg.find_reg(0x02).value &= ~(REG_0x02_FASTFED | REG_0x02_AGOHOME); + +  /* don't enable any correction for this scan */ +    dev->reg.find_reg(0x01).value &= ~REG_0x01_DVDSET; + +  /* copy to local_reg */ +  *local_reg = dev->reg; + +  /* turn off motor during this scan */ +  sanei_genesys_set_motor_power(*local_reg, false); + +  /* returned value to higher level warmup function */ +  *channels = 1; +    lines = local_reg->get24(REG_LINCNT) + 1; +  *total_size = lines * settings.pixels; + +    // now registers are ok, write them to scanner +    gl646_set_fe(dev, local_sensor, AFE_SET, settings.xres); +    dev->interface->write_registers(*local_reg); +} + + +/* + * this function moves head without scanning, forward, then backward + * so that the head goes to park position. + * as a by-product, also check for lock + */ +static void gl646_repark_head(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  Genesys_Settings settings; +  unsigned int expected, steps; + +    settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +  settings.xres = get_closest_resolution(dev->model->sensor_id, 75, 1); +  settings.yres = settings.xres; +  settings.tl_x = 0; +  settings.tl_y = 5; +  settings.pixels = 600; +    settings.requested_pixels = settings.pixels; +  settings.lines = 4; +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +    const auto& sensor = sanei_genesys_find_sensor(dev, settings.xres, 3, +                                                   dev->model->default_method); + +    setup_for_scan(dev, sensor, &dev->reg, settings, false, false, false, false); + +  /* TODO seems wrong ... no effective scan */ +    regs_set_optical_off(dev->model->asic_type, dev->reg); + +    dev->interface->write_registers(dev->reg); + +    // start scan +    dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true); + +    expected = dev->reg.get24(REG_FEEDL); +  do +    { +        dev->interface->sleep_ms(100); +        sanei_genesys_read_feed_steps (dev, &steps); +    } +  while (steps < expected); + +    // toggle motor flag, put an huge step number and redo move backward +    dev->cmd_set->move_back_home(dev, 1); +} + +/* * + * initialize ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + * @param dev device description of the scanner to initailize + */ +void CommandSetGl646::init(Genesys_Device* dev) const +{ +    DBG_INIT(); +    DBG_HELPER(dbg); + +    uint8_t val = 0; +  uint32_t addr = 0xdead; +  size_t len; + +    // to detect real power up condition, we write to REG_0x41 with pwrbit set, then read it back. +    // When scanner is cold (just replugged) PWRBIT will be set in the returned value +    auto status = scanner_read_status(*dev); +    if (status.is_replugged) { +      DBG(DBG_info, "%s: device is cold\n", __func__); +    } else { +      DBG(DBG_info, "%s: device is hot\n", __func__); +    } + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +  /* if scanning session hasn't been initialized, set it up */ +  if (!dev->already_initialized) +    { +      dev->dark_average_data.clear(); +      dev->white_average_data.clear(); + +      dev->settings.color_filter = ColorFilter::GREEN; + +      /* Set default values for registers */ +      gl646_init_regs (dev); + +        // Init shading data +        sanei_genesys_init_shading_data(dev, sensor, sensor.sensor_pixels); + +      /* initial calibration reg values */ +      dev->calib_reg = dev->reg; +    } + +    // execute physical unit init only if cold +    if (status.is_replugged) +    { +      DBG(DBG_info, "%s: device is cold\n", __func__); + +        val = 0x04; +        dev->interface->get_usb_device().control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, +                                                     VALUE_INIT, INDEX, 1, &val); + +        // ASIC reset +        dev->interface->write_register(0x0e, 0x00); +        dev->interface->sleep_ms(100); + +        // Write initial registers +        dev->interface->write_registers(dev->reg); + +        // send gamma tables if needed +        dev->cmd_set->send_gamma_table(dev, sensor); + +        // Set powersaving(default = 15 minutes) +        dev->cmd_set->set_powersaving(dev, 15); +    } + +    // Set analog frontend +    gl646_set_fe(dev, sensor, AFE_INIT, 0); + +  /* GPO enabling for XP200 */ +    if (dev->model->sensor_id == SensorId::CIS_XP200) { +        dev->interface->write_register(0x68, dev->gpo.regs.get_value(0x68)); +        dev->interface->write_register(0x69, dev->gpo.regs.get_value(0x69)); + +        // enable GPIO +        gl646_gpio_output_enable(dev->interface->get_usb_device(), 6); + +        // writes 0 to GPIO +        gl646_gpio_write(dev->interface->get_usb_device(), 0); + +        // clear GPIO enable +        gl646_gpio_output_enable(dev->interface->get_usb_device(), 0); + +        dev->interface->write_register(0x66, 0x10); +        dev->interface->write_register(0x66, 0x00); +        dev->interface->write_register(0x66, 0x10); +    } + +  /* MD6471/G2410 and XP200 read/write data from an undocumented memory area which +   * is after the second slope table */ +    if (dev->model->gpio_id != GpioId::HP3670 && +        dev->model->gpio_id != GpioId::HP2400) +    { +      switch (sensor.optical_res) +	{ +	case 600: +	  addr = 0x08200; +	  break; +	case 1200: +	  addr = 0x10200; +	  break; +	case 2400: +	  addr = 0x1fa00; +	  break; +	} +    sanei_genesys_set_buffer_address(dev, addr); + +      sanei_usb_set_timeout (2 * 1000); +      len = 6; +        // for some reason, read fails here for MD6471, HP2300 and XP200 one time out of +        // 2 scanimage launches +        try { +            dev->interface->bulk_read_data(0x45, dev->control, len); +        } catch (...) { +            dev->interface->bulk_read_data(0x45, dev->control, len); +        } +        DBG(DBG_info, "%s: control read=0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", __func__, +            dev->control[0], dev->control[1], dev->control[2], dev->control[3], dev->control[4], +            dev->control[5]); +      sanei_usb_set_timeout (30 * 1000); +    } +  else +    /* HP2400 and HP3670 case */ +    { +      dev->control[0] = 0x00; +      dev->control[1] = 0x00; +      dev->control[2] = 0x01; +      dev->control[3] = 0x00; +      dev->control[4] = 0x00; +      dev->control[5] = 0x00; +    } + +  /* ensure head is correctly parked, and check lock */ +    if (!dev->model->is_sheetfed) { +      if (dev->model->flags & GENESYS_FLAG_REPARK) +	{ +            // FIXME: if repark fails, we should print an error message that the scanner is locked and +            // the user should unlock the lock. We should also rethrow with SANE_STATUS_JAMMED +            gl646_repark_head(dev); +	} +      else +    { +            move_back_home(dev, true); +	} +    } + +  /* here session and device are initialized */ +    dev->already_initialized = true; +} + +void CommandSetGl646::move_to_ta(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); + +    simple_move(dev, static_cast<int>(dev->model->y_offset_sensor_to_ta)); +} + + +/** + * Does a simple scan: ie no line reordering and avanced data buffering and + * shading correction. Memory for data is allocated in this function + * and must be freed by caller. + * @param dev device of the scanner + * @param settings parameters of the scan + * @param move true if moving during scan + * @param forward true if moving forward during scan + * @param shading true to enable shading correction + * @param data pointer for the data + */ +static void simple_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                        Genesys_Settings settings, bool move, bool forward, +                        bool shading, std::vector<uint8_t>& data, +                        const char* scan_identifier) +{ +    DBG_HELPER_ARGS(dbg, "move=%d, forward=%d, shading=%d", move, forward, shading); +  unsigned int size, lines, x, y, bpp; +    bool split; + +  /* round up to multiple of 3 in case of CIS scanner */ +    if (dev->model->is_cis) { +      settings.lines = ((settings.lines + 2) / 3) * 3; +    } + +  /* setup for move then scan */ +    split = !(move && settings.tl_y > 0); +    setup_for_scan(dev, sensor, &dev->reg, settings, split, false, false, !forward); + +  /* allocate memory fo scan : LINCNT may have been adjusted for CCD reordering */ +    if (dev->model->is_cis) { +        lines = dev->reg.get24(REG_LINCNT) / 3; +    } else { +        lines = dev->reg.get24(REG_LINCNT) + 1; +    } +  size = lines * settings.pixels; +    if (settings.depth == 16) { +        bpp = 2; +    } else { +        bpp = 1; +    } +    size *= bpp * settings.get_channels(); +  data.clear(); +  data.resize(size); + +  DBG(DBG_io, "%s: allocated %d bytes of memory for %d lines\n", __func__, size, lines); + +  /* put back real line number in settings */ +  settings.lines = lines; + +    // initialize frontend +    gl646_set_fe(dev, sensor, AFE_SET, settings.xres); + +  /* no shading correction and not watch dog for simple scan */ +    dev->reg.find_reg(0x01).value &= ~(REG_0x01_DVDSET | REG_0x01_DOGENB); +    if (shading) { +      dev->reg.find_reg(0x01).value |= REG_0x01_DVDSET; +    } + +  /* enable gamma table for the scan */ +    dev->reg.find_reg(0x05).value |= REG_0x05_GMMENB; + +  /* one table movement for simple scan */ +    dev->reg.find_reg(0x02).value &= ~REG_0x02_FASTFED; + +    if (!move) { +      sanei_genesys_set_motor_power(dev->reg, false); + +      /* no automatic go home if no movement */ +        dev->reg.find_reg(0x02).value &= ~REG_0x02_AGOHOME; +    } + +  /* no automatic go home when using XPA */ +  if (settings.scan_method == ScanMethod::TRANSPARENCY) { +        dev->reg.find_reg(0x02).value &= ~REG_0x02_AGOHOME; +    } + +    // write scan registers +    dev->interface->write_registers(dev->reg); + +    // starts scan +    dev->cmd_set->begin_scan(dev, sensor, &dev->reg, move); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint(scan_identifier); +        return; +    } + +    wait_until_buffer_non_empty(dev, true); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +  /* in case of CIS scanner, we must reorder data */ +    if (dev->model->is_cis && settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) { +      /* alloc one line sized working buffer */ +      std::vector<uint8_t> buffer(settings.pixels * 3 * bpp); + +      /* reorder one line of data and put it back to buffer */ +      if (bpp == 1) +	{ +	  for (y = 0; y < lines; y++) +	    { +	      /* reorder line */ +	      for (x = 0; x < settings.pixels; x++) +		{ +                  buffer[x * 3] = data[y * settings.pixels * 3 + x]; +                  buffer[x * 3 + 1] = data[y * settings.pixels * 3 + settings.pixels + x]; +                  buffer[x * 3 + 2] = data[y * settings.pixels * 3 + 2 * settings.pixels + x]; +		} +	      /* copy line back */ +              memcpy (data.data() + settings.pixels * 3 * y, buffer.data(), +		      settings.pixels * 3); +	    } +	} +      else +	{ +	  for (y = 0; y < lines; y++) +	    { +	      /* reorder line */ +	      for (x = 0; x < settings.pixels; x++) +		{ +                  buffer[x * 6] = data[y * settings.pixels * 6 + x * 2]; +                  buffer[x * 6 + 1] = data[y * settings.pixels * 6 + x * 2 + 1]; +                  buffer[x * 6 + 2] = data[y * settings.pixels * 6 + 2 * settings.pixels + x * 2]; +                  buffer[x * 6 + 3] = data[y * settings.pixels * 6 + 2 * settings.pixels + x * 2 + 1]; +                  buffer[x * 6 + 4] = data[y * settings.pixels * 6 + 4 * settings.pixels + x * 2]; +                  buffer[x * 6 + 5] = data[y * settings.pixels * 6 + 4 * settings.pixels + x * 2 + 1]; +		} +	      /* copy line back */ +              memcpy (data.data() + settings.pixels * 6 * y, buffer.data(), +		      settings.pixels * 6); +	    } +	} +    } + +    // end scan , waiting the motor to stop if needed (if moving), but without ejecting doc +    end_scan_impl(dev, &dev->reg, true, false); +} + +/** + * Does a simple move of the given distance by doing a scan at lowest resolution + * shading correction. Memory for data is allocated in this function + * and must be freed by caller. + * @param dev device of the scanner + * @param distance distance to move in MM + */ +static void simple_move(Genesys_Device* dev, SANE_Int distance) +{ +    DBG_HELPER_ARGS(dbg, "%d mm", distance); +  Genesys_Settings settings; + +    unsigned resolution = sanei_genesys_get_lowest_dpi(dev); + +  const auto& sensor = sanei_genesys_find_sensor(dev, resolution, 3, dev->model->default_method); + +  /* TODO give a no AGOHOME flag */ +    settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +  settings.xres = resolution; +  settings.yres = resolution; +  settings.tl_y = 0; +  settings.tl_x = 0; +  settings.pixels = (sensor.sensor_pixels * settings.xres) / sensor.optical_res; +    settings.requested_pixels = settings.pixels; +    settings.lines = static_cast<unsigned>((distance * settings.xres) / MM_PER_INCH); +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  std::vector<uint8_t> data; +    simple_scan(dev, sensor, settings, true, true, false, data, "simple_move"); +} + +/** + * update the status of the required sensor in the scanner session + * the button fileds are used to make events 'sticky' + */ +void CommandSetGl646::update_hardware_sensors(Genesys_Scanner* session) const +{ +    DBG_HELPER(dbg); +  Genesys_Device *dev = session->dev; +  uint8_t value; + +    // do what is needed to get a new set of events, but try to not loose any of them. +    gl646_gpio_read(dev->interface->get_usb_device(), &value); +    DBG(DBG_io, "%s: GPIO=0x%02x\n", __func__, value); + +    // scan button +    if (dev->model->buttons & GENESYS_HAS_SCAN_SW) { +        switch (dev->model->gpio_id) { +        case GpioId::XP200: +            session->buttons[BUTTON_SCAN_SW].write((value & 0x02) != 0); +            break; +        case GpioId::MD_5345: +            session->buttons[BUTTON_SCAN_SW].write(value == 0x16); +            break; +        case GpioId::HP2300: +            session->buttons[BUTTON_SCAN_SW].write(value == 0x6c); +            break; +        case GpioId::HP3670: +        case GpioId::HP2400: +            session->buttons[BUTTON_SCAN_SW].write((value & 0x20) == 0); +            break; +        default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +	} +    } + +    // email button +    if (dev->model->buttons & GENESYS_HAS_EMAIL_SW) { +        switch (dev->model->gpio_id) { +        case GpioId::MD_5345: +            session->buttons[BUTTON_EMAIL_SW].write(value == 0x12); +            break; +        case GpioId::HP3670: +        case GpioId::HP2400: +            session->buttons[BUTTON_EMAIL_SW].write((value & 0x08) == 0); +            break; +        default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +    } +    } + +    // copy button +    if (dev->model->buttons & GENESYS_HAS_COPY_SW) { +        switch (dev->model->gpio_id) { +        case GpioId::MD_5345: +            session->buttons[BUTTON_COPY_SW].write(value == 0x11); +            break; +        case GpioId::HP2300: +            session->buttons[BUTTON_COPY_SW].write(value == 0x5c); +            break; +        case GpioId::HP3670: +        case GpioId::HP2400: +            session->buttons[BUTTON_COPY_SW].write((value & 0x10) == 0); +            break; +        default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +    } +    } + +    // power button +    if (dev->model->buttons & GENESYS_HAS_POWER_SW) { +        switch (dev->model->gpio_id) { +        case GpioId::MD_5345: +            session->buttons[BUTTON_POWER_SW].write(value == 0x14); +            break; +        default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +    } +    } + +    // ocr button +    if (dev->model->buttons & GENESYS_HAS_OCR_SW) { +        switch (dev->model->gpio_id) { +    case GpioId::MD_5345: +            session->buttons[BUTTON_OCR_SW].write(value == 0x13); +            break; +	default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +    } +    } + +    // document detection +    if (dev->model->buttons & GENESYS_HAS_PAGE_LOADED_SW) { +        switch (dev->model->gpio_id) { +        case GpioId::XP200: +            session->buttons[BUTTON_PAGE_LOADED_SW].write((value & 0x04) != 0); +            break; +        default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +    } +    } + +  /* XPA detection */ +  if (dev->model->flags & GENESYS_FLAG_XPA) +    { +        switch (dev->model->gpio_id) { +            case GpioId::HP3670: +            case GpioId::HP2400: +	  /* test if XPA is plugged-in */ +	  if ((value & 0x40) == 0) +	    { +	      DBG(DBG_io, "%s: enabling XPA\n", __func__); +	      session->opt[OPT_SOURCE].cap &= ~SANE_CAP_INACTIVE; +	    } +	  else +	    { +	      DBG(DBG_io, "%s: disabling XPA\n", __func__); +	      session->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE; +	    } +      break; +            default: +                throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type"); +    } +    } +} + + +static void write_control(Genesys_Device* dev, const Genesys_Sensor& sensor, int resolution) +{ +    DBG_HELPER(dbg); +  uint8_t control[4]; +  uint32_t addr = 0xdead; + +  /* 2300 does not write to 'control' */ +    if (dev->model->motor_id == MotorId::HP2300) { +        return; +    } + +  /* MD6471/G2410/HP2300 and XP200 read/write data from an undocumented memory area which +   * is after the second slope table */ +  switch (sensor.optical_res) +    { +    case 600: +      addr = 0x08200; +      break; +    case 1200: +      addr = 0x10200; +      break; +    case 2400: +      addr = 0x1fa00; +      break; +    default: +        throw SaneException("failed to compute control address"); +    } + +  /* XP200 sets dpi, what other scanner put is unknown yet */ +  switch (dev->model->motor_id) +    { +        case MotorId::XP200: +      /* we put scan's dpi, not motor one */ +            control[0] = resolution & 0xff; +            control[1] = (resolution >> 8) & 0xff; +      control[2] = dev->control[4]; +      control[3] = dev->control[5]; +      break; +        case MotorId::HP3670: +        case MotorId::HP2400: +        case MotorId::MD_5345: +        default: +      control[0] = dev->control[2]; +      control[1] = dev->control[3]; +      control[2] = dev->control[4]; +      control[3] = dev->control[5]; +      break; +    } + +  DBG(DBG_info, "%s: control write=0x%02x 0x%02x 0x%02x 0x%02x\n", __func__, control[0], control[1], +      control[2], control[3]); +    dev->interface->write_buffer(0x3c, addr, control, 4); +} + +/** + * search for a full width black or white strip. + * @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 CommandSetGl646::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward, +                                   bool black) const +{ +    DBG_HELPER(dbg); +    (void) sensor; + +  Genesys_Settings settings; +  int res = get_closest_resolution(dev->model->sensor_id, 75, 1); +  unsigned int pass, count, found, x, y; +  char title[80]; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, res, 1, ScanMethod::FLATBED); + +  /* we set up for a lowest available resolution color grey scan, full width */ +    settings.scan_method = dev->model->default_method; +  settings.scan_mode = ScanColorMode::GRAY; +  settings.xres = res; +  settings.yres = res; +  settings.tl_x = 0; +  settings.tl_y = 0; +    settings.pixels = static_cast<unsigned>((dev->model->x_size * res) / MM_PER_INCH); +    settings.pixels /= calib_sensor.get_ccd_size_divisor_for_dpi(res); +    settings.requested_pixels = settings.pixels; + +  /* 15 mm at at time */ +    settings.lines = static_cast<unsigned>((15 * settings.yres) / MM_PER_INCH); +  settings.depth = 8; +  settings.color_filter = ColorFilter::RED; + +  settings.disable_interpolation = 0; +  settings.threshold = 0; + +  /* signals if a strip of the given color has been found */ +  found = 0; + +  /* detection pass done */ +  pass = 0; + +  std::vector<uint8_t> data; + +  /* loop until strip is found or maximum pass number done */ +  while (pass < 20 && !found) +    { +        // scan a full width strip +        simple_scan(dev, calib_sensor, settings, true, forward, false, data, "search_strip"); + +        if (is_testing_mode()) { +            return; +        } + +      if (DBG_LEVEL >= DBG_data) +	{ +            std::sprintf(title, "gl646_search_strip_%s%02d.pnm", forward ? "fwd" : "bwd", pass); +          sanei_genesys_write_pnm_file (title, data.data(), settings.depth, 1, +					settings.pixels, settings.lines); +	} + +      /* search data to find black strip */ +      /* when searching forward, we only need one line of the searched color since we +       * will scan forward. But when doing backward search, we need all the area of the +       * same color */ +      if (forward) +	{ +	  for (y = 0; y < settings.lines && !found; y++) +	    { +	      count = 0; +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < settings.pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +		  if (black && data[y * settings.pixels + x] > 90) +		    { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +		  if (!black && data[y * settings.pixels + x] < 60) +		    { +		      count++; +		    } +		} + +	      /* at end of line, if count >= 3%, line is not fully of the desired color +	       * so we must go to next line of the buffer */ +	      /* count*100/pixels < 3 */ +	      if ((count * 100) / settings.pixels < 3) +		{ +		  found = 1; +		  DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__, +		      pass, y); +		} +	      else +		{ +		  DBG(DBG_data, "%s: pixels=%d, count=%d\n", __func__, settings.pixels, count); +		} +	    } +	} +      else			/* since calibration scans are done forward, we need the whole area +				   to be of the required color when searching backward */ +	{ +	  count = 0; +	  for (y = 0; y < settings.lines; y++) +	    { +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < settings.pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +		  if (black && data[y * settings.pixels + x] > 60) +		    { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +		  if (!black && data[y * settings.pixels + x] < 60) +		    { +		      count++; +		    } +		} +	    } + +	  /* at end of area, if count >= 3%, area is not fully of the desired color +	   * so we must go to next buffer */ +	  if ((count * 100) / (settings.pixels * settings.lines) < 3) +	    { +	      found = 1; +	      DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); +	    } +	  else +	    { +	      DBG(DBG_data, "%s: pixels=%d, count=%d\n", __func__, settings.pixels, count); +	    } +	} +      pass++; +    } +  if (found) +    { +      DBG(DBG_info, "%s: strip found\n", __func__); +    } +  else +    { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white"); +    } +} + +void CommandSetGl646::wait_for_motor_stop(Genesys_Device* dev) const +{ +    (void) dev; +} + +void CommandSetGl646::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        std::uint8_t* data, int size) const +{ +    (void) dev; +    (void) sensor; +    (void) data; +    (void) size; +    throw SaneException("not implemented"); +} + +ScanSession CommandSetGl646::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +    // compute distance to move +    float move = 0; +    // XXX STEF XXX MD5345 -> optical_ydpi, other base_ydpi => half/full step ? */ +    if (!dev->model->is_sheetfed) { +        move = static_cast<float>(dev->model->y_offset); +        // add tl_y to base movement +    } +    move += static_cast<float>(settings.tl_y); + +    if (move < 0) { +        DBG(DBG_error, "%s: overriding negative move value %f\n", __func__, move); +        move = 0; +    } + +    move = static_cast<float>((move * dev->motor.optical_ydpi) / MM_PER_INCH); +    float start = static_cast<float>(settings.tl_x); +    if (settings.scan_method == ScanMethod::FLATBED) { +        start += static_cast<float>(dev->model->x_offset); +    } else { +        start += static_cast<float>(dev->model->x_offset_ta); +    } +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    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 = dev->settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::USE_XCORRECTION; +    if (settings.scan_method == ScanMethod::TRANSPARENCY) { +        session.params.flags |= ScanFlag::USE_XPA; +    } +    compute_session(dev, session, sensor); + +    return session; +} + +void CommandSetGl646::asic_boot(Genesys_Device *dev, bool cold) const +{ +    (void) dev; +    (void) cold; +    throw SaneException("not implemented"); +} + +std::unique_ptr<CommandSet> create_gl646_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl646{}); +} + +} // namespace gl646 +} // namespace genesys | 
