diff options
Diffstat (limited to 'backend/genesys')
77 files changed, 47649 insertions, 0 deletions
diff --git a/backend/genesys/buffer.cpp b/backend/genesys/buffer.cpp new file mode 100644 index 0000000..f17e361 --- /dev/null +++ b/backend/genesys/buffer.cpp @@ -0,0 +1,102 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#include "buffer.h" +#include <cstring> +#include <stdexcept> + +namespace genesys { + +void Genesys_Buffer::alloc(std::size_t size) +{ +    buffer_.resize(size); +    avail_ = 0; +    pos_ = 0; +} + +void Genesys_Buffer::clear() +{ +    buffer_.clear(); +    avail_ = 0; +    pos_ = 0; +} + +void Genesys_Buffer::reset() +{ +    avail_ = 0; +    pos_ = 0; +} + +std::uint8_t* Genesys_Buffer::get_write_pos(std::size_t size) +{ +    if (avail_ + size > buffer_.size()) +        return nullptr; +    if (pos_ + avail_ + size > buffer_.size()) +    { +        std::memmove(buffer_.data(), buffer_.data() + pos_, avail_); +        pos_ = 0; +    } +    return buffer_.data() + pos_ + avail_; +} + +std::uint8_t* Genesys_Buffer::get_read_pos() +{ +    return buffer_.data() + pos_; +} + +void Genesys_Buffer::produce(std::size_t size) +{ +    if (size > buffer_.size() - avail_) +        throw std::runtime_error("buffer size exceeded"); +    avail_ += size; +} + +void Genesys_Buffer::consume(std::size_t size) +{ +    if (size > avail_) +        throw std::runtime_error("no more data in buffer"); +    avail_ -= size; +    pos_ += size; +} + +} // namespace genesys diff --git a/backend/genesys/buffer.h b/backend/genesys/buffer.h new file mode 100644 index 0000000..e9c889b --- /dev/null +++ b/backend/genesys/buffer.h @@ -0,0 +1,89 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_BUFFER_H +#define BACKEND_GENESYS_BUFFER_H + +#include <vector> +#include <cstddef> +#include <cstdint> + +namespace genesys { + +/*  A FIFO buffer. Note, that this is _not_ a ringbuffer. +    if we need a block which does not fit at the end of our available data, +    we move the available data to the beginning. +*/ +struct Genesys_Buffer +{ +    Genesys_Buffer() = default; + +    std::size_t size() const { return buffer_.size(); } +    std::size_t avail() const { return avail_; } +    std::size_t pos() const { return pos_; } + +    // TODO: refactor code that uses this function to no longer use it +    void set_pos(std::size_t pos) { pos_ = pos; } + +    void alloc(std::size_t size); +    void clear(); + +    void reset(); + +    std::uint8_t* get_write_pos(std::size_t size); +    std::uint8_t* get_read_pos(); // TODO: mark as const + +    void produce(std::size_t size); +    void consume(std::size_t size); + +private: +    std::vector<std::uint8_t> buffer_; +    // current position in read buffer +    std::size_t pos_ = 0; +    // data bytes currently in buffer +    std::size_t avail_ = 0; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_BUFFER_H diff --git a/backend/genesys/calibration.h b/backend/genesys/calibration.h new file mode 100644 index 0000000..f14aaa3 --- /dev/null +++ b/backend/genesys/calibration.h @@ -0,0 +1,108 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_CALIBRATION_H +#define BACKEND_GENESYS_CALIBRATION_H + +#include "sensor.h" +#include "settings.h" +#include <ctime> + +namespace genesys { + +struct Genesys_Calibration_Cache +{ +    Genesys_Calibration_Cache() = default; +    ~Genesys_Calibration_Cache() = default; + +    // used to check if entry is compatible +    SetupParams params; + +    std::time_t last_calibration = 0; + +    Genesys_Frontend frontend; +    Genesys_Sensor sensor; + +    size_t calib_pixels = 0; +    size_t calib_channels = 0; +    size_t average_size = 0; +    std::vector<std::uint16_t> white_average_data; +    std::vector<std::uint16_t> dark_average_data; + +    bool operator==(const Genesys_Calibration_Cache& other) const +    { +        return params == other.params && +            last_calibration == other.last_calibration && +            frontend == other.frontend && +            sensor == other.sensor && +            calib_pixels == other.calib_pixels && +            calib_channels == other.calib_channels && +            average_size == other.average_size && +            white_average_data == other.white_average_data && +            dark_average_data == other.dark_average_data; +    } +}; + +template<class Stream> +void serialize(Stream& str, Genesys_Calibration_Cache& x) +{ +    serialize(str, x.params); +    serialize_newline(str); +    serialize(str, x.last_calibration); +    serialize_newline(str); +    serialize(str, x.frontend); +    serialize_newline(str); +    serialize(str, x.sensor); +    serialize_newline(str); +    serialize(str, x.calib_pixels); +    serialize(str, x.calib_channels); +    serialize(str, x.average_size); +    serialize_newline(str); +    serialize(str, x.white_average_data); +    serialize_newline(str); +    serialize(str, x.dark_average_data); +} + +} // namespace genesys + +#endif // BACKEND_GENESYS_CALIBRATION_H diff --git a/backend/genesys/command_set.h b/backend/genesys/command_set.h new file mode 100644 index 0000000..ab3a4b6 --- /dev/null +++ b/backend/genesys/command_set.h @@ -0,0 +1,166 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_COMMAND_SET_H +#define BACKEND_GENESYS_COMMAND_SET_H + +#include "device.h" +#include "fwd.h" +#include <cstdint> + +namespace genesys { + + +/** Scanner command set description. + +    This description contains parts which are common to all scanners with the +    same command set, but may have different optical resolution and other +    parameters. + */ +class CommandSet +{ +public: +    virtual ~CommandSet() = default; + +    virtual bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const = 0; + +    virtual void init(Genesys_Device* dev) const = 0; + +    virtual void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                      Genesys_Register_Set* regs, int* channels, +                                      int* total_size) const = 0; + +    virtual void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                  Genesys_Register_Set& regs) const = 0; +    virtual void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                       Genesys_Register_Set& regs) const = 0; +    virtual void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const = 0; + +    /** Set up registers for a scan. Similar to init_regs_for_scan except that the session is +        already computed from the session +    */ +    virtual void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set* reg, +                                            const ScanSession& session) const= 0; + +    virtual void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, std::uint8_t set) const = 0; +    virtual void set_powersaving(Genesys_Device* dev, int delay) const = 0; +    virtual void save_power(Genesys_Device* dev, bool enable) const = 0; + +    virtual void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set* regs, bool start_motor) const = 0; +    virtual void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, +                          bool check_stop) const = 0; + + +    /** +     * Send gamma tables to ASIC +     */ +    virtual void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const = 0; + +    virtual void search_start_position(Genesys_Device* dev) const = 0; +    virtual void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set& regs) const = 0; +    virtual void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs, int dpi) const = 0; +    virtual SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set& regs) const = 0; + +    virtual void wait_for_motor_stop(Genesys_Device* dev) const = 0; +    virtual void move_back_home(Genesys_Device* dev, bool wait_until_home) const = 0; + +    // Updates hardware sensor information in Genesys_Scanner.val[]. +    virtual void update_hardware_sensors(struct Genesys_Scanner* s) const = 0; + +    /** Whether the scanner needs to call update_home_sensor_gpio before reading the status of the +        home sensor. On some chipsets this is unreliable until update_home_sensor_gpio() is called. +    */ +    virtual bool needs_update_home_sensor_gpio() const { return false; } + +    /** Needed on some chipsets before reading the status of the home sensor to make this operation +        reliable. +    */ +    virtual void update_home_sensor_gpio(Genesys_Device& dev) const { (void) dev; } + +    // functions for sheetfed scanners + +    // load document into scanner +    virtual void load_document(Genesys_Device* dev) const = 0; + +    /** Detects is the scanned document has left scanner. In this case it updates the amount of +        data to read and set up flags in the dev struct +     */ +    virtual void detect_document_end(Genesys_Device* dev) const = 0; + +    /// eject document from scanner +    virtual void eject_document(Genesys_Device* dev) const = 0; +    /** +     * search for an black or white area in forward or reverse +     * direction */ +    virtual void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              bool forward, bool black) const = 0; + +    /// move scanning head to transparency adapter +    virtual void move_to_ta(Genesys_Device* dev) const = 0; + +    /// write shading data calibration to ASIC +    virtual void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   std::uint8_t* data, int size) const = 0; + +    virtual bool has_send_shading_data() const +    { +        return true; +    } + +    /// calculate an instance of ScanSession for scanning with the given settings +    virtual ScanSession calculate_scan_session(const Genesys_Device* dev, +                                               const Genesys_Sensor& sensor, +                                               const Genesys_Settings& settings) const = 0; + +    /// cold boot init function +    virtual void asic_boot(Genesys_Device* dev, bool cold) const = 0; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_COMMAND_SET_H diff --git a/backend/genesys/conv.cpp b/backend/genesys/conv.cpp new file mode 100644 index 0000000..a87c463 --- /dev/null +++ b/backend/genesys/conv.cpp @@ -0,0 +1,238 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2005, 2006 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "conv.h" +#include "sane/sanei_magic.h" + +namespace genesys { + +/** + * uses the threshold/threshold_curve to control software binarization + * This code was taken from the epjistsu backend by m. allan noah + * @param dev device set up for the scan + * @param src pointer to raw data + * @param dst pointer where to store result + * @param width width of the processed line + * */ +void binarize_line(Genesys_Device* dev, std::uint8_t* src, std::uint8_t* dst, int width) +{ +    DBG_HELPER(dbg); +  int j, windowX, sum = 0; +  int thresh; +  int offset, addCol, dropCol; +  unsigned char mask; + +  int x; +    std::uint8_t min, max; + +  /* normalize line */ +  min = 255; +  max = 0; +    for (x = 0; x < width; x++) +      { +	if (src[x] > max) +	  { +	    max = src[x]; +	  } +	if (src[x] < min) +	  { +	    min = src[x]; +	  } +      } + +    /* safeguard against dark or white areas */ +    if(min>80) +	    min=0; +    if(max<80) +	    max=255; +    for (x = 0; x < width; x++) +      { +	src[x] = ((src[x] - min) * 255) / (max - min); +      } + +  /* ~1mm works best, but the window needs to have odd # of pixels */ +  windowX = (6 * dev->settings.xres) / 150; +  if (!(windowX % 2)) +    windowX++; + +  /* second, prefill the sliding sum */ +  for (j = 0; j < windowX; j++) +    sum += src[j]; + +  /* third, walk the input buffer, update the sliding sum, */ +  /* determine threshold, output bits */ +  for (j = 0; j < width; j++) +    { +      /* output image location */ +      offset = j % 8; +      mask = 0x80 >> offset; +      thresh = dev->settings.threshold; + +      /* move sum/update threshold only if there is a curve */ +      if (dev->settings.threshold_curve) +	{ +	  addCol = j + windowX / 2; +	  dropCol = addCol - windowX; + +	  if (dropCol >= 0 && addCol < width) +	    { +	      sum -= src[dropCol]; +	      sum += src[addCol]; +	    } +	  thresh = dev->lineart_lut[sum / windowX]; +	} + +      /* use average to lookup threshold */ +      if (src[j] > thresh) +	*dst &= ~mask;		/* white */ +      else +	*dst |= mask;		/* black */ + +      if (offset == 7) +	dst++; +    } +} + +/** + * software lineart using data from a 8 bit gray scan. We assume true gray + * or monochrome scan as input. + */ +void genesys_gray_lineart(Genesys_Device* dev, +                          std::uint8_t* src_data, std::uint8_t* dst_data, +                          std::size_t pixels, std::size_t lines, std::uint8_t threshold) +{ +    DBG_HELPER(dbg); +    std::size_t y; + +    DBG(DBG_io2, "%s: converting %zu lines of %zu pixels\n", __func__, lines, pixels); +  DBG(DBG_io2, "%s: threshold=%d\n", __func__, threshold); + +  for (y = 0; y < lines; y++) +    { +      binarize_line (dev, src_data + y * pixels, dst_data, pixels); +      dst_data += pixels / 8; +    } +} + +/** Look in image for likely left/right/bottom paper edges, then crop image. + */ +void genesys_crop(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +  Genesys_Device *dev = s->dev; +  int top = 0; +  int bottom = 0; +  int left = 0; +  int right = 0; + +    // first find edges if any +    TIE(sanei_magic_findEdges(&s->params, dev->img_buffer.data(), +                              dev->settings.xres, dev->settings.yres, +                              &top, &bottom, &left, &right)); + +  DBG (DBG_io, "%s: t:%d b:%d l:%d r:%d\n", __func__, top, bottom, left, +       right); + +    // now crop the image +    TIE(sanei_magic_crop (&(s->params), dev->img_buffer.data(), top, bottom, left, right)); + +  /* update counters to new image size */ +  dev->total_bytes_to_read = s->params.bytes_per_line * s->params.lines; +} + +/** Look in image for likely upper and left paper edges, then rotate + * image so that upper left corner of paper is upper left of image. + */ +void genesys_deskew(Genesys_Scanner *s, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +  Genesys_Device *dev = s->dev; + +  int x = 0, y = 0, bg; +  double slope = 0; + +  bg=0; +  if(s->params.format==SANE_FRAME_GRAY && s->params.depth == 1) +    { +      bg=0xff; +    } +    TIE(sanei_magic_findSkew(&s->params, dev->img_buffer.data(), +                             sensor.optical_res, sensor.optical_res, +                             &x, &y, &slope)); + +    DBG(DBG_info, "%s: slope=%f => %f\n", __func__, slope, slope * 180 / M_PI); + +    // rotate image slope is in [-PI/2,PI/2]. Positive values rotate trigonometric direction wise +    TIE(sanei_magic_rotate(&s->params, dev->img_buffer.data(), +                           x, y, slope, bg)); +} + +/** remove lone dots + */ +void genesys_despeck(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +    TIE(sanei_magic_despeck(&s->params, s->dev->img_buffer.data(), s->despeck)); +} + +/** Look if image needs rotation and apply it + * */ +void genesys_derotate(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +  int angle = 0; + +    TIE(sanei_magic_findTurn(&s->params, s->dev->img_buffer.data(), +                             s->resolution, s->resolution, &angle)); + +    // apply rotation angle found +    TIE(sanei_magic_turn(&s->params, s->dev->img_buffer.data(), angle)); + +    // update counters to new image size +    s->dev->total_bytes_to_read = s->params.bytes_per_line * s->params.lines; +} + +} // namespace genesys diff --git a/backend/genesys/conv.h b/backend/genesys/conv.h new file mode 100644 index 0000000..446a80d --- /dev/null +++ b/backend/genesys/conv.h @@ -0,0 +1,69 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_CONV_H +#define BACKEND_GENESYS_CONV_H + +#include "device.h" +#include "sensor.h" +#include "genesys.h" + +namespace genesys { + +void binarize_line(Genesys_Device* dev, std::uint8_t* src, std::uint8_t* dst, int width); + +void genesys_gray_lineart(Genesys_Device* dev, +                          std::uint8_t* src_data, std::uint8_t* dst_data, +                          std::size_t pixels, size_t lines, std::uint8_t threshold); + +void genesys_crop(Genesys_Scanner* s); + +void genesys_deskew(Genesys_Scanner *s, const Genesys_Sensor& sensor); + +void genesys_despeck(Genesys_Scanner* s); + +void genesys_derotate(Genesys_Scanner* s); + +} // namespace genesys + +#endif // BACKEND_GENESYS_CONV_H diff --git a/backend/genesys/device.cpp b/backend/genesys/device.cpp new file mode 100644 index 0000000..ba035fd --- /dev/null +++ b/backend/genesys/device.cpp @@ -0,0 +1,272 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "device.h" +#include "command_set.h" +#include "low.h" +#include "utilities.h" + +namespace genesys { + +std::vector<unsigned> MethodResolutions::get_resolutions() const +{ +    std::vector<unsigned> ret; +    std::copy(resolutions_x.begin(), resolutions_x.end(), std::back_inserter(ret)); +    std::copy(resolutions_y.begin(), resolutions_y.end(), std::back_inserter(ret)); +    // sort in decreasing order + +    std::sort(ret.begin(), ret.end(), std::greater<unsigned>()); +    ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); +    return ret; +} + +const MethodResolutions& Genesys_Model::get_resolution_settings(ScanMethod method) const +{ +    for (const auto& res_for_method : resolutions) { +        for (auto res_method : res_for_method.methods) { +            if (res_method == method) { +                return res_for_method; +            } +        } +    } +    throw SaneException("Could not find resolution settings for method %d", +                        static_cast<unsigned>(method)); +} + +std::vector<unsigned> Genesys_Model::get_resolutions(ScanMethod method) const +{ +    return get_resolution_settings(method).get_resolutions(); +} + +Genesys_Device::~Genesys_Device() +{ +    clear(); +} + +void Genesys_Device::clear() +{ +    read_buffer.clear(); +    binarize_buffer.clear(); +    local_buffer.clear(); + +    calib_file.clear(); + +    calibration_cache.clear(); + +    white_average_data.clear(); +    dark_average_data.clear(); +} + +ImagePipelineNodeBytesSource& Genesys_Device::get_pipeline_source() +{ +    return static_cast<ImagePipelineNodeBytesSource&>(pipeline.front()); +} + +bool Genesys_Device::is_head_pos_known(ScanHeadId scan_head) const +{ +    switch (scan_head) { +        case ScanHeadId::PRIMARY: return is_head_pos_primary_known_; +        case ScanHeadId::SECONDARY: return is_head_pos_secondary_known_; +        case ScanHeadId::ALL: return is_head_pos_primary_known_ && is_head_pos_secondary_known_; +        default: +            throw SaneException("Unknown scan head ID"); +    } +} +unsigned Genesys_Device::head_pos(ScanHeadId scan_head) const +{ +    switch (scan_head) { +        case ScanHeadId::PRIMARY: return head_pos_primary_; +        case ScanHeadId::SECONDARY: return head_pos_secondary_; +        default: +            throw SaneException("Unknown scan head ID"); +    } +} + +void Genesys_Device::set_head_pos_unknown() +{ +    is_head_pos_primary_known_ = false; +    is_head_pos_secondary_known_ = false; +} + +void Genesys_Device::set_head_pos_zero(ScanHeadId scan_head) +{ +    if ((scan_head & ScanHeadId::PRIMARY) != ScanHeadId::NONE) { +        head_pos_primary_ = 0; +        is_head_pos_primary_known_ = true; +    } +    if ((scan_head & ScanHeadId::SECONDARY) != ScanHeadId::NONE) { +        head_pos_secondary_ = 0; +        is_head_pos_secondary_known_ = true; +    } +} + +void Genesys_Device::advance_head_pos_by_session(ScanHeadId scan_head) +{ +    int motor_steps = session.params.starty + +                      (session.params.lines * motor.base_ydpi) / session.params.yres; +    auto direction = has_flag(session.params.flags, ScanFlag::REVERSE) ? Direction::BACKWARD +                                                                       : Direction::FORWARD; +    advance_head_pos_by_steps(scan_head, direction, motor_steps); +} + +static void advance_pos(unsigned& pos, Direction direction, unsigned offset) +{ +    if (direction == Direction::FORWARD) { +        pos += offset; +    } else { +        if (pos < offset) { +            throw SaneException("Trying to advance head behind the home sensor"); +        } +        pos -= offset; +    } +} + +void Genesys_Device::advance_head_pos_by_steps(ScanHeadId scan_head, Direction direction, +                                               unsigned steps) +{ +    if ((scan_head & ScanHeadId::PRIMARY) != ScanHeadId::NONE) { +        if (!is_head_pos_primary_known_) { +            throw SaneException("Trying to advance head while scanhead position is not known"); +        } +        advance_pos(head_pos_primary_, direction, steps); +    } +    if ((scan_head & ScanHeadId::SECONDARY) != ScanHeadId::NONE) { +        if (!is_head_pos_secondary_known_) { +            throw SaneException("Trying to advance head while scanhead position is not known"); +        } +        advance_pos(head_pos_secondary_, direction, steps); +    } +} + +void print_scan_position(std::ostream& out, const Genesys_Device& dev, ScanHeadId scan_head) +{ +    if (dev.is_head_pos_known(scan_head)) { +        out << dev.head_pos(scan_head); +    } else { +        out <<"(unknown)"; +    } +} + +std::ostream& operator<<(std::ostream& out, const Genesys_Device& dev) +{ +    StreamStateSaver state_saver{out}; + +    out << "Genesys_Device{\n" +        << std::hex +        << "    vendorId: 0x" << dev.vendorId << '\n' +        << "    productId: 0x" << dev.productId << '\n' +        << std::dec +        << "    usb_mode: " << dev.usb_mode << '\n' +        << "    file_name: " << dev.file_name << '\n' +        << "    calib_file: " << dev.calib_file << '\n' +        << "    force_calibration: " << dev.force_calibration << '\n' +        << "    ignore_offsets: " << dev.ignore_offsets << '\n' +        << "    model: (not printed)\n" +        << "    reg: " << format_indent_braced_list(4, dev.reg) << '\n' +        << "    calib_reg: " << format_indent_braced_list(4, dev.calib_reg) << '\n' +        << "    settings: " << format_indent_braced_list(4, dev.settings) << '\n' +        << "    frontend: " << format_indent_braced_list(4, dev.frontend) << '\n' +        << "    frontend_initial: " << format_indent_braced_list(4, dev.frontend_initial) << '\n' +        << "    frontend_is_init: " << dev.frontend_is_init << '\n' +        << "    gpo.regs: " << format_indent_braced_list(4, dev.gpo.regs) << '\n' +        << "    motor: " << format_indent_braced_list(4, dev.motor) << '\n' +        << "    control[0..6]: " << std::hex +        << static_cast<unsigned>(dev.control[0]) << ' ' +        << static_cast<unsigned>(dev.control[1]) << ' ' +        << static_cast<unsigned>(dev.control[2]) << ' ' +        << static_cast<unsigned>(dev.control[3]) << ' ' +        << static_cast<unsigned>(dev.control[4]) << ' ' +        << static_cast<unsigned>(dev.control[5]) << '\n' << std::dec +        << "    average_size: " << dev.average_size << '\n' +        << "    calib_pixels: " << dev.calib_pixels << '\n' +        << "    calib_lines: " << dev.calib_lines << '\n' +        << "    calib_channels: " << dev.calib_channels << '\n' +        << "    calib_resolution: " << dev.calib_resolution << '\n' +        << "    calib_total_bytes_to_read: " << dev.calib_total_bytes_to_read << '\n' +        << "    calib_session: " << format_indent_braced_list(4, dev.calib_session) << '\n' +        << "    calib_pixels_offset: " << dev.calib_pixels_offset << '\n' +        << "    gamma_override_tables[0].size(): " << dev.gamma_override_tables[0].size() << '\n' +        << "    gamma_override_tables[1].size(): " << dev.gamma_override_tables[1].size() << '\n' +        << "    gamma_override_tables[2].size(): " << dev.gamma_override_tables[2].size() << '\n' +        << "    white_average_data.size(): " << dev.white_average_data.size() << '\n' +        << "    dark_average_data.size(): " << dev.dark_average_data.size() << '\n' +        << "    already_initialized: " << dev.already_initialized << '\n' +        << "    scanhead_position[PRIMARY]: "; +    print_scan_position(out, dev, ScanHeadId::PRIMARY); +    out << '\n' +        << "    scanhead_position[SECONDARY]: "; +    print_scan_position(out, dev, ScanHeadId::SECONDARY); +    out << '\n' +        << "    read_active: " << dev.read_active << '\n' +        << "    parking: " << dev.parking << '\n' +        << "    document: " << dev.document << '\n' +        << "    read_buffer.size(): " << dev.read_buffer.size() << '\n' +        << "    binarize_buffer.size(): " << dev.binarize_buffer.size() << '\n' +        << "    local_buffer.size(): " << dev.local_buffer.size() << '\n' +        << "    oe_buffer.size(): " << dev.oe_buffer.size() << '\n' +        << "    total_bytes_read: " << dev.total_bytes_read << '\n' +        << "    total_bytes_to_read: " << dev.total_bytes_to_read << '\n' +        << "    session: " << format_indent_braced_list(4, dev.session) << '\n' +        << "    lineart_lut: (not printed)\n" +        << "    calibration_cache: (not printed)\n" +        << "    line_count: " << dev.line_count << '\n' +        << "    segment_order: " +        << format_indent_braced_list(4, format_vector_unsigned(4, dev.segment_order)) << '\n' +        << "    buffer_image: " << dev.buffer_image << '\n' +        << "    img_buffer.size(): " << dev.img_buffer.size() << '\n' +        << '}'; +    return out; +} + +void apply_reg_settings_to_device(Genesys_Device& dev, const GenesysRegisterSettingSet& regs) +{ +    for (const auto& reg : regs) { +        uint8_t val = dev.interface->read_register(reg.address); +        val = (val & ~reg.mask) | (reg.value & reg.mask); +        dev.interface->write_register(reg.address, val); +    } +} + +} // namespace genesys diff --git a/backend/genesys/device.h b/backend/genesys/device.h new file mode 100644 index 0000000..6c744c9 --- /dev/null +++ b/backend/genesys/device.h @@ -0,0 +1,387 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_DEVICE_H +#define BACKEND_GENESYS_DEVICE_H + +#include "calibration.h" +#include "command_set.h" +#include "buffer.h" +#include "enums.h" +#include "image_pipeline.h" +#include "motor.h" +#include "settings.h" +#include "sensor.h" +#include "register.h" +#include "usb_device.h" +#include "scanner_interface.h" +#include <vector> + +namespace genesys { + +struct Genesys_Gpo +{ +    Genesys_Gpo() = default; + +    // Genesys_Gpo +    GpioId id = GpioId::UNKNOWN; + +    /*  GL646 and possibly others: +        - have the value registers at 0x66 and 0x67 +        - have the enable registers at 0x68 and 0x69 + +        GL841, GL842, GL843, GL846, GL848 and possibly others: +        - have the value registers at 0x6c and 0x6d. +        - have the enable registers at 0x6e and 0x6f. +    */ +    GenesysRegisterSettingSet regs; +}; + +/// Stores a SANE_Fixed value which is automatically converted from and to floating-point values +class FixedFloat +{ +public: +    FixedFloat() = default; +    FixedFloat(const FixedFloat&) = default; +    FixedFloat(double number) : value_{SANE_FIX(number)} {} +    FixedFloat& operator=(const FixedFloat&) = default; +    FixedFloat& operator=(double number) { value_ = SANE_FIX(number); return *this; } + +    operator double() const { return value(); } + +    double value() const { return SANE_UNFIX(value_); } + +private: +    SANE_Fixed value_ = 0; +}; + +struct MethodResolutions +{ +    std::vector<ScanMethod> methods; +    std::vector<unsigned> resolutions_x; +    std::vector<unsigned> resolutions_y; + +    unsigned get_min_resolution_x() const +    { +        return *std::min_element(resolutions_x.begin(), resolutions_x.end()); +    } + +    unsigned get_min_resolution_y() const +    { +        return *std::min_element(resolutions_y.begin(), resolutions_y.end()); +    } + +    std::vector<unsigned> get_resolutions() const; +}; + +/** @brief structure to describe a scanner model + * This structure describes a model. It is composed of information on the + * sensor, the motor, scanner geometry and flags to drive operation. + */ +struct Genesys_Model +{ +    Genesys_Model() = default; + +    const char* name = nullptr; +    const char* vendor = nullptr; +    const char* model = nullptr; +    ModelId model_id = ModelId::UNKNOWN; + +    AsicType asic_type = AsicType::UNKNOWN; + +    // possible x and y resolutions for each method supported by the scanner +    std::vector<MethodResolutions> resolutions; + +    // possible depths in gray mode +    std::vector<unsigned> bpp_gray_values; +    // possible depths in color mode +    std::vector<unsigned> bpp_color_values; + +    // the default scanning method. This is used when moving the head for example +    ScanMethod default_method = ScanMethod::FLATBED; + +    // All offsets below are with respect to the sensor home position + +    // Start of scan area in mm +    FixedFloat x_offset = 0; + +    // Start of scan area in mm (Amount of feeding needed to get to the medium) +    FixedFloat y_offset = 0; + +    // Size of scan area in mm +    FixedFloat x_size = 0; + +    // Size of scan area in mm +    FixedFloat y_size = 0; + +    // Start of white strip in mm +    FixedFloat y_offset_calib_white = 0; + +    // Start of black mark in mm +    FixedFloat x_offset_calib_black = 0; + +    // Start of scan area in transparency mode in mm +    FixedFloat x_offset_ta = 0; + +    // Start of scan area in transparency mode in mm +    FixedFloat y_offset_ta = 0; + +    // Size of scan area in transparency mode in mm +    FixedFloat x_size_ta = 0; + +    // Size of scan area in transparency mode in mm +    FixedFloat y_size_ta = 0; + +    // The position of the sensor when it's aligned with the lamp for transparency scanning +    FixedFloat y_offset_sensor_to_ta = 0; + +    // Start of white strip in transparency mode in mm +    FixedFloat y_offset_calib_white_ta = 0; + +    // Start of black strip in transparency mode in mm +    FixedFloat y_offset_calib_black_ta = 0; + +    // Size of scan area after paper sensor stop sensing document in mm +    FixedFloat post_scan = 0; + +    // Amount of feeding needed to eject document after finishing scanning in mm +    FixedFloat eject_feed = 0; + +    // Line-distance correction (in pixel at optical_ydpi) for CCD scanners +    SANE_Int ld_shift_r = 0; +    SANE_Int ld_shift_g = 0; +    SANE_Int ld_shift_b = 0; + +    // Order of the CCD/CIS colors +    ColorOrder line_mode_color_order = ColorOrder::RGB; + +    // Is this a CIS or CCD scanner? +    bool is_cis = false; + +    // Is this sheetfed scanner? +    bool is_sheetfed = false; + +    // sensor type +    SensorId sensor_id = SensorId::UNKNOWN; +    // Analog-Digital converter type +    AdcId adc_id = AdcId::UNKNOWN; +    // General purpose output type +    GpioId gpio_id = GpioId::UNKNOWN; +    // stepper motor type +    MotorId motor_id = MotorId::UNKNOWN; + +    // Which hacks are needed for this scanner? +    SANE_Word flags = 0; + +    // Button flags, described existing buttons for the model +    SANE_Word buttons = 0; + +    // how many lines are used for shading calibration +    SANE_Int shading_lines = 0; +    // how many lines are used for shading calibration in TA mode +    SANE_Int shading_ta_lines = 0; +    // how many lines are used to search start position +    SANE_Int search_lines = 0; + +    const MethodResolutions& get_resolution_settings(ScanMethod method) const; + +    std::vector<unsigned> get_resolutions(ScanMethod method) const; +}; + +/** + * Describes the current device status for the backend + * session. This should be more accurately called + * Genesys_Session . + */ +struct Genesys_Device +{ +    Genesys_Device() = default; +    ~Genesys_Device(); + +    using Calibration = std::vector<Genesys_Calibration_Cache>; + +    // frees commonly used data +    void clear(); + +    SANE_Word vendorId = 0;			/**< USB vendor identifier */ +    SANE_Word productId = 0;			/**< USB product identifier */ + +    // USB mode: +    // 0: not set +    // 1: USB 1.1 +    // 2: USB 2.0 +    SANE_Int usb_mode = 0; + +    std::string file_name; +    std::string calib_file; + +    // if enabled, no calibration data will be loaded or saved to files +    SANE_Int force_calibration = 0; +    // if enabled, will ignore the scan offsets and start scanning at true origin. This allows +    // acquiring the positions of the black and white strips and the actual scan area +    bool ignore_offsets = false; + +    Genesys_Model *model = nullptr; + +    // pointers to low level functions +    std::unique_ptr<CommandSet> cmd_set; + +    Genesys_Register_Set reg; +    Genesys_Register_Set calib_reg; +    Genesys_Settings settings; +    Genesys_Frontend frontend, frontend_initial; + +    // whether the frontend is initialized. This is currently used just to preserve historical +    // behavior +    bool frontend_is_init = false; + +    Genesys_Gpo gpo; +    Genesys_Motor motor; +    std::uint8_t control[6] = {}; + +    size_t average_size = 0; +    // number of pixels used during shading calibration +    size_t calib_pixels = 0; +    // number of lines used during shading calibration +    size_t calib_lines = 0; +    size_t calib_channels = 0; +    size_t calib_resolution = 0; +     // bytes to read from USB when calibrating. If 0, this is not set +    size_t calib_total_bytes_to_read = 0; + +    // the session that was configured for calibration +    ScanSession calib_session; + +    // certain scanners support much higher resolution when scanning transparency, but we can't +    // read whole width of the scanner as a single line at that resolution. Thus for stuff like +    // calibration we want to read only the possible calibration area. +    size_t calib_pixels_offset = 0; + +    // gamma overrides. If a respective array is not empty then it means that the gamma for that +    // color is overridden. +    std::vector<std::uint16_t> gamma_override_tables[3]; + +    std::vector<std::uint16_t> white_average_data; +    std::vector<std::uint16_t> dark_average_data; + +    bool already_initialized = false; + +    bool read_active = false; +    // signal wether the park command has been issued +    bool parking = false; + +    // for sheetfed scanner's, is TRUE when there is a document in the scanner +    bool document = false; + +    Genesys_Buffer read_buffer; + +    // buffer for digital lineart from gray data +    Genesys_Buffer binarize_buffer; +    // local buffer for gray data during dynamix lineart +    Genesys_Buffer local_buffer; + +    // total bytes read sent to frontend +    size_t total_bytes_read = 0; +    // total bytes read to be sent to frontend +    size_t total_bytes_to_read = 0; + +    // contains computed data for the current setup +    ScanSession session; + +    // look up table used in dynamic rasterization +    unsigned char lineart_lut[256] = {}; + +    Calibration calibration_cache; + +    // number of scan lines used during scan +    int line_count = 0; + +    // array describing the order of the sub-segments of the sensor +    std::vector<unsigned> segment_order; + +    // buffer to handle even/odd data +    Genesys_Buffer oe_buffer = {}; + +    // stores information about how the input image should be processed +    ImagePipelineStack pipeline; + +    // an buffer that allows reading from `pipeline` in chunks of any size +    ImageBuffer pipeline_buffer; + +    // when true the scanned picture is first buffered to allow software image enhancements +    bool buffer_image = false; + +    // image buffer where the scanned picture is stored +    std::vector<std::uint8_t> img_buffer; + +    ImagePipelineNodeBytesSource& get_pipeline_source(); + +    std::unique_ptr<ScannerInterface> interface; + +    bool is_head_pos_known(ScanHeadId scan_head) const; +    unsigned head_pos(ScanHeadId scan_head) const; +    void set_head_pos_unknown(); +    void set_head_pos_zero(ScanHeadId scan_head); +    void advance_head_pos_by_session(ScanHeadId scan_head); +    void advance_head_pos_by_steps(ScanHeadId scan_head, Direction direction, unsigned steps); + +private: +    // the position of the primary scan head in motor->base_dpi units +    unsigned head_pos_primary_ = 0; +    bool is_head_pos_primary_known_ = true; + +    // the position of the secondary scan head in motor->base_dpi units. Only certain scanners +    // have a secondary scan head. +    unsigned head_pos_secondary_ = 0; +    bool is_head_pos_secondary_known_ = true; + +    friend class ScannerInterfaceUsb; +}; + +std::ostream& operator<<(std::ostream& out, const Genesys_Device& dev); + +void apply_reg_settings_to_device(Genesys_Device& dev, const GenesysRegisterSettingSet& regs); + +} // namespace genesys + +#endif diff --git a/backend/genesys/enums.cpp b/backend/genesys/enums.cpp new file mode 100644 index 0000000..f515cfd --- /dev/null +++ b/backend/genesys/enums.cpp @@ -0,0 +1,131 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "enums.h" +#include "genesys.h" +#include <iomanip> + +namespace genesys { + +const char* scan_method_to_option_string(ScanMethod method) +{ +    switch (method) { +        case ScanMethod::FLATBED: return STR_FLATBED; +        case ScanMethod::TRANSPARENCY: return STR_TRANSPARENCY_ADAPTER; +        case ScanMethod::TRANSPARENCY_INFRARED: return STR_TRANSPARENCY_ADAPTER_INFRARED; +    } +    throw SaneException("Unknown scan method %d", static_cast<unsigned>(method)); +} + +ScanMethod option_string_to_scan_method(const std::string& str) +{ +    if (str == STR_FLATBED) { +        return ScanMethod::FLATBED; +    } else if (str == STR_TRANSPARENCY_ADAPTER) { +        return ScanMethod::TRANSPARENCY; +    } else if (str == STR_TRANSPARENCY_ADAPTER_INFRARED) { +        return ScanMethod::TRANSPARENCY_INFRARED; +    } +    throw SaneException("Unknown scan method option %s", str.c_str()); +} + +const char* scan_color_mode_to_option_string(ScanColorMode mode) +{ +    switch (mode) { +        case ScanColorMode::COLOR_SINGLE_PASS: return SANE_VALUE_SCAN_MODE_COLOR; +        case ScanColorMode::GRAY: return SANE_VALUE_SCAN_MODE_GRAY; +        case ScanColorMode::HALFTONE: return SANE_VALUE_SCAN_MODE_HALFTONE; +        case ScanColorMode::LINEART: return SANE_VALUE_SCAN_MODE_LINEART; +    } +    throw SaneException("Unknown scan mode %d", static_cast<unsigned>(mode)); +} + +ScanColorMode option_string_to_scan_color_mode(const std::string& str) +{ +    if (str == SANE_VALUE_SCAN_MODE_COLOR) { +        return ScanColorMode::COLOR_SINGLE_PASS; +    } else if (str == SANE_VALUE_SCAN_MODE_GRAY) { +        return ScanColorMode::GRAY; +    } else if (str == SANE_VALUE_SCAN_MODE_HALFTONE) { +        return ScanColorMode::HALFTONE; +    } else if (str == SANE_VALUE_SCAN_MODE_LINEART) { +        return ScanColorMode::LINEART; +    } +    throw SaneException("Unknown scan color mode %s", str.c_str()); +} + + +std::ostream& operator<<(std::ostream& out, ColorFilter mode) +{ +    switch (mode) { +        case ColorFilter::RED: out << "RED"; break; +        case ColorFilter::GREEN: out << "GREEN"; break; +        case ColorFilter::BLUE: out << "BLUE"; break; +        case ColorFilter::NONE: out << "NONE"; break; +        default: out << static_cast<unsigned>(mode); break; +    } +    return out; +} + +std::ostream& operator<<(std::ostream& out, StepType type) +{ +    switch (type) { +        case StepType::FULL: out << "1/1"; break; +        case StepType::HALF: out << "1/2"; break; +        case StepType::QUARTER: out << "1/4"; break; +        case StepType::EIGHTH: out << "1/8"; break; +        default: out << static_cast<unsigned>(type); break; +    } +    return out; +} + +std::ostream& operator<<(std::ostream& out, ScanFlag flags) +{ +    StreamStateSaver state_saver{out}; +    out << "0x" << std::hex << static_cast<unsigned>(flags); +    return out; +} + +} // namespace genesys diff --git a/backend/genesys/enums.h b/backend/genesys/enums.h new file mode 100644 index 0000000..810c4ca --- /dev/null +++ b/backend/genesys/enums.h @@ -0,0 +1,530 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_ENUMS_H +#define BACKEND_GENESYS_ENUMS_H + +#include <iostream> +#include "serialize.h" + +namespace genesys { + +enum class ScanMethod : unsigned { +    // normal scan method +    FLATBED = 0, +    // scan using transparency adaptor +    TRANSPARENCY = 1, +    // scan using transparency adaptor via infrared channel +    TRANSPARENCY_INFRARED = 2 +}; + +inline std::ostream& operator<<(std::ostream& out, ScanMethod mode) +{ +    switch (mode) { +        case ScanMethod::FLATBED: out << "FLATBED"; return out; +        case ScanMethod::TRANSPARENCY: out << "TRANSPARENCY"; return out; +        case ScanMethod::TRANSPARENCY_INFRARED: out << "TRANSPARENCY_INFRARED"; return out; +    } +    return out; +} + +inline void serialize(std::istream& str, ScanMethod& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<ScanMethod>(value); +} + +inline void serialize(std::ostream& str, ScanMethod& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + +const char* scan_method_to_option_string(ScanMethod method); +ScanMethod option_string_to_scan_method(const std::string& str); + +enum class ScanColorMode : unsigned { +    LINEART = 0, +    HALFTONE, +    GRAY, +    COLOR_SINGLE_PASS +}; + +inline std::ostream& operator<<(std::ostream& out, ScanColorMode mode) +{ +    switch (mode) { +        case ScanColorMode::LINEART: out << "LINEART"; return out; +        case ScanColorMode::HALFTONE: out << "HALFTONE"; return out; +        case ScanColorMode::GRAY: out << "GRAY"; return out; +        case ScanColorMode::COLOR_SINGLE_PASS: out << "COLOR_SINGLE_PASS"; return out; +    } +    return out; +} + +inline void serialize(std::istream& str, ScanColorMode& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<ScanColorMode>(value); +} + +inline void serialize(std::ostream& str, ScanColorMode& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + +const char* scan_color_mode_to_option_string(ScanColorMode mode); +ScanColorMode option_string_to_scan_color_mode(const std::string& str); + + +enum class ScanHeadId : unsigned { +    NONE = 0, +    PRIMARY = 1 << 0, +    SECONDARY = 1 << 1, +    ALL = PRIMARY | SECONDARY, +}; + +inline ScanHeadId operator|(ScanHeadId left, ScanHeadId right) +{ +    return static_cast<ScanHeadId>(static_cast<unsigned>(left) | static_cast<unsigned>(right)); +} + +inline ScanHeadId operator&(ScanHeadId left, ScanHeadId right) +{ +    return static_cast<ScanHeadId>(static_cast<unsigned>(left) & static_cast<unsigned>(right)); +} + + +enum class ColorFilter : unsigned { +    RED = 0, +    GREEN, +    BLUE, +    NONE +}; + +std::ostream& operator<<(std::ostream& out, ColorFilter mode); + +inline void serialize(std::istream& str, ColorFilter& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<ColorFilter>(value); +} + +inline void serialize(std::ostream& str, ColorFilter& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + +enum class ColorOrder +{ +    RGB, +    GBR, +    BGR, +}; + +/*  Enum value naming conventions: +    Full name must be included with the following exceptions: + +    Canon scanners omit "Canoscan" if present +*/ +enum class ModelId : unsigned +{ +    UNKNOWN = 0, +    CANON_4400F, +    CANON_5600F, +    CANON_8400F, +    CANON_8600F, +    CANON_IMAGE_FORMULA_101, +    CANON_LIDE_50, +    CANON_LIDE_60, +    CANON_LIDE_80, +    CANON_LIDE_100, +    CANON_LIDE_110, +    CANON_LIDE_120, +    CANON_LIDE_200, +    CANON_LIDE_210, +    CANON_LIDE_220, +    CANON_LIDE_700F, +    DCT_DOCKETPORT_487, +    HP_SCANJET_2300C, +    HP_SCANJET_2400C, +    HP_SCANJET_3670, +    HP_SCANJET_4850C, +    HP_SCANJET_G4010, +    HP_SCANJET_G4050, +    HP_SCANJET_N6310, +    MEDION_MD5345, +    PANASONIC_KV_SS080, +    PENTAX_DSMOBILE_600, +    PLUSTEK_OPTICBOOK_3800, +    PLUSTEK_OPTICFILM_7200I, +    PLUSTEK_OPTICFILM_7300, +    PLUSTEK_OPTICFILM_7500I, +    PLUSTEK_OPTICPRO_3600, +    PLUSTEK_OPTICPRO_ST12, +    PLUSTEK_OPTICPRO_ST24, +    SYSCAN_DOCKETPORT_465, +    SYSCAN_DOCKETPORT_467, +    SYSCAN_DOCKETPORT_485, +    SYSCAN_DOCKETPORT_665, +    SYSCAN_DOCKETPORT_685, +    UMAX_ASTRA_4500, +    VISIONEER_7100, +    VISIONEER_ROADWARRIOR, +    VISIONEER_STROBE_XP100_REVISION3, +    VISIONEER_STROBE_XP200, +    VISIONEER_STROBE_XP300, +    XEROX_2400, +    XEROX_TRAVELSCANNER_100, +}; + +enum class SensorId : unsigned +{ +    UNKNOWN = 0, +    CCD_5345, +    CCD_CANON_4400F, +    CCD_CANON_8400F, +    CCD_CANON_8600F, +    CCD_DP665, +    CCD_DP685, +    CCD_DSMOBILE600, +    CCD_G4050, +    CCD_HP2300, +    CCD_HP2400, +    CCD_HP3670, +    CCD_HP_N6310, +    CCD_HP_4850C, +    CCD_IMG101, +    CCD_KVSS080, +    CCD_PLUSTEK_OPTICBOOK_3800, +    CCD_PLUSTEK_OPTICFILM_7200I, +    CCD_PLUSTEK_OPTICFILM_7300, +    CCD_PLUSTEK_OPTICFILM_7500I, +    CCD_PLUSTEK_OPTICPRO_3600, +    CCD_ROADWARRIOR, +    CCD_ST12,         // SONY ILX548: 5340 Pixel  ??? +    CCD_ST24,         // SONY ILX569: 10680 Pixel ??? +    CCD_UMAX, +    CCD_XP300, +    CIS_CANON_LIDE_35, +    CIS_CANON_LIDE_80, +    CIS_CANON_LIDE_100, +    CIS_CANON_LIDE_110, +    CIS_CANON_LIDE_120, +    CIS_CANON_LIDE_200, +    CIS_CANON_LIDE_210, +    CIS_CANON_LIDE_220, +    CIS_CANON_LIDE_700F, +    CIS_XP200, +}; + +inline void serialize(std::istream& str, SensorId& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<SensorId>(value); +} + +inline void serialize(std::ostream& str, SensorId& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + + +enum class AdcId : unsigned +{ +    UNKNOWN = 0, +    AD_XP200, +    CANON_LIDE_35, +    CANON_LIDE_80, +    CANON_LIDE_110, +    CANON_LIDE_120, +    CANON_LIDE_200, +    CANON_LIDE_700F, +    CANON_4400F, +    CANON_8400F, +    CANON_8600F, +    G4050, +    IMG101, +    KVSS080, +    PLUSTEK_OPTICBOOK_3800, +    PLUSTEK_OPTICFILM_7200I, +    PLUSTEK_OPTICFILM_7300, +    PLUSTEK_OPTICFILM_7500I, +    PLUSTEK_OPTICPRO_3600, +    WOLFSON_5345, +    WOLFSON_DSM600, +    WOLFSON_HP2300, +    WOLFSON_HP2400, +    WOLFSON_HP3670, +    WOLFSON_ST12, +    WOLFSON_ST24, +    WOLFSON_UMAX, +    WOLFSON_XP300, +}; + +inline void serialize(std::istream& str, AdcId& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<AdcId>(value); +} + +inline void serialize(std::ostream& str, AdcId& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + +enum class GpioId : unsigned +{ +    UNKNOWN = 0, +    CANON_LIDE_35, +    CANON_LIDE_80, +    CANON_LIDE_110, +    CANON_LIDE_120, +    CANON_LIDE_200, +    CANON_LIDE_210, +    CANON_LIDE_700F, +    CANON_4400F, +    CANON_8400F, +    CANON_8600F, +    DP665, +    DP685, +    G4050, +    HP2300, +    HP2400, +    HP3670, +    HP_N6310, +    IMG101, +    KVSS080, +    MD_5345, +    PLUSTEK_OPTICBOOK_3800, +    PLUSTEK_OPTICFILM_7200I, +    PLUSTEK_OPTICFILM_7300, +    PLUSTEK_OPTICFILM_7500I, +    PLUSTEK_OPTICPRO_3600, +    ST12, +    ST24, +    UMAX, +    XP200, +    XP300, +}; + +enum class MotorId : unsigned +{ +    UNKNOWN = 0, +    CANON_LIDE_100, +    CANON_LIDE_110, +    CANON_LIDE_120, +    CANON_LIDE_200, +    CANON_LIDE_210, +    CANON_LIDE_35, +    CANON_LIDE_700, +    CANON_LIDE_80, +    CANON_4400F, +    CANON_8400F, +    CANON_8600F, +    DP665, +    DSMOBILE_600, +    G4050, +    HP2300, +    HP2400, +    HP3670, +    IMG101, +    KVSS080, +    MD_5345, +    PLUSTEK_OPTICBOOK_3800, +    PLUSTEK_OPTICFILM_7200I, +    PLUSTEK_OPTICFILM_7300, +    PLUSTEK_OPTICFILM_7500I, +    PLUSTEK_OPTICPRO_3600, +    ROADWARRIOR, +    ST24, +    UMAX, +    XP200, +    XP300, +}; + +enum class StepType : unsigned +{ +    FULL = 0, +    HALF = 1, +    QUARTER = 2, +    EIGHTH = 3, +}; + +std::ostream& operator<<(std::ostream& out, StepType type); + +inline bool operator<(StepType lhs, StepType rhs) +{ +    return static_cast<unsigned>(lhs) < static_cast<unsigned>(rhs); +} +inline bool operator<=(StepType lhs, StepType rhs) +{ +    return static_cast<unsigned>(lhs) <= static_cast<unsigned>(rhs); +} +inline bool operator>(StepType lhs, StepType rhs) +{ +    return static_cast<unsigned>(lhs) > static_cast<unsigned>(rhs); +} +inline bool operator>=(StepType lhs, StepType rhs) +{ +    return static_cast<unsigned>(lhs) >= static_cast<unsigned>(rhs); +} + +enum class AsicType : unsigned +{ +    UNKNOWN = 0, +    GL646, +    GL841, +    GL843, +    GL845, +    GL846, +    GL847, +    GL124, +}; + + +enum class ScanFlag : unsigned +{ +    NONE = 0, +    SINGLE_LINE = 1 << 0, +    DISABLE_SHADING = 1 << 1, +    DISABLE_GAMMA = 1 << 2, +    DISABLE_BUFFER_FULL_MOVE = 1 << 3, +    IGNORE_LINE_DISTANCE = 1 << 4, +    DISABLE_LAMP = 1 << 5, +    CALIBRATION = 1 << 6, +    FEEDING = 1 << 7, +    USE_XPA = 1 << 8, +    ENABLE_LEDADD = 1 << 9, +    USE_XCORRECTION = 1 << 10, +    REVERSE = 1 << 11, +}; + +inline ScanFlag operator|(ScanFlag left, ScanFlag right) +{ +    return static_cast<ScanFlag>(static_cast<unsigned>(left) | static_cast<unsigned>(right)); +} + +inline ScanFlag& operator|=(ScanFlag& left, ScanFlag right) +{ +    left = left | right; +    return left; +} + +inline ScanFlag operator&(ScanFlag left, ScanFlag right) +{ +    return static_cast<ScanFlag>(static_cast<unsigned>(left) & static_cast<unsigned>(right)); +} + +inline bool has_flag(ScanFlag flags, ScanFlag which) +{ +    return (flags & which) == which; +} + +inline void serialize(std::istream& str, ScanFlag& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<ScanFlag>(value); +} + +inline void serialize(std::ostream& str, ScanFlag& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + +std::ostream& operator<<(std::ostream& out, ScanFlag flags); + + + +enum class MotorFlag : unsigned +{ +    NONE = 0, +    AUTO_GO_HOME = 1 << 0, +    DISABLE_BUFFER_FULL_MOVE = 1 << 2, +    FEED = 1 << 3, +    USE_XPA = 1 << 4, +    REVERSE = 1 << 5, +}; + +inline MotorFlag operator|(MotorFlag left, MotorFlag right) +{ +    return static_cast<MotorFlag>(static_cast<unsigned>(left) | static_cast<unsigned>(right)); +} + +inline MotorFlag& operator|=(MotorFlag& left, MotorFlag right) +{ +    left = left | right; +    return left; +} + +inline MotorFlag operator&(MotorFlag left, MotorFlag right) +{ +    return static_cast<MotorFlag>(static_cast<unsigned>(left) & static_cast<unsigned>(right)); +} + +inline bool has_flag(MotorFlag flags, MotorFlag which) +{ +    return (flags & which) == which; +} + + +enum class Direction : unsigned +{ +    FORWARD = 0, +    BACKWARD = 1 +}; + + +} // namespace genesys + +#endif // BACKEND_GENESYS_ENUMS_H diff --git a/backend/genesys/error.cpp b/backend/genesys/error.cpp new file mode 100644 index 0000000..6c921c1 --- /dev/null +++ b/backend/genesys/error.cpp @@ -0,0 +1,215 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "error.h" +#include <cstdarg> + +namespace genesys { + +extern "C" void sanei_debug_msg(int level, int max_level, const char *be, const char *fmt, +                                std::va_list ap); + +#if (defined(__GNUC__) || defined(__CLANG__)) && (defined(__linux__) || defined(__APPLE__)) +extern "C" char* __cxa_get_globals(); +#endif + +static unsigned num_uncaught_exceptions() +{ +#if __cplusplus >= 201703L +    int count = std::uncaught_exceptions(); +    return count >= 0 ? count : 0; +#elif (defined(__GNUC__) || defined(__CLANG__)) && (defined(__linux__) || defined(__APPLE__)) +    // the format of the __cxa_eh_globals struct is enshrined into the Itanium C++ ABI and it's +    // very unlikely we'll get issues referencing it directly +    char* cxa_eh_globals_ptr = __cxa_get_globals(); +    return *reinterpret_cast<unsigned*>(cxa_eh_globals_ptr + sizeof(void*)); +#else +    return std::uncaught_exception() ? 1 : 0; +#endif +} + +SaneException::SaneException(SANE_Status status) : status_(status) +{ +    set_msg(); +} + +SaneException::SaneException(SANE_Status status, const char* format, ...) : status_(status) +{ +    std::va_list args; +    va_start(args, format); +    set_msg(format, args); +    va_end(args); +} + +SaneException::SaneException(const char* format, ...) : status_(SANE_STATUS_INVAL) +{ +    std::va_list args; +    va_start(args, format); +    set_msg(format, args); +    va_end(args); +} + +SANE_Status SaneException::status() const +{ +    return status_; +} + +const char* SaneException::what() const noexcept +{ +    return msg_.c_str(); +} + +void SaneException::set_msg() +{ +    const char* status_msg = sane_strstatus(status_); +    std::size_t status_msg_len = std::strlen(status_msg); +    msg_.reserve(status_msg_len); +    msg_ = status_msg; +} + +void SaneException::set_msg(const char* format, std::va_list vlist) +{ +    const char* status_msg = sane_strstatus(status_); +    std::size_t status_msg_len = std::strlen(status_msg); + +    std::va_list vlist2; +    va_copy(vlist2, vlist); +    int msg_len = std::vsnprintf(nullptr, 0, format, vlist2); +    va_end(vlist2); + +    if (msg_len < 0) { +        const char* formatting_error_msg = "(error formatting arguments)"; +        msg_.reserve(std::strlen(formatting_error_msg) + 3 + status_msg_len); +        msg_ = formatting_error_msg; +        msg_ += " : "; +        msg_ += status_msg; +        return; +    } + +    msg_.reserve(msg_len + status_msg_len + 3); +    msg_.resize(msg_len + 1, ' '); +    std::vsnprintf(&msg_[0], msg_len + 1, format, vlist); +    msg_.resize(msg_len, ' '); + +    msg_ += " : "; +    msg_ += status_msg; +} + +DebugMessageHelper::DebugMessageHelper(const char* func) +{ +    func_ = func; +    num_exceptions_on_enter_ = num_uncaught_exceptions(); +    msg_[0] = '\0'; +    DBG(DBG_proc, "%s: start\n", func_); +} + +DebugMessageHelper::DebugMessageHelper(const char* func, const char* format, ...) +{ +    func_ = func; +    num_exceptions_on_enter_ = num_uncaught_exceptions(); +    msg_[0] = '\0'; +    DBG(DBG_proc, "%s: start\n", func_); +    DBG(DBG_proc, "%s: ", func_); + +    std::va_list args; +    va_start(args, format); +    sanei_debug_msg(DBG_proc, DBG_LEVEL, STRINGIFY(BACKEND_NAME), format, args); +    va_end(args); +    DBG(DBG_proc, "\n"); +} + + +DebugMessageHelper::~DebugMessageHelper() +{ +    if (num_exceptions_on_enter_ < num_uncaught_exceptions()) { +        if (msg_[0] != '\0') { +            DBG(DBG_error, "%s: failed during %s\n", func_, msg_); +        } else { +            DBG(DBG_error, "%s: failed\n", func_); +        } +    } else { +        DBG(DBG_proc, "%s: completed\n", func_); +    } +} + +void DebugMessageHelper::vstatus(const char* format, ...) +{ +    std::va_list args; +    va_start(args, format); +    std::vsnprintf(msg_, MAX_BUF_SIZE, format, args); +    va_end(args); +} + +void DebugMessageHelper::log(unsigned level, const char* msg) +{ +    DBG(level, "%s: %s\n", func_, msg); +} + +void DebugMessageHelper::vlog(unsigned level, const char* format, ...) +{ +    std::string msg; + +    std::va_list args; + +    va_start(args, format); +    int msg_len = std::vsnprintf(nullptr, 0, format, args); +    va_end(args); + +    if (msg_len < 0) { +        DBG(level, "%s: error formatting error message: %s\n", func_, format); +        return; +    } +    msg.resize(msg_len + 1, ' '); + +    va_start(args, format); +    std::vsnprintf(&msg.front(), msg.size(), format, args); +    va_end(args); + +    msg.resize(msg_len, ' '); // strip the null character + +    DBG(level, "%s: %s\n", func_, msg.c_str()); +} + +} // namespace genesys diff --git a/backend/genesys/error.h b/backend/genesys/error.h new file mode 100644 index 0000000..5aba8cf --- /dev/null +++ b/backend/genesys/error.h @@ -0,0 +1,199 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_ERROR_H +#define BACKEND_GENESYS_ERROR_H + +#include "../include/sane/config.h" +#include "../include/sane/sane.h" +#include "../include/sane/sanei_backend.h" + +#include <stdexcept> +#include <cstdarg> +#include <cstring> +#include <string> +#include <new> + +#define DBG_error0      0	/* errors/warnings printed even with devuglevel 0 */ +#define DBG_error       1	/* fatal errors */ +#define DBG_init        2	/* initialization and scanning time messages */ +#define DBG_warn        3	/* warnings and non-fatal errors */ +#define DBG_info        4	/* informational messages */ +#define DBG_proc        5	/* starting/finishing functions */ +#define DBG_io          6	/* io functions */ +#define DBG_io2         7	/* io functions that are called very often */ +#define DBG_data        8	/* log image data */ + +namespace genesys { + +class SaneException : public std::exception { +public: +    SaneException(SANE_Status status); +    SaneException(SANE_Status status, const char* format, ...) +    #ifdef __GNUC__ +        __attribute__((format(printf, 3, 4))) +    #endif +    ; + +    SaneException(const char* format, ...) +    #ifdef __GNUC__ +        __attribute__((format(printf, 2, 3))) +    #endif +    ; + +    SANE_Status status() const; +    const char* what() const noexcept override; + +private: + +    void set_msg(); +    void set_msg(const char* format, std::va_list vlist); + +    std::string msg_; +    SANE_Status status_; +}; + +// call a function and throw an exception on error +#define TIE(function)                                                                              \ +    do {                                                                                           \ +        SANE_Status tmp_status = function;                                                         \ +        if (tmp_status != SANE_STATUS_GOOD) {                                                      \ +            throw ::genesys::SaneException(tmp_status);                                            \ +        }                                                                                          \ +    } while (false) + +class DebugMessageHelper { +public: +    static constexpr unsigned MAX_BUF_SIZE = 120; + +    DebugMessageHelper(const char* func); +    DebugMessageHelper(const char* func, const char* format, ...) +    #ifdef __GNUC__ +        __attribute__((format(printf, 3, 4))) +    #endif +    ; + +    ~DebugMessageHelper(); + +    void status(const char* msg) { vstatus("%s", msg); } +    void vstatus(const char* format, ...) +    #ifdef __GNUC__ +        __attribute__((format(printf, 2, 3))) +    #endif +    ; + +    void clear() { msg_[0] = '\n'; } + +    void log(unsigned level, const char* msg); +    void vlog(unsigned level, const char* format, ...) +    #ifdef __GNUC__ +        __attribute__((format(printf, 3, 4))) +    #endif +    ; + +private: +    const char* func_ = nullptr; +    char msg_[MAX_BUF_SIZE]; +    unsigned num_exceptions_on_enter_ = 0; +}; + + +#if defined(__GNUC__) || defined(__clang__) +#define GENESYS_CURRENT_FUNCTION __PRETTY_FUNCTION__ +#elif defined(__FUNCSIG__) +#define GENESYS_CURRENT_FUNCTION __FUNCSIG__ +#else +#define GENESYS_CURRENT_FUNCTION __func__ +#endif + +#define DBG_HELPER(var) DebugMessageHelper var(GENESYS_CURRENT_FUNCTION) +#define DBG_HELPER_ARGS(var, ...) DebugMessageHelper var(GENESYS_CURRENT_FUNCTION, __VA_ARGS__) + +template<class F> +SANE_Status wrap_exceptions_to_status_code(const char* func, F&& function) +{ +    try { +        function(); +        return SANE_STATUS_GOOD; +    } catch (const SaneException& exc) { +        DBG(DBG_error, "%s: got error: %s\n", func, exc.what()); +        return exc.status(); +    } catch (const std::bad_alloc& exc) { +        (void) exc; +        DBG(DBG_error, "%s: failed to allocate memory\n", func); +        return SANE_STATUS_NO_MEM; +    } catch (const std::exception& exc) { +        DBG(DBG_error, "%s: got uncaught exception: %s\n", func, exc.what()); +        return SANE_STATUS_INVAL; +    } catch (...) { +        DBG(DBG_error, "%s: got unknown uncaught exception\n", func); +        return SANE_STATUS_INVAL; +    } +} + +template<class F> +void catch_all_exceptions(const char* func, F&& function) +{ +    try { +        function(); +    } catch (const SaneException& exc) { +        DBG(DBG_error, "%s: got exception: %s\n", func, exc.what()); +    } catch (const std::bad_alloc& exc) { +        DBG(DBG_error, "%s: got exception: could not allocate memory: %s\n", func, exc.what()); +    } catch (const std::exception& exc) { +        DBG(DBG_error, "%s: got uncaught exception: %s\n", func, exc.what()); +    } catch (...) { +        DBG(DBG_error, "%s: got unknown uncaught exception\n", func); +    } +} + +inline void wrap_status_code_to_exception(SANE_Status status) +{ +    if (status == SANE_STATUS_GOOD) +        return; +    throw SaneException(status); +} + +} // namespace genesys + +#endif // BACKEND_GENESYS_ERROR_H diff --git a/backend/genesys/fwd.h b/backend/genesys/fwd.h new file mode 100644 index 0000000..2d55f98 --- /dev/null +++ b/backend/genesys/fwd.h @@ -0,0 +1,132 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_FWD_H +#define BACKEND_GENESYS_FWD_H + +namespace genesys { + +// buffer.h +struct Genesys_Buffer; + +// calibration.h +struct Genesys_Calibration_Cache; + +// command_set.h +class CommandSet; + +// device.h +class FixedFloat; +struct Genesys_Gpo; +struct MethodResolutions; +struct Genesys_Model; +struct Genesys_Device; + +// error.h +class DebugMessageHelper; +class SaneException; + +// genesys.h +class GenesysButton; +struct Genesys_Scanner; + +// image.h +class Image; + +// image_buffer.h +class ImageBuffer; +class FakeBufferModel; +class ImageBufferGenesysUsb; + +// image_pipeline.h +class ImagePipelineNode; +// ImagePipelineNode* skipped +class ImagePipelineStack; + +// image_pixel.h +struct Pixel; +struct RawPixel; + +// low.h +struct Genesys_USB_Device_Entry; +struct Motor_Profile; + +// motor.h +struct Genesys_Motor; +struct MotorSlope; +struct MotorSlopeTable; + +// register.h +class Genesys_Register_Set; +struct GenesysRegisterSetState; + +// row_buffer.h +class RowBuffer; + +// usb_device.h +class IUsbDevice; +class UsbDevice; + +// scanner_interface.h +class ScannerInterface; +class ScannerInterfaceUsb; +class TestScannerInterface; + +// sensor.h +class ResolutionFilter; +struct GenesysFrontendLayout; +struct Genesys_Frontend; +struct SensorExposure; +struct Genesys_Sensor; + +// settings.h +struct Genesys_Settings; +struct SetupParams; +struct ScanSession; + +// test_usb_device.h +class TestUsbDevice; + +} // namespace genesys + +#endif diff --git a/backend/genesys/genesys.cpp b/backend/genesys/genesys.cpp new file mode 100644 index 0000000..7c25168 --- /dev/null +++ b/backend/genesys/genesys.cpp @@ -0,0 +1,6172 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de> +   Copyright (C) 2004-2016 Stéphane Voltz <stef.dev@free.fr> +   Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> +   Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com> +   Copyright (C) 2007 Luke <iceyfor@gmail.com> +   Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de> +                 for Plustek Opticbook 3600 support + +   Dynamic rasterization code was taken from the epjistsu backend by +   m. allan noah <kitno455 at gmail dot com> + +   Software processing for deskew, crop and dspeckle are inspired by allan's +   noah work in the fujitsu backend + +   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. +*/ + +/* + * SANE backend for Genesys Logic GL646/GL841/GL842/GL843/GL846/GL847/GL124 based scanners + */ + +#define DEBUG_NOT_STATIC + +#include "genesys.h" +#include "conv.h" +#include "gl124_registers.h" +#include "gl841_registers.h" +#include "gl843_registers.h" +#include "gl846_registers.h" +#include "gl847_registers.h" +#include "usb_device.h" +#include "utilities.h" +#include "scanner_interface_usb.h" +#include "test_scanner_interface.h" +#include "test_settings.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/sanei_magic.h" + +#include <array> +#include <cmath> +#include <cstring> +#include <fstream> +#include <iterator> +#include <list> +#include <numeric> +#include <exception> +#include <vector> + +#ifndef SANE_GENESYS_API_LINKAGE +#define SANE_GENESYS_API_LINKAGE extern "C" +#endif + +namespace genesys { + +// Data that we allocate to back SANE_Device objects in s_sane_devices +struct SANE_Device_Data +{ +    std::string name; +}; + +namespace { +    StaticInit<std::list<Genesys_Scanner>> s_scanners; +    StaticInit<std::vector<SANE_Device>> s_sane_devices; +    StaticInit<std::vector<SANE_Device_Data>> s_sane_devices_data; +    StaticInit<std::vector<SANE_Device*>> s_sane_devices_ptrs; +    StaticInit<std::list<Genesys_Device>> s_devices; + +    // Maximum time for lamp warm-up +    constexpr unsigned WARMUP_TIME = 65; +} // namespace + +static SANE_String_Const mode_list[] = { +  SANE_VALUE_SCAN_MODE_COLOR, +  SANE_VALUE_SCAN_MODE_GRAY, +  /* SANE_TITLE_HALFTONE,  currently unused */ +  SANE_VALUE_SCAN_MODE_LINEART, +    nullptr +}; + +static SANE_String_Const color_filter_list[] = { +  SANE_I18N ("Red"), +  SANE_I18N ("Green"), +  SANE_I18N ("Blue"), +    nullptr +}; + +static SANE_String_Const cis_color_filter_list[] = { +  SANE_I18N ("Red"), +  SANE_I18N ("Green"), +  SANE_I18N ("Blue"), +  SANE_I18N ("None"), +    nullptr +}; + +static SANE_Range swdespeck_range = { +  1, +  9, +  1 +}; + +static SANE_Range time_range = { +  0,				/* minimum */ +  60,				/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range u12_range = { +  0,				/* minimum */ +  4095,				/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range u14_range = { +  0,				/* minimum */ +  16383,			/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range u16_range = { +  0,				/* minimum */ +  65535,			/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range percentage_range = { +  SANE_FIX (0),			/* minimum */ +  SANE_FIX (100),		/* maximum */ +  SANE_FIX (1)			/* quantization */ +}; + +static const SANE_Range threshold_curve_range = { +  0,			/* minimum */ +  127,		        /* maximum */ +  1			/* quantization */ +}; + +/** + * range for brightness and contrast + */ +static const SANE_Range enhance_range = { +  -100,	/* minimum */ +  100,		/* maximum */ +  1		/* quantization */ +}; + +/** + * range for expiration time + */ +static const SANE_Range expiration_range = { +  -1,	        /* minimum */ +  30000,	/* maximum */ +  1		/* quantization */ +}; + +const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    for (const auto& sensor : *s_sensors) { +        if (dev->model->sensor_id == sensor.sensor_id) { +            return sensor; +        } +    } +    throw std::runtime_error("Given device does not have sensor defined"); +} + +Genesys_Sensor* find_sensor_impl(Genesys_Device* dev, unsigned dpi, unsigned channels, +                                 ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    for (auto& sensor : *s_sensors) { +        if (dev->model->sensor_id == sensor.sensor_id && sensor.resolutions.matches(dpi) && +            sensor.matches_channel_count(channels) && sensor.method == scan_method) +        { +            return &sensor; +        } +    } +    return nullptr; +} + +bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels, +                              ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    return find_sensor_impl(dev, dpi, channels, scan_method) != nullptr; +} + +const Genesys_Sensor& sanei_genesys_find_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels, +                                                ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    const auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method); +    if (sensor) +        return *sensor; +    throw std::runtime_error("Given device does not have sensor defined"); +} + +Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigned dpi, +                                                    unsigned channels, +                                                    ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method); +    if (sensor) +        return *sensor; +    throw std::runtime_error("Given device does not have sensor defined"); +} + + +std::vector<std::reference_wrapper<const Genesys_Sensor>> +    sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method)); +    std::vector<std::reference_wrapper<const Genesys_Sensor>> ret; +    for (const Genesys_Sensor& sensor : sanei_genesys_find_sensors_all_for_write(dev, scan_method)) { +        ret.push_back(sensor); +    } +    return ret; +} + +std::vector<std::reference_wrapper<Genesys_Sensor>> +    sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method)); +    std::vector<std::reference_wrapper<Genesys_Sensor>> ret; +    for (auto& sensor : *s_sensors) { +        if (dev->model->sensor_id == sensor.sensor_id && sensor.method == scan_method) { +            ret.push_back(sensor); +        } +    } +    return ret; +} + +void sanei_genesys_init_structs (Genesys_Device * dev) +{ +    DBG_HELPER(dbg); + +    bool gpo_ok = false; +    bool motor_ok = false; +    bool fe_ok = false; + +  /* initialize the GPO data stuff */ +    for (const auto& gpo : *s_gpo) { +        if (dev->model->gpio_id == gpo.id) { +            dev->gpo = gpo; +            gpo_ok = true; +            break; +        } +    } + +    // initialize the motor data stuff +    for (const auto& motor : *s_motors) { +        if (dev->model->motor_id == motor.id) { +            dev->motor = motor; +            motor_ok = true; +            break; +        } +    } + +    for (const auto& frontend : *s_frontends) { +        if (dev->model->adc_id == frontend.id) { +            dev->frontend_initial = frontend; +            dev->frontend = frontend; +            fe_ok = true; +            break; +        } +    } + +    if (!motor_ok || !gpo_ok || !fe_ok) { +        throw SaneException("bad description(s) for fe/gpo/motor=%d/%d/%d\n", +                            static_cast<unsigned>(dev->model->sensor_id), +                            static_cast<unsigned>(dev->model->gpio_id), +                            static_cast<unsigned>(dev->model->motor_id)); +    } +} + +/* Generate slope table for motor movement */ +/** + * This function generates a slope table using the slope from the motor struct + * truncated at the given exposure time or step count, whichever comes first. + * The summed time of the acceleration steps is returned, and the + * number of accerelation steps is put into used_steps. + * + * @param dev            Device struct + * @param slope_table    Table to write to + * @param step_type      Generate table for this step_type. 0=>full, 1=>half, + *                       2=>quarter + * @param exposure_time  Minimum exposure time of a scan line + * @param yres           Resolution of a scan line + * @param used_steps     Final number of steps is stored here + * @return               Motor slope table + * @note  all times in pixel time + */ +MotorSlopeTable sanei_genesys_create_slope_table3(AsicType asic_type, const Genesys_Motor& motor, +                                                  StepType step_type, int exposure_time, +                                                  unsigned yres) +{ +    unsigned target_speed_w = (exposure_time * yres) / motor.base_ydpi; + +    return create_slope_table(motor.get_slope(step_type), target_speed_w, step_type, 1, 1, +                              get_slope_table_max_size(asic_type)); +} + +/** @brief computes gamma table + * Generates a gamma table of the given length within 0 and the given + * maximum value + * @param gamma_table gamma table to fill + * @param size size of the table + * @param maximum value allowed for gamma + * @param gamma_max maximum gamma value + * @param gamma gamma to compute values + * @return a gamma table filled with the computed values + * */ +void +sanei_genesys_create_gamma_table (std::vector<uint16_t>& gamma_table, int size, +                                  float maximum, float gamma_max, float gamma) +{ +    gamma_table.clear(); +    gamma_table.resize(size, 0); + +  int i; +  float value; + +  DBG(DBG_proc, "%s: size = %d, ""maximum = %g, gamma_max = %g, gamma = %g\n", __func__, size, +      maximum, gamma_max, gamma); +  for (i = 0; i < size; i++) +    { +        value = static_cast<float>(gamma_max * std::pow(static_cast<double>(i) / size, 1.0 / gamma)); +        if (value > maximum) { +            value = maximum; +        } +        gamma_table[i] = static_cast<std::uint16_t>(value); +    } +  DBG(DBG_proc, "%s: completed\n", __func__); +} + +void sanei_genesys_create_default_gamma_table(Genesys_Device* dev, +                                              std::vector<uint16_t>& gamma_table, float gamma) +{ +    int size = 0; +    int max = 0; +    if (dev->model->asic_type == AsicType::GL646) { +        if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) { +            size = 16384; +        } else { +            size = 4096; +        } +        max = size - 1; +    } else if (dev->model->asic_type == AsicType::GL124 || +               dev->model->asic_type == AsicType::GL846 || +               dev->model->asic_type == AsicType::GL847) { +        size = 257; +        max = 65535; +    } else { +        size = 256; +        max = 65535; +    } +    sanei_genesys_create_gamma_table(gamma_table, size, max, max, gamma); +} + +/* computes the exposure_time on the basis of the given vertical dpi, +   the number of pixels the ccd needs to send, +   the step_type and the corresponding maximum speed from the motor struct */ +/* +  Currently considers maximum motor speed at given step_type, minimum +  line exposure needed for conversion and led exposure time. + +  TODO: Should also consider maximum transfer rate: ~6.5MB/s. +    Note: The enhance option of the scanners does _not_ help. It only halves +          the amount of pixels transfered. + */ +SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, float ydpi, +                                      StepType step_type, int endpixel, int exposure_by_led) +{ +  int exposure_by_ccd = endpixel + 32; +    unsigned max_speed_motor_w = dev->motor.get_slope(step_type).max_speed_w; +    int exposure_by_motor = static_cast<int>((max_speed_motor_w * dev->motor.base_ydpi) / ydpi); + +  int exposure = exposure_by_ccd; + +  if (exposure < exposure_by_motor) +    exposure = exposure_by_motor; + +  if (exposure < exposure_by_led && dev->model->is_cis) +    exposure = exposure_by_led; + +    DBG(DBG_info, "%s: ydpi=%d, step=%d, endpixel=%d led=%d => exposure=%d\n", __func__, +        static_cast<int>(ydpi), static_cast<unsigned>(step_type), endpixel, +        exposure_by_led, exposure); +  return exposure; +} + + +/* Sends a block of shading information to the scanner. +   The data is placed at address 0x0000 for color mode, gray mode and +   unconditionally for the following CCD chips: HP2300, HP2400 and HP5345 +   In the other cases (lineart, halftone on ccd chips not mentioned) the +   addresses are 0x2a00 for dpihw==0, 0x5500 for dpihw==1 and 0xa800 for +   dpihw==2. //Note: why this? + +   The data needs to be of size "size", and in little endian byte order. + */ +static void genesys_send_offset_and_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            uint8_t* data, int size) +{ +    DBG_HELPER_ARGS(dbg, "(size = %d)", size); +  int dpihw; +  int start_address; + +  /* ASIC higher than gl843 doesn't have register 2A/2B, so we route to +   * a per ASIC shading data loading function if available. +   * It is also used for scanners using SHDAREA */ +    if (dev->cmd_set->has_send_shading_data()) { +        dev->cmd_set->send_shading_data(dev, sensor, data, size); +        return; +    } + +  /* gl646, gl84[123] case */ +  dpihw = dev->reg.get8(0x05) >> 6; + +  /* TODO invert the test so only the 2 models behaving like that are +   * tested instead of adding all the others */ +  /* many scanners send coefficient for lineart/gray like in color mode */ +  if ((dev->settings.scan_mode == ScanColorMode::LINEART || +       dev->settings.scan_mode == ScanColorMode::HALFTONE) +        && dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICBOOK_3800 +        && dev->model->sensor_id != SensorId::CCD_KVSS080 +        && dev->model->sensor_id != SensorId::CCD_G4050 +        && dev->model->sensor_id != SensorId::CCD_HP_4850C +        && dev->model->sensor_id != SensorId::CCD_CANON_4400F +        && dev->model->sensor_id != SensorId::CCD_CANON_8400F +        && dev->model->sensor_id != SensorId::CCD_CANON_8600F +        && dev->model->sensor_id != SensorId::CCD_DSMOBILE600 +        && dev->model->sensor_id != SensorId::CCD_XP300 +        && dev->model->sensor_id != SensorId::CCD_DP665 +        && dev->model->sensor_id != SensorId::CCD_DP685 +        && dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80 +        && dev->model->sensor_id != SensorId::CCD_ROADWARRIOR +        && dev->model->sensor_id != SensorId::CCD_HP2300 +        && dev->model->sensor_id != SensorId::CCD_HP2400 +        && dev->model->sensor_id != SensorId::CCD_HP3670 +        && dev->model->sensor_id != SensorId::CCD_5345)	/* lineart, halftone */ +    { +        if (dpihw == 0) {		/* 600 dpi */ +            start_address = 0x02a00; +        } else if (dpihw == 1) {	/* 1200 dpi */ +            start_address = 0x05500; +        } else if (dpihw == 2) {	/* 2400 dpi */ +            start_address = 0x0a800; +        } else {			/* reserved */ +            throw SaneException("unknown dpihw"); +        } +    } +    else { // color +        start_address = 0x00; +    } + +    dev->interface->write_buffer(0x3c, start_address, data, size); +} + +// ? +void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                     int pixels_per_line) +{ +    DBG_HELPER_ARGS(dbg, "pixels_per_line: %d", pixels_per_line); + +    if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { +        return; +    } + +  int channels; +  int i; + +    if (dev->cmd_set->has_send_shading_data()) { +        return; +    } + +  DBG(DBG_proc, "%s (pixels_per_line = %d)\n", __func__, pixels_per_line); + +    // BUG: GRAY shouldn't probably be in the if condition below. Discovered when refactoring +    if (dev->settings.scan_mode == ScanColorMode::GRAY || +        dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +    { +        channels = 3; +    } else { +        channels = 1; +    } + +  // 16 bit black, 16 bit white +  std::vector<uint8_t> shading_data(pixels_per_line * 4 * channels, 0); + +  uint8_t* shading_data_ptr = shading_data.data(); + +  for (i = 0; i < pixels_per_line * channels; i++) +    { +      *shading_data_ptr++ = 0x00;	/* dark lo */ +      *shading_data_ptr++ = 0x00;	/* dark hi */ +      *shading_data_ptr++ = 0x00;	/* white lo */ +      *shading_data_ptr++ = 0x40;	/* white hi -> 0x4000 */ +    } + +    genesys_send_offset_and_shading(dev, sensor, shading_data.data(), +                                    pixels_per_line * 4 * channels); +} + + +// Find the position of the reference point: takes gray level 8 bits data and find +// first CCD usable pixel and top of scanning area +void sanei_genesys_search_reference_point(Genesys_Device* dev, Genesys_Sensor& sensor, +                                          const uint8_t* src_data, int start_pixel, int dpi, +                                          int width, int height) +{ +    DBG_HELPER(dbg); +  int x, y; +  int current, left, top = 0; +  int size, count; +  int level = 80;		/* edge threshold level */ + +    // sanity check +    if ((width < 3) || (height < 3)) { +        throw SaneException("invalid width or height"); +    } + +  /* transformed image data */ +  size = width * height; +  std::vector<uint8_t> image2(size, 0); +  std::vector<uint8_t> image(size, 0); + +  /* laplace filter to denoise picture */ +    std::memcpy(image2.data(), src_data, size); +    std::memcpy(image.data(), src_data, size);	// to initialize unprocessed part of the image buffer + +    for (y = 1; y < height - 1; y++) { +        for (x = 1; x < width - 1; x++) { +            image[y * width + x] = +                (image2[(y - 1) * width + x + 1] + 2 * image2[(y - 1) * width + x] + +                image2[(y - 1) * width + x - 1] + 2 * image2[y * width + x + 1] + +                4 * image2[y * width + x] + 2 * image2[y * width + x - 1] + +                image2[(y + 1) * width + x + 1] + 2 * image2[(y + 1) * width + x] + +                image2[(y + 1) * width + x - 1]) / 16; +        } +    } + +    image2 = image; +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_laplace.pnm", image.data(), 8, 1, width, height); + +  /* apply X direction sobel filter +     -1  0  1 +     -2  0  2 +     -1  0  1 +     and finds threshold level +   */ +  level = 0; +    for (y = 2; y < height - 2; y++) { +        for (x = 2; x < width - 2; x++) { +            current = image2[(y - 1) * width + x + 1] - image2[(y - 1) * width + x - 1] + +                      2 * image2[y * width + x + 1] - 2 * image2[y * width + x - 1] + +                      image2[(y + 1) * width + x + 1] - image2[(y + 1) * width + x - 1]; +	if (current < 0) +	  current = -current; +	if (current > 255) +	  current = 255; +	image[y * width + x] = current; +	if (current > level) +	  level = current; +        } +    } +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_xsobel.pnm", image.data(), 8, 1, width, height); + +  /* set up detection level */ +  level = level / 3; + +  /* find left black margin first +     todo: search top before left +     we average the result of N searches */ +  left = 0; +  count = 0; +  for (y = 2; y < 11; y++) +    { +      x = 8; +      while ((x < width / 2) && (image[y * width + x] < level)) +	{ +	  image[y * width + x] = 255; +	  x++; +	} +      count++; +      left += x; +    } +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_detected-xsobel.pnm", image.data(), 8, 1, width, height); +  left = left / count; + +    // turn it in CCD pixel at full sensor optical resolution +    sensor.ccd_start_xoffset = start_pixel + (left * sensor.optical_res) / dpi; + +  /* find top edge by detecting black strip */ +  /* apply Y direction sobel filter +     -1 -2 -1 +     0  0  0 +     1  2  1 +   */ +  level = 0; +    for (y = 2; y < height - 2; y++) { +        for (x = 2; x < width - 2; x++) { +            current = -image2[(y - 1) * width + x + 1] - 2 * image2[(y - 1) * width + x] - +                      image2[(y - 1) * width + x - 1] + image2[(y + 1) * width + x + 1] + +                      2 * image2[(y + 1) * width + x] + image2[(y + 1) * width + x - 1]; +	if (current < 0) +	  current = -current; +	if (current > 255) +	  current = 255; +	image[y * width + x] = current; +	if (current > level) +	  level = current; +      } +    } +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_ysobel.pnm", image.data(), 8, 1, width, height); + +  /* set up detection level */ +  level = level / 3; + +  /* search top of horizontal black stripe : TODO yet another flag */ +    if (dev->model->sensor_id == SensorId::CCD_5345 +        && dev->model->motor_id == MotorId::MD_5345) +    { +      top = 0; +      count = 0; +      for (x = width / 2; x < width - 1; x++) +	{ +	  y = 2; +	  while ((y < height) && (image[x + y * width] < level)) +	    { +	      image[y * width + x] = 255; +	      y++; +	    } +	  count++; +	  top += y; +	} +      if (DBG_LEVEL >= DBG_data) +        sanei_genesys_write_pnm_file("gl_detected-ysobel.pnm", image.data(), 8, 1, width, height); +      top = top / count; + +      /* bottom of black stripe is of fixed witdh, this hardcoded value +       * will be moved into device struct if more such values are needed */ +      top += 10; +        dev->model->y_offset_calib_white = (top * MM_PER_INCH) / dpi; +        DBG(DBG_info, "%s: black stripe y_offset = %f mm \n", __func__, +            dev->model->y_offset_calib_white.value()); +    } + +  /* find white corner in dark area : TODO yet another flag */ +    if ((dev->model->sensor_id == SensorId::CCD_HP2300 && dev->model->motor_id == MotorId::HP2300) || +        (dev->model->sensor_id == SensorId::CCD_HP2400 && dev->model->motor_id == MotorId::HP2400) || +        (dev->model->sensor_id == SensorId::CCD_HP3670 && dev->model->motor_id == MotorId::HP3670)) +    { +      top = 0; +      count = 0; +      for (x = 10; x < 60; x++) +	{ +	  y = 2; +	  while ((y < height) && (image[x + y * width] < level)) +	    y++; +	  top += y; +	  count++; +	} +      top = top / count; +        dev->model->y_offset_calib_white = (top * MM_PER_INCH) / dpi; +        DBG(DBG_info, "%s: white corner y_offset = %f mm\n", __func__, +            dev->model->y_offset_calib_white.value()); +    } + +    DBG(DBG_proc, "%s: ccd_start_xoffset = %d, left = %d, top = %d\n", __func__, +        sensor.ccd_start_xoffset, left, top); +} + +namespace gl843 { +    void gl843_park_xpa_lamp(Genesys_Device* dev); +    void gl843_set_xpa_motor_power(Genesys_Device* dev, Genesys_Register_Set& regs, bool set); +} // namespace gl843 + +namespace gl124 { +    void gl124_setup_scan_gpio(Genesys_Device* dev, int resolution); +} // namespace gl124 + +void scanner_clear_scan_and_feed_counts(Genesys_Device& dev) +{ +    switch (dev.model->asic_type) { +        case AsicType::GL843: { +            dev.interface->write_register(gl843::REG_0x0D, +                                          gl843::REG_0x0D_CLRLNCNT | gl843::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            dev.interface->write_register(gl846::REG_0x0D, +                                          gl846::REG_0x0D_CLRLNCNT | gl846::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL847:{ +            dev.interface->write_register(gl847::REG_0x0D, +                                          gl847::REG_0x0D_CLRLNCNT | gl847::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL124:{ +            dev.interface->write_register(gl124::REG_0x0D, +                                          gl124::REG_0x0D_CLRLNCNT | gl124::REG_0x0D_CLRMCNT); +            break; +        } +        default: +            throw SaneException("Unsupported asic type"); +    } +} + +void scanner_clear_scan_and_feed_counts2(Genesys_Device& dev) +{ +    // FIXME: switch to scanner_clear_scan_and_feed_counts when updating tests +    switch (dev.model->asic_type) { +        case AsicType::GL843: { +            dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            dev.interface->write_register(gl846::REG_0x0D, gl846::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl846::REG_0x0D, gl846::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL847: { +            dev.interface->write_register(gl847::REG_0x0D, gl847::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl847::REG_0x0D, gl847::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL124: { +            dev.interface->write_register(gl124::REG_0x0D, gl124::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl124::REG_0x0D, gl124::REG_0x0D_CLRMCNT); +            break; +        } +        default: +            throw SaneException("Unsupported asic type"); +    } +} + +bool scanner_is_motor_stopped(Genesys_Device& dev) +{ +    switch (dev.model->asic_type) { +        case AsicType::GL646: { +            auto status = scanner_read_status(dev); +            return !status.is_motor_enabled && status.is_feeding_finished; +        } +        case AsicType::GL841: { +            auto reg = dev.interface->read_register(gl841::REG_0x40); + +            return (!(reg & gl841::REG_0x40_DATAENB) && !(reg & gl841::REG_0x40_MOTMFLG)); +        } +        case AsicType::GL843: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl843::REG_0x40); + +            return (!(reg & gl843::REG_0x40_DATAENB) && !(reg & gl843::REG_0x40_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl846::REG_0x40); + +            return (!(reg & gl846::REG_0x40_DATAENB) && !(reg & gl846::REG_0x40_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        case AsicType::GL847: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl847::REG_0x40); + +            return (!(reg & gl847::REG_0x40_DATAENB) && !(reg & gl847::REG_0x40_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        case AsicType::GL124: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl124::REG_0x100); + +            return (!(reg & gl124::REG_0x100_DATAENB) && !(reg & gl124::REG_0x100_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        default: +            throw SaneException("Unsupported asic type"); +    } +} + +void scanner_stop_action(Genesys_Device& dev) +{ +    DBG_HELPER(dbg); + +    switch (dev.model->asic_type) { +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    if (dev.cmd_set->needs_update_home_sensor_gpio()) { +        dev.cmd_set->update_home_sensor_gpio(dev); +    } + +    if (scanner_is_motor_stopped(dev)) { +        DBG(DBG_info, "%s: already stopped\n", __func__); +        return; +    } + +    scanner_stop_action_no_move(dev, dev.reg); + +    if (is_testing_mode()) { +        return; +    } + +    for (unsigned i = 0; i < 10; ++i) { +        if (scanner_is_motor_stopped(dev)) { +            return; +        } + +        dev.interface->sleep_ms(100); +    } + +    throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor"); +} + +void scanner_stop_action_no_move(Genesys_Device& dev, genesys::Genesys_Register_Set& regs) +{ +    switch (dev.model->asic_type) { +        case AsicType::GL646: +        case AsicType::GL841: +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    regs_set_optical_off(dev.model->asic_type, regs); +    // same across all supported ASICs +    dev.interface->write_register(0x01, regs.get8(0x01)); + +    // looks like certain scanners lock up if we try to scan immediately after stopping previous +    // action. +    dev.interface->sleep_ms(100); +} + +void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, Direction direction) +{ +    DBG_HELPER_ARGS(dbg, "steps=%d direction=%d", steps, static_cast<unsigned>(direction)); + +    auto local_reg = dev.reg; + +    unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y(); + +    const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 3, scan_method); + +    bool uses_secondary_head = (scan_method == ScanMethod::TRANSPARENCY || +                                scan_method == ScanMethod::TRANSPARENCY_INFRARED); +    bool uses_secondary_pos = uses_secondary_head && +                              dev.model->default_method == ScanMethod::FLATBED; + +    if (!dev.is_head_pos_known(ScanHeadId::PRIMARY)) { +        throw SaneException("Unknown head position"); +    } +    if (uses_secondary_pos && !dev.is_head_pos_known(ScanHeadId::SECONDARY)) { +        throw SaneException("Unknown head position"); +    } +    if (direction == Direction::BACKWARD && steps > dev.head_pos(ScanHeadId::PRIMARY)) { +        throw SaneException("Trying to feed behind the home position %d %d", +                            steps, dev.head_pos(ScanHeadId::PRIMARY)); +    } +    if (uses_secondary_pos && direction == Direction::BACKWARD && +        steps > dev.head_pos(ScanHeadId::SECONDARY)) +    { +        throw SaneException("Trying to feed behind the home position %d %d", +                            steps, dev.head_pos(ScanHeadId::SECONDARY)); +    } + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = steps; +    session.params.pixels = 100; +    session.params.lines = 3; +    session.params.depth = 8; +    session.params.channels = 3; +    session.params.scan_method = scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.color_filter = ColorFilter::RED; +    } else { +        session.params.color_filter = dev.settings.color_filter; +    } +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::FEEDING | +                           ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev.model->asic_type == AsicType::GL124) { +        session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    } + +    if (direction == Direction::BACKWARD) { +        session.params.flags |= ScanFlag::REVERSE; +    } + +    compute_session(&dev, session, sensor); + +    dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + +    if (dev.model->asic_type != AsicType::GL843) { +        regs_set_exposure(dev.model->asic_type, local_reg, {0, 0, 0}); +    } +    scanner_clear_scan_and_feed_counts2(dev); + +    dev.interface->write_registers(local_reg); +    if (uses_secondary_head) { +        gl843::gl843_set_xpa_motor_power(&dev, local_reg, true); +    } + +    try { +        scanner_start_action(dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { +            gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +        }); +        catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); }); +        throw; +    } + +    if (is_testing_mode()) { +        dev.interface->test_checkpoint("feed"); + +        dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps); +        if (uses_secondary_pos) { +            dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps); +        } + +        // FIXME: why don't we stop the scanner like on other ASICs +        if (dev.model->asic_type != AsicType::GL843) { +            scanner_stop_action(dev); +        } +        if (uses_secondary_head) { +            gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +        } +        return; +    } + +    // wait until feed count reaches the required value +    // FIXME: should porbably wait for some timeout +    Status status; +    for (unsigned i = 0;; ++i) { +        status = scanner_read_status(dev); +        if (status.is_feeding_finished || ( +            direction == Direction::BACKWARD && status.is_at_home)) +        { +            break; +        } +        dev.interface->sleep_ms(10); +    } + +    // FIXME: why don't we stop the scanner like on other ASICs +    if (dev.model->asic_type != AsicType::GL843) { +        scanner_stop_action(dev); +    } +    if (uses_secondary_head) { +        gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +    } + +    dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps); +    if (uses_secondary_pos) { +        dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps); +    } + +    // looks like certain scanners lock up if we scan immediately after feeding +    dev.interface->sleep_ms(100); +} + +void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) +{ +    DBG_HELPER_ARGS(dbg, "wait_until_home = %d", wait_until_home); + +    switch (dev.model->asic_type) { +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    // FIXME: also check whether the scanner actually has a secondary head +    if (!dev.is_head_pos_known(ScanHeadId::SECONDARY) || +        dev.head_pos(ScanHeadId::SECONDARY) > 0 || +        dev.settings.scan_method == ScanMethod::TRANSPARENCY || +        dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        scanner_move_back_home_ta(dev); +    } + +    if (dev.is_head_pos_known(ScanHeadId::PRIMARY) && +        dev.head_pos(ScanHeadId::PRIMARY) > 1000) +    { +        // leave 500 steps for regular slow back home +        scanner_move(dev, dev.model->default_method, dev.head_pos(ScanHeadId::PRIMARY) - 500, +                     Direction::BACKWARD); +    } + +    if (dev.cmd_set->needs_update_home_sensor_gpio()) { +        dev.cmd_set->update_home_sensor_gpio(dev); +    } + +    auto status = scanner_read_reliable_status(dev); + +    if (status.is_at_home) { +        dbg.log(DBG_info, "already at home"); +        dev.set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +    if (dev.model->model_id == ModelId::CANON_LIDE_210) { +        // move the head back a little first +        if (dev.is_head_pos_known(ScanHeadId::PRIMARY) && +            dev.head_pos(ScanHeadId::PRIMARY) > 30) +        { +            scanner_move(dev, dev.model->default_method, 20, Direction::BACKWARD); +        } +    } + +    Genesys_Register_Set local_reg = dev.reg; +    unsigned resolution = sanei_genesys_get_lowest_ydpi(&dev); + +    const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, dev.model->default_method); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 100; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.starty = 40000; +    } else { +        session.params.starty = 30000; +    } +    session.params.pixels = 100; +    session.params.lines = 100; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev.settings.scan_method; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.scan_mode = ScanColorMode::LINEART; +        session.params.color_filter = dev.settings.color_filter; +    } else { +        session.params.scan_mode = ScanColorMode::GRAY; +        session.params.color_filter = ColorFilter::RED; +    } +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::IGNORE_LINE_DISTANCE | +                            ScanFlag::REVERSE; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    } + +    compute_session(&dev, session, sensor); + +    dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + +    scanner_clear_scan_and_feed_counts(dev); + +    dev.interface->write_registers(local_reg); + +    if (dev.model->asic_type == AsicType::GL124) { +        gl124::gl124_setup_scan_gpio(&dev, resolution); +    } + +    try { +        scanner_start_action(dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() +        { +            dev.interface->write_registers(dev.reg); +        }); +        throw; +    } + +    if (dev.cmd_set->needs_update_home_sensor_gpio()) { +        dev.cmd_set->update_home_sensor_gpio(dev); +    } + +    if (is_testing_mode()) { +        dev.interface->test_checkpoint("move_back_home"); +        dev.set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +    if (wait_until_home) { +        for (unsigned i = 0; i < 300; ++i) { +            auto status = scanner_read_status(dev); + +            if (status.is_at_home) { +                dbg.log(DBG_info, "reached home position"); +                if (dev.model->asic_type == AsicType::GL846 || +                    dev.model->asic_type == AsicType::GL847) +                { +                    scanner_stop_action(dev); +                } +                dev.set_head_pos_zero(ScanHeadId::PRIMARY); +                return; +            } + +            dev.interface->sleep_ms(100); +        } + +        // when we come here then the scanner needed too much time for this, so we better stop +        // the motor +        catch_all_exceptions(__func__, [&](){ scanner_stop_action(dev); }); +        dev.set_head_pos_unknown(); +        throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home"); +    } +    dbg.log(DBG_info, "scanhead is still moving"); +} + +void scanner_move_back_home_ta(Genesys_Device& dev) +{ +    DBG_HELPER(dbg); + +    switch (dev.model->asic_type) { +        case AsicType::GL843: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    Genesys_Register_Set local_reg = dev.reg; + +    auto scan_method = ScanMethod::TRANSPARENCY; +    unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y(); + +    const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, scan_method); + +    if (dev.is_head_pos_known(ScanHeadId::SECONDARY) && +        dev.head_pos(ScanHeadId::SECONDARY) > 1000) +    { +        // leave 500 steps for regular slow back home +        scanner_move(dev, scan_method, dev.head_pos(ScanHeadId::SECONDARY) - 500, +                     Direction::BACKWARD); +    } + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 100; +    session.params.starty = 30000; +    session.params.pixels = 100; +    session.params.lines = 100; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::IGNORE_LINE_DISTANCE | +                            ScanFlag::REVERSE; + +    compute_session(&dev, session, sensor); + +    dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + +    scanner_clear_scan_and_feed_counts(dev); + +    dev.interface->write_registers(local_reg); +    gl843::gl843_set_xpa_motor_power(&dev, local_reg, true); + +    try { +        scanner_start_action(dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); }); +        throw; +    } + +    if (is_testing_mode()) { +        dev.interface->test_checkpoint("move_back_home_ta"); + +        if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) { +            if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) { +                dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD, +                                              dev.head_pos(ScanHeadId::SECONDARY)); +            } else { +                dev.set_head_pos_zero(ScanHeadId::PRIMARY); +            } +            dev.set_head_pos_zero(ScanHeadId::SECONDARY); +        } + +        scanner_stop_action(dev); +        gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +        return; +    } + +    for (unsigned i = 0; i < 1200; ++i) { + +        auto status = scanner_read_status(dev); + +        if (status.is_at_home) { +            dbg.log(DBG_info, "TA reached home position"); + +            if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) { +                if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) { +                    dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD, +                                                  dev.head_pos(ScanHeadId::SECONDARY)); +                } else { +                    dev.set_head_pos_zero(ScanHeadId::PRIMARY); +                } +                dev.set_head_pos_zero(ScanHeadId::SECONDARY); +            } + +            scanner_stop_action(dev); +            gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +            return; +        } + +        dev.interface->sleep_ms(100); +    } + +    throw SaneException("Timeout waiting for XPA lamp to park"); +} + +void sanei_genesys_calculate_zmod(bool two_table, +                                  uint32_t exposure_time, +                                  const std::vector<uint16_t>& slope_table, +                                  unsigned acceleration_steps, +                                  unsigned move_steps, +                                  unsigned buffer_acceleration_steps, +                                  uint32_t* out_z1, uint32_t* out_z2) +{ +    DBG(DBG_info, "%s: two_table=%d\n", __func__, two_table); + +    // acceleration total time +    unsigned sum = std::accumulate(slope_table.begin(), slope_table.begin() + acceleration_steps, +                                   0, std::plus<unsigned>()); + +    /* Z1MOD: +        c = sum(slope_table; reg_stepno) +        d = reg_fwdstep * <cruising speed> +        Z1MOD = (c+d) % exposure_time +    */ +    *out_z1 = (sum + buffer_acceleration_steps * slope_table[acceleration_steps - 1]) % exposure_time; + +    /* Z2MOD: +        a = sum(slope_table; reg_stepno) +        b = move_steps or 1 if 2 tables +        Z1MOD = (a+b) % exposure_time +    */ +    if (!two_table) { +        sum = sum + (move_steps * slope_table[acceleration_steps - 1]); +    } else { +        sum = sum + slope_table[acceleration_steps - 1]; +    } +    *out_z2 = sum % exposure_time; +} + +static uint8_t genesys_adjust_gain(double* applied_multi, double multi, uint8_t gain) +{ +  double voltage, original_voltage; +  uint8_t new_gain = 0; + +  DBG(DBG_proc, "%s: multi=%f, gain=%d\n", __func__, multi, gain); + +  voltage = 0.5 + gain * 0.25; +  original_voltage = voltage; + +  voltage *= multi; + +    new_gain = static_cast<std::uint8_t>((voltage - 0.5) * 4); +  if (new_gain > 0x0e) +    new_gain = 0x0e; + +  voltage = 0.5 + (new_gain) * 0.25; + +  *applied_multi = voltage / original_voltage; + +  DBG(DBG_proc, "%s: orig voltage=%.2f, new voltage=%.2f, *applied_multi=%f, new_gain=%d\n", +      __func__, original_voltage, voltage, *applied_multi, new_gain); + +  return new_gain; +} + + +// todo: is return status necessary (unchecked?) +static void genesys_average_white(Genesys_Device* dev, Genesys_Sensor& sensor, int channels, +                                  int channel, uint8_t* data, int size, int *max_average) +{ + +    DBG_HELPER_ARGS(dbg, "channels=%d, channel=%d, size=%d", channels, channel, size); +  int gain_white_ref, sum, range; +  int average; +  int i; + +  range = size / 50; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        gain_white_ref = sensor.fau_gain_white_ref * 256; +    } else { +        gain_white_ref = sensor.gain_white_ref * 256; +    } + +  if (range < 1) +    range = 1; + +  size = size / (2 * range * channels); + +  data += (channel * 2); + +  *max_average = 0; + +  while (size--) +    { +      sum = 0; +      for (i = 0; i < range; i++) +	{ +	  sum += (*data); +	  sum += *(data + 1) * 256; +	  data += (2 * channels);	/* byte based */ +	} + +      average = (sum / range); +      if (average > *max_average) +	*max_average = average; +    } + +  DBG(DBG_proc, "%s: max_average=%d, gain_white_ref = %d, finished\n", __func__, *max_average, +      gain_white_ref); + +  if (*max_average >= gain_white_ref) +        throw SaneException(SANE_STATUS_INVAL); +} + +/* todo: understand, values are too high */ +static int +genesys_average_black (Genesys_Device * dev, int channel, +		       uint8_t * data, int pixels) +{ +  int i; +  int sum; +  int pixel_step; + +  DBG(DBG_proc, "%s: channel=%d, pixels=%d\n", __func__, channel, pixels); + +  sum = 0; + +  if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +    { +      data += (channel * 2); +      pixel_step = 3 * 2; +    } +  else +    { +      pixel_step = 2; +    } + +  for (i = 0; i < pixels; i++) +    { +      sum += *data; +      sum += *(data + 1) * 256; + +      data += pixel_step; +    } + +  DBG(DBG_proc, "%s = %d\n", __func__, sum / pixels); + +    return sum / pixels; +} + + +// todo: check; it works but the lines 1, 2, and 3 are too dark even with the +// same offset and gain settings? +static void genesys_coarse_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER_ARGS(dbg, "scan_mode = %d", static_cast<unsigned>(dev->settings.scan_mode)); +  int black_pixels; +  int white_average; +  uint8_t offset[4] = { 0xa0, 0x00, 0xa0, 0x40 };	/* first value isn't used */ +  uint16_t white[12], dark[12]; +  int i, j; + +  black_pixels = sensor.black_pixels +    * dev->settings.xres / sensor.optical_res; + +    unsigned channels = dev->settings.get_channels(); + +  DBG(DBG_info, "channels %d y_size %f xres %d\n", channels, dev->model->y_size.value(), +      dev->settings.xres); +    unsigned size = static_cast<unsigned>(channels * 2 * dev->model->y_size * dev->settings.xres / +                                          MM_PER_INCH); +  /*       1        1               mm                      1/inch        inch/mm */ + +  std::vector<uint8_t> calibration_data(size); +  std::vector<uint8_t> all_data(size * 4, 1); + +    dev->cmd_set->set_fe(dev, sensor, AFE_INIT); + +  dev->frontend.set_gain(0, 2); +  dev->frontend.set_gain(1, 2); +  dev->frontend.set_gain(2, 2); // TODO: ?  was 2 +  dev->frontend.set_offset(0, offset[0]); +  dev->frontend.set_offset(1, offset[0]); +  dev->frontend.set_offset(2, offset[0]); + +  for (i = 0; i < 4; i++)	/* read 4 lines */ +    { +      if (i < 3)		/* first 3 lines */ +        { +          dev->frontend.set_offset(0, offset[i]); +          dev->frontend.set_offset(1, offset[i]); +          dev->frontend.set_offset(2, offset[i]); +        } + +      if (i == 1)		/* second line */ +	{ +	  double applied_multi; +	  double gain_white_ref; + +            if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +                dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +            { +                gain_white_ref = sensor.fau_gain_white_ref * 256; +            } else { +                gain_white_ref = sensor.gain_white_ref * 256; +            } + +            // white and black are defined downwards + +            uint8_t gain0 = genesys_adjust_gain(&applied_multi, +                                                gain_white_ref / (white[0] - dark[0]), +                                                dev->frontend.get_gain(0)); +            uint8_t gain1 = genesys_adjust_gain(&applied_multi, +                                                gain_white_ref / (white[1] - dark[1]), +                                                dev->frontend.get_gain(1)); +            uint8_t gain2 = genesys_adjust_gain(&applied_multi, +                                                gain_white_ref / (white[2] - dark[2]), +                                                dev->frontend.get_gain(2)); +            // FIXME: looks like overwritten data. Are the above calculations doing +            // anything at all? +            dev->frontend.set_gain(0, gain0); +            dev->frontend.set_gain(1, gain1); +            dev->frontend.set_gain(2, gain2); +            dev->frontend.set_gain(0, 2); +            dev->frontend.set_gain(1, 2); +            dev->frontend.set_gain(2, 2); + +            dev->interface->write_fe_register(0x28, dev->frontend.get_gain(0)); +            dev->interface->write_fe_register(0x29, dev->frontend.get_gain(1)); +            dev->interface->write_fe_register(0x2a, dev->frontend.get_gain(2)); +	} + +      if (i == 3)		/* last line */ +	{ +	  double x, y, rate; + +	  for (j = 0; j < 3; j++) +	    { + +                x = static_cast<double>(dark[(i - 2) * 3 + j] - +			  dark[(i - 1) * 3 + j]) * 254 / (offset[i - 1] / 2 - +							  offset[i - 2] / 2); +	      y = x - x * (offset[i - 1] / 2) / 254 - dark[(i - 1) * 3 + j]; +	      rate = (x - DARK_VALUE - y) * 254 / x + 0.5; + +                uint8_t curr_offset = static_cast<uint8_t>(rate); + +                if (curr_offset > 0x7f) { +                    curr_offset = 0x7f; +                } +                curr_offset <<= 1; +                dev->frontend.set_offset(j, curr_offset); +	    } +	} +        dev->interface->write_fe_register(0x20, dev->frontend.get_offset(0)); +        dev->interface->write_fe_register(0x21, dev->frontend.get_offset(1)); +        dev->interface->write_fe_register(0x22, dev->frontend.get_offset(2)); + +      DBG(DBG_info, +          "%s: doing scan: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__, +          dev->frontend.get_gain(0), +          dev->frontend.get_gain(1), +          dev->frontend.get_gain(2), +          dev->frontend.get_offset(0), +          dev->frontend.get_offset(1), +          dev->frontend.get_offset(2)); + + +        dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("coarse_calibration"); +            dev->cmd_set->end_scan(dev, &dev->calib_reg, true); +            return; +        } + +      sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size); +      std::memcpy(all_data.data() + i * size, calibration_data.data(), size); +      if (i == 3)		/* last line */ +	{ +          std::vector<uint8_t> all_data_8(size * 4 / 2); +	  unsigned int count; + +        for (count = 0; count < static_cast<unsigned>(size * 4 / 2); count++) { +            all_data_8[count] = all_data[count * 2 + 1]; +        } +        sanei_genesys_write_pnm_file("gl_coarse.pnm", all_data_8.data(), 8, channels, size / 6, 4); +	} + +        dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + +      if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +	{ +	  for (j = 0; j < 3; j++) +	    { +            genesys_average_white(dev, sensor, 3, j, calibration_data.data(), size, &white_average); +	      white[i * 3 + j] = white_average; +	      dark[i * 3 + j] = +        genesys_average_black (dev, j, calibration_data.data(), +				       black_pixels); +              DBG(DBG_info, "%s: white[%d]=%d, black[%d]=%d\n", __func__, +                  i * 3 + j, white[i * 3 + j], i * 3 + j, dark[i * 3 + j]); +	    } +	} +      else			/* one color-component modes */ +	{ +        genesys_average_white(dev, sensor, 1, 0, calibration_data.data(), size, &white_average); +	  white[i * 3 + 0] = white[i * 3 + 1] = white[i * 3 + 2] = +	    white_average; +	  dark[i * 3 + 0] = dark[i * 3 + 1] = dark[i * 3 + 2] = +        genesys_average_black (dev, 0, calibration_data.data(), black_pixels); +	} +    }				/* for (i = 0; i < 4; i++) */ + +  DBG(DBG_info, "%s: final: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__, +      dev->frontend.get_gain(0), +      dev->frontend.get_gain(1), +      dev->frontend.get_gain(2), +      dev->frontend.get_offset(0), +      dev->frontend.get_offset(1), +      dev->frontend.get_offset(2)); +} + +/** + * scans a white area with motor and lamp off to get the per CCD pixel offset + * that will be used to compute shading coefficient + * @param dev scanner's device + */ +static void genesys_shading_calibration_impl(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                             std::vector<std::uint16_t>& out_average_data, +                                             bool is_dark, const std::string& log_filename_prefix) +{ +    DBG_HELPER(dbg); + +    debug_dump(DBG_info, dev->calib_session); + +  size_t size; +  uint32_t pixels_per_line; +  uint8_t channels; + +  /* end pixel - start pixel */ +  pixels_per_line = dev->calib_pixels; +  channels = dev->calib_channels; + +  uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + +    // FIXME: we set this during both dark and white calibration. A cleaner approach should +    // probably be used +    dev->average_size = channels * out_pixels_per_line; + +    out_average_data.clear(); +    out_average_data.resize(dev->average_size); + +    if (is_dark && dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { +        // FIXME: dark shading currently not supported on infrared transparency scans +        return; +    } + +    // FIXME: the current calculation is likely incorrect on non-GL843 implementations, +    // but this needs checking +    if (dev->calib_total_bytes_to_read > 0) { +        size = dev->calib_total_bytes_to_read; +    } else if (dev->model->asic_type == AsicType::GL843) { +        size = channels * 2 * pixels_per_line * dev->calib_lines; +    } else { +        size = channels * 2 * pixels_per_line * (dev->calib_lines + 1); +    } + +  std::vector<uint16_t> calibration_data(size / 2); + +    bool motor = true; +  if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE) +    { +        motor = false; +    } + +    // turn off motor and lamp power for flatbed scanners, but not for sheetfed scanners +    // because they have a calibration sheet with a sufficient black strip +    if (is_dark && !dev->model->is_sheetfed) { +        sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, false); +        sanei_genesys_set_motor_power(dev->calib_reg, motor); +    } else { +        sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true); +        sanei_genesys_set_motor_power(dev->calib_reg, motor); +    } + +    dev->interface->write_registers(dev->calib_reg); + +    if (is_dark) { +        // wait some time to let lamp to get dark +        dev->interface->sleep_ms(200); +    } else if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) { +        // make sure lamp is bright again +        // FIXME: what about scanners that take a long time to warm the lamp? +        dev->interface->sleep_ms(500); +    } + +    bool start_motor = !is_dark; +    dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, start_motor); + + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint(is_dark ? "dark_shading_calibration" +                                                : "white_shading_calibration"); +        dev->cmd_set->end_scan(dev, &dev->calib_reg, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, reinterpret_cast<std::uint8_t*>(calibration_data.data()), +                                         size); + +    dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + +    if (dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) { +        for (std::size_t i = 0; i < size / 2; ++i) { +            auto value = calibration_data[i]; +            value = ((value >> 8) & 0xff) | ((value << 8) & 0xff00); +            calibration_data[i] = value; +        } +    } + +    std::fill(out_average_data.begin(), +              out_average_data.begin() + dev->calib_pixels_offset * channels, 0); + +    compute_array_percentile_approx(out_average_data.data() + dev->calib_pixels_offset * channels, +                                    calibration_data.data(), +                                    dev->calib_lines, pixels_per_line * channels, +                                    0.5f); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file16((log_filename_prefix + "_shading.pnm").c_str(), +                                       calibration_data.data(), +                                       channels, pixels_per_line, dev->calib_lines); +        sanei_genesys_write_pnm_file16((log_filename_prefix + "_average.pnm").c_str(), +                                       out_average_data.data(), +                                       channels, out_pixels_per_line, 1); +    } +} + + +static void genesys_dark_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    genesys_shading_calibration_impl(dev, sensor, dev->dark_average_data, true, "gl_black_"); +} +/* + * this function builds dummy dark calibration data so that we can + * compute shading coefficient in a clean way + *  todo: current values are hardcoded, we have to find if they + * can be computed from previous calibration data (when doing offset + * calibration ?) + */ +static void genesys_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +  uint32_t pixels_per_line; +  uint8_t channels; +  uint32_t skip, xend; +  int dummy1, dummy2, dummy3;	/* dummy black average per channel */ + +  pixels_per_line = dev->calib_pixels; +  channels = dev->calib_channels; + +  uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + +    dev->average_size = channels * out_pixels_per_line; +  dev->dark_average_data.clear(); +  dev->dark_average_data.resize(dev->average_size, 0); + +  /* we average values on 'the left' where CCD pixels are under casing and +     give darkest values. We then use these as dummy dark calibration */ +  if (dev->settings.xres <= sensor.optical_res / 2) +    { +      skip = 4; +      xend = 36; +    } +  else +    { +      skip = 4; +      xend = 68; +    } +    if (dev->model->sensor_id==SensorId::CCD_G4050 || +        dev->model->sensor_id==SensorId::CCD_HP_4850C +     || dev->model->sensor_id==SensorId::CCD_CANON_4400F +     || dev->model->sensor_id==SensorId::CCD_CANON_8400F +     || dev->model->sensor_id==SensorId::CCD_KVSS080) +    { +      skip = 2; +      xend = sensor.black_pixels; +    } + +  /* average each channels on half left margin */ +  dummy1 = 0; +  dummy2 = 0; +  dummy3 = 0; + +    for (unsigned x = skip + 1; x <= xend; x++) { +        dummy1 += dev->white_average_data[channels * x]; +        if (channels > 1) { +            dummy2 += dev->white_average_data[channels * x + 1]; +            dummy3 += dev->white_average_data[channels * x + 2]; +        } +    } + +  dummy1 /= (xend - skip); +  if (channels > 1) +    { +      dummy2 /= (xend - skip); +      dummy3 /= (xend - skip); +    } +  DBG(DBG_proc, "%s: dummy1=%d, dummy2=%d, dummy3=%d \n", __func__, dummy1, dummy2, dummy3); + +  /* fill dark_average */ +    for (unsigned x = 0; x < out_pixels_per_line; x++) { +        dev->dark_average_data[channels * x] = dummy1; +        if (channels > 1) { +            dev->dark_average_data[channels * x + 1] = dummy2; +            dev->dark_average_data[channels * x + 2] = dummy3; +        } +    } +} + + +static void genesys_repark_sensor_before_shading(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) { +        dev->cmd_set->move_back_home(dev, true); + +        if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +            dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +        { +            dev->cmd_set->move_to_ta(dev); +        } +    } +} + +static void genesys_repark_sensor_after_white_shading(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) { +        dev->cmd_set->move_back_home(dev, true); +    } +} + +static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    genesys_shading_calibration_impl(dev, sensor, dev->white_average_data, false, "gl_white_"); +} + +// This calibration uses a scan over the calibration target, comprising a black and a white strip. +// (So the motor must be on.) +static void genesys_dark_white_shading_calibration(Genesys_Device* dev, +                                                   const Genesys_Sensor& sensor) +{ +    DBG_HELPER_ARGS(dbg, "lines = %zu", dev->calib_lines); +  size_t size; +  uint32_t pixels_per_line; +  uint8_t channels; +  unsigned int x; +  uint32_t dark, white, dark_sum, white_sum, dark_count, white_count, col, +    dif; + +  pixels_per_line = dev->calib_pixels; +  channels = dev->calib_channels; + +  uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + +    dev->average_size = channels * out_pixels_per_line; + +  dev->white_average_data.clear(); +  dev->white_average_data.resize(dev->average_size); + +  dev->dark_average_data.clear(); +  dev->dark_average_data.resize(dev->average_size); + +  if (dev->calib_total_bytes_to_read > 0) +    size = dev->calib_total_bytes_to_read; +  else +    size = channels * 2 * pixels_per_line * dev->calib_lines; + +  std::vector<uint8_t> calibration_data(size); + +    bool motor = true; +  if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE) +    { +        motor = false; +    } + +    // turn on motor and lamp power +    sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true); +    sanei_genesys_set_motor_power(dev->calib_reg, motor); + +    dev->interface->write_registers(dev->calib_reg); + +    dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("dark_white_shading_calibration"); +        dev->cmd_set->end_scan(dev, &dev->calib_reg, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size); + +    dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + +  if (DBG_LEVEL >= DBG_data) +    { +      if (dev->model->is_cis) +        { +          sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(), +                                       16, 1, pixels_per_line*channels, +                                       dev->calib_lines); +        } +      else +        { +          sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(), +                                       16, channels, pixels_per_line, +                                       dev->calib_lines); +        } +    } + + +    std::fill(dev->dark_average_data.begin(), +              dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0); +    std::fill(dev->white_average_data.begin(), +              dev->white_average_data.begin() + dev->calib_pixels_offset * channels, 0); + +    uint16_t* average_white = dev->white_average_data.data() + dev->calib_pixels_offset * channels; +    uint16_t* average_dark = dev->dark_average_data.data() + dev->calib_pixels_offset * channels; + +  for (x = 0; x < pixels_per_line * channels; x++) +    { +      dark = 0xffff; +      white = 0; + +            for (std::size_t y = 0; y < dev->calib_lines; y++) +	{ +	  col = calibration_data[(x + y * pixels_per_line * channels) * 2]; +	  col |= +	    calibration_data[(x + y * pixels_per_line * channels) * 2 + +			     1] << 8; + +	  if (col > white) +	    white = col; +	  if (col < dark) +	    dark = col; +	} + +      dif = white - dark; + +      dark = dark + dif / 8; +      white = white - dif / 8; + +      dark_count = 0; +      dark_sum = 0; + +      white_count = 0; +      white_sum = 0; + +            for (std::size_t y = 0; y < dev->calib_lines; y++) +	{ +	  col = calibration_data[(x + y * pixels_per_line * channels) * 2]; +	  col |= +	    calibration_data[(x + y * pixels_per_line * channels) * 2 + +			     1] << 8; + +	  if (col >= white) +	    { +	      white_sum += col; +	      white_count++; +	    } +	  if (col <= dark) +	    { +	      dark_sum += col; +	      dark_count++; +	    } + +	} + +      dark_sum /= dark_count; +      white_sum /= white_count; + +        *average_dark++ = dark_sum; +        *average_white++ = white_sum; +    } + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file16("gl_white_average.pnm", dev->white_average_data.data(), +                                       channels, out_pixels_per_line, 1); +        sanei_genesys_write_pnm_file16("gl_dark_average.pnm", dev->dark_average_data.data(), +                                       channels, out_pixels_per_line, 1); +    } +} + +/* computes one coefficient given bright-dark value + * @param coeff factor giving 1.00 gain + * @param target desired target code + * @param value brght-dark value + * */ +static unsigned int +compute_coefficient (unsigned int coeff, unsigned int target, unsigned int value) +{ +  int result; + +  if (value > 0) +    { +      result = (coeff * target) / value; +      if (result >= 65535) +	{ +	  result = 65535; +	} +    } +  else +    { +      result = coeff; +    } +  return result; +} + +/** @brief compute shading coefficients for LiDE scanners + * The dark/white shading is actually performed _after_ reducing + * resolution via averaging. only dark/white shading data for what would be + * first pixel at full resolution is used. + * + * scanner raw input to output value calculation: + *   o=(i-off)*(gain/coeff) + * + * from datasheet: + *   off=dark_average + *   gain=coeff*bright_target/(bright_average-dark_average) + * works for dark_target==0 + * + * what we want is these: + *   bright_target=(bright_average-off)*(gain/coeff) + *   dark_target=(dark_average-off)*(gain/coeff) + * leading to + *  off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target) + *  gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff + * + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param pixels_per_line number of pixels per line + * @param words_per_color memory words per color channel + * @param channels number of color channels (actually 1 or 3) + * @param o shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not (GAIN4 bit) + * @param target_bright value of the white target code + * @param target_dark value of the black target code +*/ +static void +compute_averaged_planar (Genesys_Device * dev, const Genesys_Sensor& sensor, +			 uint8_t * shading_data, +			 unsigned int pixels_per_line, +			 unsigned int words_per_color, +			 unsigned int channels, +			 unsigned int o, +			 unsigned int coeff, +			 unsigned int target_bright, +			 unsigned int target_dark) +{ +  unsigned int x, i, j, br, dk, res, avgpixels, basepixels, val; +  unsigned int fill,factor; + +  DBG(DBG_info, "%s: pixels=%d, offset=%d\n", __func__, pixels_per_line, o); + +  /* initialize result */ +  memset (shading_data, 0xff, words_per_color * 3 * 2); + +  /* +     strangely i can write 0x20000 bytes beginning at 0x00000 without overwriting +     slope tables - which begin at address 0x10000(for 1200dpi hw mode): +     memory is organized in words(2 bytes) instead of single bytes. explains +     quite some things +   */ +/* +  another one: the dark/white shading is actually performed _after_ reducing +  resolution via averaging. only dark/white shading data for what would be +  first pixel at full resolution is used. + */ +/* +  scanner raw input to output value calculation: +    o=(i-off)*(gain/coeff) + +  from datasheet: +    off=dark_average +    gain=coeff*bright_target/(bright_average-dark_average) +  works for dark_target==0 + +  what we want is these: +    bright_target=(bright_average-off)*(gain/coeff) +    dark_target=(dark_average-off)*(gain/coeff) +  leading to +    off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target) +    gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff + */ +  res = dev->settings.xres; + +    if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1) +    { +        res *= 2; +    } + +  /* this should be evenly dividable */ +  basepixels = sensor.optical_res / res; + +  /* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */ +  if (basepixels < 1) +    avgpixels = 1; +  else if (basepixels < 6) +    avgpixels = basepixels; +  else if (basepixels < 8) +    avgpixels = 6; +  else if (basepixels < 10) +    avgpixels = 8; +  else if (basepixels < 12) +    avgpixels = 10; +  else if (basepixels < 15) +    avgpixels = 12; +  else +    avgpixels = 15; + +  /* LiDE80 packs shading data */ +    if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80) { +      factor=1; +      fill=avgpixels; +    } +  else +    { +      factor=avgpixels; +      fill=1; +    } + +  DBG(DBG_info, "%s: averaging over %d pixels\n", __func__, avgpixels); +  DBG(DBG_info, "%s: packing factor is %d\n", __func__, factor); +  DBG(DBG_info, "%s: fill length is %d\n", __func__, fill); + +  for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels) +    { +      if ((x + o) * 2 * 2 + 3 > words_per_color * 2) +	break; + +      for (j = 0; j < channels; j++) +	{ + +	  dk = 0; +	  br = 0; +	  for (i = 0; i < avgpixels; i++) +	    { +                // dark data +                dk += dev->dark_average_data[(x + i + pixels_per_line * j)]; +                // white data +                br += dev->white_average_data[(x + i + pixels_per_line * j)]; +	    } + +	  br /= avgpixels; +	  dk /= avgpixels; + +	  if (br * target_dark > dk * target_bright) +	    val = 0; +	  else if (dk * target_bright - br * target_dark > +		   65535 * (target_bright - target_dark)) +	    val = 65535; +	  else +            { +	      val = (dk * target_bright - br * target_dark) / (target_bright - target_dark); +            } + +          /*fill all pixels, even if only the last one is relevant*/ +	  for (i = 0; i < fill; i++) +	    { +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j] = val & 0xff; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = val >> 8; +	    } + +	  val = br - dk; + +	  if (65535 * val > (target_bright - target_dark) * coeff) +            { +	      val = (coeff * (target_bright - target_dark)) / val; +            } +	  else +            { +	      val = 65535; +            } + +          /*fill all pixels, even if only the last one is relevant*/ +	  for (i = 0; i < fill; i++) +	    { +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = val & 0xff; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = val >> 8; +	    } +	} + +      /* fill remaining channels */ +      for (j = channels; j < 3; j++) +	{ +	  for (i = 0; i < fill; i++) +	    { +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j    ] = shading_data[(x/factor + o + i) * 2 * 2    ]; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = shading_data[(x/factor + o + i) * 2 * 2 + 1]; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = shading_data[(x/factor + o + i) * 2 * 2 + 2]; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = shading_data[(x/factor + o + i) * 2 * 2 + 3]; +	    } +	} +    } +} + +static std::array<unsigned, 3> color_order_to_cmat(ColorOrder color_order) +{ +    switch (color_order) { +        case ColorOrder::RGB: return {0, 1, 2}; +        case ColorOrder::GBR: return {2, 0, 1}; +        default: +            throw std::logic_error("Unknown color order"); +    } +} + +/** + * Computes shading coefficient using formula in data sheet. 16bit data values + * manipulated here are little endian. For now we assume deletion scanning type + * and that there is always 3 channels. + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param pixels_per_line number of pixels per line + * @param channels number of color channels (actually 1 or 3) + * @param cmat color transposition matrix + * @param offset shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not + * @param target value of the target code + */ +static void compute_coefficients(Genesys_Device * dev, +		      uint8_t * shading_data, +		      unsigned int pixels_per_line, +		      unsigned int channels, +                                 ColorOrder color_order, +		      int offset, +		      unsigned int coeff, +		      unsigned int target) +{ +  uint8_t *ptr;			/* contain 16bit words in little endian */ +  unsigned int x, c; +  unsigned int val, br, dk; +  unsigned int start, end; + +  DBG(DBG_io, "%s: pixels_per_line=%d,  coeff=0x%04x\n", __func__, pixels_per_line, coeff); + +    auto cmat = color_order_to_cmat(color_order); + +  /* compute start & end values depending of the offset */ +  if (offset < 0) +   { +      start = -1 * offset; +      end = pixels_per_line; +   } +  else +   { +     start = 0; +     end = pixels_per_line - offset; +   } + +  for (c = 0; c < channels; c++) +    { +      for (x = start; x < end; x++) +	{ +	  /* TODO if channels=1 , use filter to know the base addr */ +	  ptr = shading_data + 4 * ((x + offset) * channels + cmat[c]); + +        // dark data +        dk = dev->dark_average_data[x * channels + c]; + +        // white data +        br = dev->white_average_data[x * channels + c]; + +	  /* compute coeff */ +	  val=compute_coefficient(coeff,target,br-dk); + +	  /* assign it */ +	  ptr[0] = dk & 255; +	  ptr[1] = dk / 256; +	  ptr[2] = val & 0xff; +	  ptr[3] = val / 256; + +	} +    } +} + +/** + * Computes shading coefficient using formula in data sheet. 16bit data values + * manipulated here are little endian. Data is in planar form, ie grouped by + * lines of the same color component. + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param factor averaging factor when the calibration scan is done at a higher resolution + * than the final scan + * @param pixels_per_line number of pixels per line + * @param words_per_color total number of shading data words for one color element + * @param channels number of color channels (actually 1 or 3) + * @param cmat transcoding matrix for color channel order + * @param offset shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not + * @param target white target value + */ +static void compute_planar_coefficients(Genesys_Device * dev, +			     uint8_t * shading_data, +			     unsigned int factor, +			     unsigned int pixels_per_line, +			     unsigned int words_per_color, +			     unsigned int channels, +                                        ColorOrder color_order, +			     unsigned int offset, +			     unsigned int coeff, +			     unsigned int target) +{ +  uint8_t *ptr;			/* contains 16bit words in little endian */ +  uint32_t x, c, i; +  uint32_t val, dk, br; + +    auto cmat = color_order_to_cmat(color_order); + +  DBG(DBG_io, "%s: factor=%d, pixels_per_line=%d, words=0x%X, coeff=0x%04x\n", __func__, factor, +      pixels_per_line, words_per_color, coeff); +  for (c = 0; c < channels; c++) +    { +      /* shading data is larger than pixels_per_line so offset can be neglected */ +      for (x = 0; x < pixels_per_line; x+=factor) +	{ +	  /* x2 because of 16 bit values, and x2 since one coeff for dark +	   * and another for white */ +	  ptr = shading_data + words_per_color * cmat[c] * 2 + (x + offset) * 4; + +	  dk = 0; +	  br = 0; + +	  /* average case */ +	  for(i=0;i<factor;i++) +	  { +                dk += dev->dark_average_data[((x+i) + pixels_per_line * c)]; +                br += dev->white_average_data[((x+i) + pixels_per_line * c)]; +	  } +	  dk /= factor; +	  br /= factor; + +	  val = compute_coefficient (coeff, target, br - dk); + +	  /* we duplicate the information to have calibration data at optical resolution */ +	  for (i = 0; i < factor; i++) +	    { +	      ptr[0 + 4 * i] = dk & 255; +	      ptr[1 + 4 * i] = dk / 256; +	      ptr[2 + 4 * i] = val & 0xff; +	      ptr[3 + 4 * i] = val / 256; +	    } +	} +    } +  /* in case of gray level scan, we duplicate shading information on all +   * three color channels */ +  if(channels==1) +  { +	  memcpy(shading_data+cmat[1]*2*words_per_color, +	         shading_data+cmat[0]*2*words_per_color, +		 words_per_color*2); +	  memcpy(shading_data+cmat[2]*2*words_per_color, +	         shading_data+cmat[0]*2*words_per_color, +		 words_per_color*2); +  } +} + +static void +compute_shifted_coefficients (Genesys_Device * dev, +                              const Genesys_Sensor& sensor, +			      uint8_t * shading_data, +			      unsigned int pixels_per_line, +			      unsigned int channels, +                              ColorOrder color_order, +			      int offset, +			      unsigned int coeff, +			      unsigned int target_dark, +			      unsigned int target_bright, +			      unsigned int patch_size)		/* contigous extent */ +{ +  unsigned int x, avgpixels, basepixels, i, j, val1, val2; +  unsigned int br_tmp [3], dk_tmp [3]; +  uint8_t *ptr = shading_data + offset * 3 * 4;                 /* contain 16bit words in little endian */ +  unsigned int patch_cnt = offset * 3;                          /* at start, offset of first patch */ + +    auto cmat = color_order_to_cmat(color_order); + +  x = dev->settings.xres; +  if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1) +    x *= 2;							/* scanner is using half-ccd mode */ +  basepixels = sensor.optical_res / x;			/*this should be evenly dividable */ + +      /* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */ +      if (basepixels < 1) +        avgpixels = 1; +      else if (basepixels < 6) +        avgpixels = basepixels; +      else if (basepixels < 8) +        avgpixels = 6; +      else if (basepixels < 10) +        avgpixels = 8; +      else if (basepixels < 12) +        avgpixels = 10; +      else if (basepixels < 15) +        avgpixels = 12; +      else +        avgpixels = 15; +  DBG(DBG_info, "%s: pixels_per_line=%d,  coeff=0x%04x,  averaging over %d pixels\n", __func__, +      pixels_per_line, coeff, avgpixels); + +  for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels) { +    memset (&br_tmp, 0, sizeof(br_tmp)); +    memset (&dk_tmp, 0, sizeof(dk_tmp)); + +    for (i = 0; i < avgpixels; i++) { +      for (j = 0; j < channels; j++) { +                br_tmp[j] += dev->white_average_data[((x + i) * channels + j)]; +                dk_tmp[i] += dev->dark_average_data[((x + i) * channels + j)]; +      } +    } +    for (j = 0; j < channels; j++) { +      br_tmp[j] /= avgpixels; +      dk_tmp[j] /= avgpixels; + +      if (br_tmp[j] * target_dark > dk_tmp[j] * target_bright) +        val1 = 0; +      else if (dk_tmp[j] * target_bright - br_tmp[j] * target_dark > 65535 * (target_bright - target_dark)) +        val1 = 65535; +      else +        val1 = (dk_tmp[j] * target_bright - br_tmp[j] * target_dark) / (target_bright - target_dark); + +      val2 = br_tmp[j] - dk_tmp[j]; +      if (65535 * val2 > (target_bright - target_dark) * coeff) +        val2 = (coeff * (target_bright - target_dark)) / val2; +      else +        val2 = 65535; + +      br_tmp[j] = val1; +      dk_tmp[j] = val2; +    } +    for (i = 0; i < avgpixels; i++) { +      for (j = 0; j < channels; j++) { +        * ptr++ = br_tmp[ cmat[j] ] & 0xff; +        * ptr++ = br_tmp[ cmat[j] ] >> 8; +        * ptr++ = dk_tmp[ cmat[j] ] & 0xff; +        * ptr++ = dk_tmp[ cmat[j] ] >> 8; +        patch_cnt++; +        if (patch_cnt == patch_size) { +          patch_cnt = 0; +          val1 = cmat[2]; +          cmat[2] = cmat[1]; +          cmat[1] = cmat[0]; +          cmat[0] = val1; +        } +      } +    } +  } +} + +static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); + +    if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { +        return; +    } + +  uint32_t pixels_per_line; +  uint8_t channels; +  int o; +  unsigned int length;		/**> number of shading calibration data words */ +  unsigned int factor; +  unsigned int coeff, target_code, words_per_color = 0; + +  pixels_per_line = dev->calib_pixels + dev->calib_pixels_offset; +  channels = dev->calib_channels; + +  /* we always build data for three channels, even for gray +   * we make the shading data such that each color channel data line is contiguous +   * to the next one, which allow to write the 3 channels in 1 write +   * during genesys_send_shading_coefficient, some values are words, other bytes +   * hence the x2 factor */ +  switch (dev->reg.get8(0x05) >> 6) +    { +      /* 600 dpi */ +    case 0: +      words_per_color = 0x2a00; +      break; +      /* 1200 dpi */ +    case 1: +      words_per_color = 0x5500; +      break; +      /* 2400 dpi */ +    case 2: +      words_per_color = 0xa800; +      break; +      /* 4800 dpi */ +    case 3: +      words_per_color = 0x15000; +      break; +    } + +  /* special case, memory is aligned on 0x5400, this has yet to be explained */ +  /* could be 0xa800 because sensor is truly 2400 dpi, then halved because +   * we only set 1200 dpi */ +  if(dev->model->sensor_id==SensorId::CIS_CANON_LIDE_80) +    { +      words_per_color = 0x5400; +    } + +  length = words_per_color * 3 * 2; + +  /* allocate computed size */ +  // contains 16bit words in little endian +  std::vector<uint8_t> shading_data(length, 0); + +  /* TARGET/(Wn-Dn) = white gain -> ~1.xxx then it is multiplied by 0x2000 +     or 0x4000 to give an integer +     Wn = white average for column n +     Dn = dark average for column n +   */ +    if (get_registers_gain4_bit(dev->model->asic_type, dev->calib_reg)) { +        coeff = 0x4000; +    } else { +        coeff = 0x2000; +    } + +  /* compute avg factor */ +  if(dev->settings.xres>sensor.optical_res) +    { +      factor=1; +    } +  else +    { +      factor=sensor.optical_res/dev->settings.xres; +    } + +  /* for GL646, shading data is planar if REG_0x01_FASTMOD is set and +   * chunky if not. For now we rely on the fact that we know that +   * each sensor is used only in one mode. Currently only the CIS_XP200 +   * sets REG_0x01_FASTMOD. +   */ + +  /* TODO setup a struct in genesys_devices that +   * will handle these settings instead of having this switch growing up */ +  switch (dev->model->sensor_id) +    { +    case SensorId::CCD_XP300: +    case SensorId::CCD_ROADWARRIOR: +    case SensorId::CCD_DP665: +    case SensorId::CCD_DP685: +    case SensorId::CCD_DSMOBILE600: +      target_code = 0xdc00; +      o = 4; +      compute_planar_coefficients (dev, +                   shading_data.data(), +				   factor, +				   pixels_per_line, +				   words_per_color, +				   channels, +                   ColorOrder::RGB, +				   o, +				   coeff, +				   target_code); +      break; +    case SensorId::CIS_XP200: +      target_code = 0xdc00; +      o = 2; +      compute_planar_coefficients (dev, +                   shading_data.data(), +				   1, +				   pixels_per_line, +				   words_per_color, +				   channels, +                   ColorOrder::GBR, +				   o, +				   coeff, +				   target_code); +      break; +    case SensorId::CCD_HP2300: +      target_code = 0xdc00; +      o = 2; +      if(dev->settings.xres<=sensor.optical_res/2) +       { +          o = o - sensor.dummy_pixel / 2; +       } +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CCD_5345: +      target_code = 0xe000; +      o = 4; +      if(dev->settings.xres<=sensor.optical_res/2) +       { +          o = o - sensor.dummy_pixel; +       } +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CCD_HP3670: +    case SensorId::CCD_HP2400: +      target_code = 0xe000; +            // offset is dependent on ccd_pixels_per_system_pixel(), but we couldn't use this in +            // common code previously. +            // FIXME: use sensor.ccd_pixels_per_system_pixel() +      if(dev->settings.xres<=300) +        { +                o = -10; +        } +      else if(dev->settings.xres<=600) +        { +                o = -6; +        } +      else +        { +          o = +2; +        } +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CCD_KVSS080: +    case SensorId::CCD_PLUSTEK_OPTICBOOK_3800: +    case SensorId::CCD_G4050: +        case SensorId::CCD_HP_4850C: +    case SensorId::CCD_CANON_4400F: +    case SensorId::CCD_CANON_8400F: +    case SensorId::CCD_CANON_8600F: +    case SensorId::CCD_PLUSTEK_OPTICFILM_7200I: +    case SensorId::CCD_PLUSTEK_OPTICFILM_7300: +    case SensorId::CCD_PLUSTEK_OPTICFILM_7500I: +      target_code = 0xe000; +      o = 0; +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CIS_CANON_LIDE_700F: +    case SensorId::CIS_CANON_LIDE_100: +    case SensorId::CIS_CANON_LIDE_200: +    case SensorId::CIS_CANON_LIDE_110: +    case SensorId::CIS_CANON_LIDE_120: +    case SensorId::CIS_CANON_LIDE_210: +    case SensorId::CIS_CANON_LIDE_220: +        /* TODO store this in a data struct so we avoid +         * growing this switch */ +        switch(dev->model->sensor_id) +          { +          case SensorId::CIS_CANON_LIDE_110: +          case SensorId::CIS_CANON_LIDE_120: +          case SensorId::CIS_CANON_LIDE_210: +          case SensorId::CIS_CANON_LIDE_220: +          case SensorId::CIS_CANON_LIDE_700F: +                target_code = 0xc000; +            break; +          default: +            target_code = 0xdc00; +          } +        words_per_color=pixels_per_line*2; +        length = words_per_color * 3 * 2; +        shading_data.clear(); +        shading_data.resize(length, 0); +        compute_planar_coefficients (dev, +                                     shading_data.data(), +                                     1, +                                     pixels_per_line, +                                     words_per_color, +                                     channels, +                                     ColorOrder::RGB, +                                     0, +                                     coeff, +                                     target_code); +      break; +    case SensorId::CIS_CANON_LIDE_35: +      compute_averaged_planar (dev, sensor, +                               shading_data.data(), +                               pixels_per_line, +                               words_per_color, +                               channels, +                               4, +                               coeff, +                               0xe000, +                               0x0a00); +      break; +    case SensorId::CIS_CANON_LIDE_80: +      compute_averaged_planar (dev, sensor, +                               shading_data.data(), +                               pixels_per_line, +                               words_per_color, +                               channels, +                               0, +                               coeff, +			       0xe000, +                               0x0800); +      break; +    case SensorId::CCD_PLUSTEK_OPTICPRO_3600: +      compute_shifted_coefficients (dev, sensor, +                        shading_data.data(), +			            pixels_per_line, +			            channels, +                        ColorOrder::RGB, +			            12,         /* offset */ +			            coeff, + 			            0x0001,      /* target_dark */ +			            0xf900,      /* target_bright */ +			            256);        /* patch_size: contigous extent */ +      break; +    default: +        throw SaneException(SANE_STATUS_UNSUPPORTED, "sensor %d not supported", +                            static_cast<unsigned>(dev->model->sensor_id)); +      break; +    } + +    // do the actual write of shading calibration data to the scanner +    genesys_send_offset_and_shading(dev, sensor, shading_data.data(), length); +} + + +/** + * search calibration cache list for an entry matching required scan. + * If one is found, set device calibration with it + * @param dev scanner's device + * @return false if no matching cache entry has been + * found, true if one has been found and used. + */ +static bool +genesys_restore_calibration(Genesys_Device * dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); + +    // if no cache or no function to evaluate cache entry ther can be no match/ +    if (dev->calibration_cache.empty()) { +        return false; +    } + +    auto session = dev->cmd_set->calculate_scan_session(dev, sensor, dev->settings); + +  /* we walk the link list of calibration cache in search for a +   * matching one */ +  for (auto& cache : dev->calibration_cache) +    { +        if (sanei_genesys_is_compatible_calibration(dev, session, &cache, false)) { +            dev->frontend = cache.frontend; +          /* we don't restore the gamma fields */ +          sensor.exposure = cache.sensor.exposure; + +          dev->average_size = cache.average_size; +          dev->calib_pixels = cache.calib_pixels; +          dev->calib_channels = cache.calib_channels; + +          dev->dark_average_data = cache.dark_average_data; +          dev->white_average_data = cache.white_average_data; + +            if (!dev->cmd_set->has_send_shading_data()) { +            genesys_send_shading_coefficient(dev, sensor); +          } + +          DBG(DBG_proc, "%s: restored\n", __func__); +          return true; +	} +    } +  DBG(DBG_proc, "%s: completed(nothing found)\n", __func__); +  return false; +} + + +static void genesys_save_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +#ifdef HAVE_SYS_TIME_H +  struct timeval time; +#endif + +    auto session = dev->cmd_set->calculate_scan_session(dev, sensor, dev->settings); + +  auto found_cache_it = dev->calibration_cache.end(); +  for (auto cache_it = dev->calibration_cache.begin(); cache_it != dev->calibration_cache.end(); +       cache_it++) +    { +        if (sanei_genesys_is_compatible_calibration(dev, session, &*cache_it, true)) { +            found_cache_it = cache_it; +            break; +        } +    } + +  /* if we found on overridable cache, we reuse it */ +  if (found_cache_it == dev->calibration_cache.end()) +    { +      /* create a new cache entry and insert it in the linked list */ +      dev->calibration_cache.push_back(Genesys_Calibration_Cache()); +      found_cache_it = std::prev(dev->calibration_cache.end()); +    } + +  found_cache_it->average_size = dev->average_size; + +  found_cache_it->dark_average_data = dev->dark_average_data; +  found_cache_it->white_average_data = dev->white_average_data; + +    found_cache_it->params = session.params; +  found_cache_it->frontend = dev->frontend; +  found_cache_it->sensor = sensor; + +  found_cache_it->calib_pixels = dev->calib_pixels; +  found_cache_it->calib_channels = dev->calib_channels; + +#ifdef HAVE_SYS_TIME_H +    gettimeofday(&time, nullptr); +  found_cache_it->last_calibration = time.tv_sec; +#endif +} + +/** + * does the calibration process for a flatbed scanner + * - offset calibration + * - gain calibration + * - shading calibration + * @param dev device to calibrate + */ +static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +  uint32_t pixels_per_line; + +    unsigned coarse_res = sensor.optical_res; +    if (dev->settings.yres <= sensor.optical_res / 2) { +        coarse_res /= 2; +    } + +    if (dev->model->model_id == ModelId::CANON_8400F) { +        coarse_res = 1600; +    } + +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        coarse_res = 1200; +    } + +  /* do offset calibration if needed */ +  if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) +    { +        dev->interface->record_progress_message("offset_calibration"); +        dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + +      /* since all the registers are set up correctly, just use them */ +        dev->interface->record_progress_message("coarse_gain_calibration"); +        dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res); +    } else { +    /* since we have 2 gain calibration proc, skip second if first one was +       used. */ +        dev->interface->record_progress_message("init_regs_for_coarse_calibration"); +        dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg); + +        dev->interface->record_progress_message("genesys_coarse_calibration"); +        genesys_coarse_calibration(dev, sensor); +    } + +  if (dev->model->is_cis) +    { +      /* the afe now sends valid data for doing led calibration */ +        dev->interface->record_progress_message("led_calibration"); +        switch (dev->model->asic_type) { +            case AsicType::GL124: +            case AsicType::GL845: +            case AsicType::GL846: +            case AsicType::GL847: { +                auto calib_exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); +                for (auto& sensor_update : +                        sanei_genesys_find_sensors_all_for_write(dev, sensor.method)) { +                    sensor_update.get().exposure = calib_exposure; +                } +                sensor.exposure = calib_exposure; +                break; +            } +            default: { +                sensor.exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); +            } +        } + + +      /* calibrate afe again to match new exposure */ +      if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) { +            dev->interface->record_progress_message("offset_calibration"); +            dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + +            // since all the registers are set up correctly, just use them + +            dev->interface->record_progress_message("coarse_gain_calibration"); +            dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res); +        } else { +            // since we have 2 gain calibration proc, skip second if first one was used +            dev->interface->record_progress_message("init_regs_for_coarse_calibration"); +            dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg); + +            dev->interface->record_progress_message("genesys_coarse_calibration"); +            genesys_coarse_calibration(dev, sensor); +        } +    } + +  /* we always use sensor pixel number when the ASIC can't handle multi-segments sensor */ +  if (!(dev->model->flags & GENESYS_FLAG_SIS_SENSOR)) +    { +        pixels_per_line = static_cast<std::uint32_t>((dev->model->x_size * dev->settings.xres) / +                                                     MM_PER_INCH); +    } +  else +    { +      pixels_per_line = sensor.sensor_pixels; +    } + +    // send default shading data +    dev->interface->record_progress_message("sanei_genesys_init_shading_data"); +    sanei_genesys_init_shading_data(dev, sensor, pixels_per_line); + +  if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +      dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +  { +        dev->cmd_set->move_to_ta(dev); +  } + +    // shading calibration +    if (dev->model->flags & GENESYS_FLAG_DARK_WHITE_CALIBRATION) { +        dev->interface->record_progress_message("init_regs_for_shading"); +        dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +        dev->interface->record_progress_message("genesys_dark_white_shading_calibration"); +        genesys_dark_white_shading_calibration(dev, sensor); +    } else { +        DBG(DBG_proc, "%s : genesys_dark_shading_calibration dev->calib_reg ", __func__); +        debug_dump(DBG_proc, dev->calib_reg); + +        if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) { +            dev->interface->record_progress_message("init_regs_for_shading"); +            dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +            dev->interface->record_progress_message("genesys_dark_shading_calibration"); +            genesys_dark_shading_calibration(dev, sensor); +            genesys_repark_sensor_before_shading(dev); +        } + +        dev->interface->record_progress_message("init_regs_for_shading2"); +        dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +        dev->interface->record_progress_message("genesys_white_shading_calibration"); +        genesys_white_shading_calibration(dev, sensor); +        genesys_repark_sensor_after_white_shading(dev); + +        if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) { +            genesys_dummy_dark_shading(dev, sensor); +        } +    } + +    if (!dev->cmd_set->has_send_shading_data()) { +        dev->interface->record_progress_message("genesys_send_shading_coefficient"); +        genesys_send_shading_coefficient(dev, sensor); +    } +} + +/** + * Does the calibration process for a sheetfed scanner + * - offset calibration + * - gain calibration + * - shading calibration + * During calibration a predefined calibration sheet with specific black and white + * areas is used. + * @param dev device to calibrate + */ +static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    bool forward = true; + +    // first step, load document +    dev->cmd_set->load_document(dev); + +  /* led, offset and gain calibration are influenced by scan +   * settings. So we set it to sensor resolution */ +  dev->settings.xres = sensor.optical_res; +  /* XP200 needs to calibrate a full and half sensor's resolution */ +    if (dev->model->sensor_id == SensorId::CIS_XP200 && +        dev->settings.xres <= sensor.optical_res / 2) +    { +        dev->settings.xres /= 2; +    } + +  /* the afe needs to sends valid data even before calibration */ + +  /* go to a white area */ +    try { +        dev->cmd_set->search_strip(dev, sensor, forward, false); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +        throw; +    } + +  if (dev->model->is_cis) +    { +        dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); +    } + +  /* calibrate afe */ +  if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) +    { +        dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + +      /* since all the registers are set up correctly, just use them */ + +        dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, sensor.optical_res); +    } +  else +    /* since we have 2 gain calibration proc, skip second if first one was +       used. */ +    { +        dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg); + +        genesys_coarse_calibration(dev, sensor); +    } + +  /* search for a full width black strip and then do a 16 bit scan to +   * gather black shading data */ +  if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) +    { +      /* seek black/white reverse/forward */ +        try { +            dev->cmd_set->search_strip(dev, sensor, forward, true); +        } catch (...) { +            catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +            throw; +        } + +        dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +        try { +            genesys_dark_shading_calibration(dev, sensor); +        } catch (...) { +            catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +            throw; +        } +        forward = false; +    } + + +  /* go to a white area */ +    try { +        dev->cmd_set->search_strip(dev, sensor, forward, false); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +        throw; +    } + +  genesys_repark_sensor_before_shading(dev); + +    dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +    try { +        genesys_white_shading_calibration(dev, sensor); +        genesys_repark_sensor_after_white_shading(dev); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +        throw; +    } + +    // in case we haven't black shading data, build it from black pixels of white calibration +    // FIXME: shouldn't we use genesys_dummy_dark_shading() ? +    if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) { +      dev->dark_average_data.clear(); +        dev->dark_average_data.resize(dev->average_size, 0x0f0f); +      /* XXX STEF XXX +       * with black point in white shading, build an average black +       * pixel and use it to fill the dark_average +       * dev->calib_pixels +       (sensor.sensor_pixels * dev->settings.xres) / sensor.optical_res, +       dev->calib_lines, +       */ +    } + +  /* send the shading coefficient when doing whole line shading +   * but not when using SHDAREA like GL124 */ +    if (!dev->cmd_set->has_send_shading_data()) { +        genesys_send_shading_coefficient(dev, sensor); +    } + +    // save the calibration data +    genesys_save_calibration(dev, sensor); + +    // and finally eject calibration sheet +    dev->cmd_set->eject_document(dev); + +    // restore settings +    dev->settings.xres = sensor.optical_res; +} + +/** + * does the calibration process for a device + * @param dev device to calibrate + */ +static void genesys_scanner_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    if (!dev->model->is_sheetfed) { +        genesys_flatbed_calibration(dev, sensor); +        return; +    } +    genesys_sheetfed_calibration(dev, sensor); +} + + +/* ------------------------------------------------------------------------ */ +/*                  High level (exported) functions                         */ +/* ------------------------------------------------------------------------ */ + +/* + * wait lamp to be warm enough by scanning the same line until + * differences between two scans are below a threshold + */ +static void genesys_warmup_lamp(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    unsigned seconds = 0; +  int pixel; +  int channels, total_size; +  double first_average = 0; +  double second_average = 0; +  int difference = 255; +    int lines = 3; + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +    dev->cmd_set->init_regs_for_warmup(dev, sensor, &dev->reg, &channels, &total_size); +  std::vector<uint8_t> first_line(total_size); +  std::vector<uint8_t> second_line(total_size); + +  do +    { +      DBG(DBG_info, "%s: one more loop\n", __func__); +        dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("warmup_lamp"); +            dev->cmd_set->end_scan(dev, &dev->reg, true); +            return; +        } + +        wait_until_buffer_non_empty(dev); + +        try { +            sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +        } catch (...) { +            // FIXME: document why this retry is here +            sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +        } + +        dev->cmd_set->end_scan(dev, &dev->reg, true); + +        dev->interface->sleep_ms(1000); +      seconds++; + +        dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false); + +        wait_until_buffer_non_empty(dev); + +        sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); +        dev->cmd_set->end_scan(dev, &dev->reg, true); + +      /* compute difference between the two scans */ +      for (pixel = 0; pixel < total_size; pixel++) +	{ +            // 16 bit data +            if (dev->session.params.depth == 16) { +	      first_average += (first_line[pixel] + first_line[pixel + 1] * 256); +	      second_average += (second_line[pixel] + second_line[pixel + 1] * 256); +	      pixel++; +	    } +	  else +	    { +	      first_average += first_line[pixel]; +	      second_average += second_line[pixel]; +	    } +	} +        if (dev->session.params.depth == 16) { +	  first_average /= pixel; +	  second_average /= pixel; +            difference = static_cast<int>(std::fabs(first_average - second_average)); +	  DBG(DBG_info, "%s: average = %.2f, diff = %.3f\n", __func__, +	      100 * ((second_average) / (256 * 256)), +	      100 * (difference / second_average)); + +	  if (second_average > (100 * 256) +	      && (difference / second_average) < 0.002) +	    break; +	} +      else +	{ +	  first_average /= pixel; +	  second_average /= pixel; +	  if (DBG_LEVEL >= DBG_data) +	    { +              sanei_genesys_write_pnm_file("gl_warmup1.pnm", first_line.data(), 8, channels, +                                           total_size / (lines * channels), lines); +              sanei_genesys_write_pnm_file("gl_warmup2.pnm", second_line.data(), 8, channels, +                                           total_size / (lines * channels), lines); +	    } +	  DBG(DBG_info, "%s: average 1 = %.2f, average 2 = %.2f\n", __func__, first_average, +	      second_average); +          /* if delta below 15/255 ~= 5.8%, lamp is considred warm enough */ +	  if (fabs (first_average - second_average) < 15 +	      && second_average > 55) +	    break; +	} + +      /* sleep another second before next loop */ +        dev->interface->sleep_ms(1000); +        seconds++; +    } while (seconds < WARMUP_TIME); + +  if (seconds >= WARMUP_TIME) +    { +        throw SaneException(SANE_STATUS_IO_ERROR, +                            "warmup timed out after %d seconds. Lamp defective?", seconds); +    } +  else +    { +      DBG(DBG_info, "%s: warmup succeeded after %d seconds\n", __func__, seconds); +    } +} + + +// High-level start of scanning +static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) +{ +    DBG_HELPER(dbg); +  unsigned int steps, expected; + +  /* since not all scanners are set ot wait for head to park +   * we check we are not still parking before starting a new scan */ +    if (dev->parking) { +        sanei_genesys_wait_for_home(dev); +    } + +    // disable power saving +    dev->cmd_set->save_power(dev, false); + +  /* wait for lamp warmup : until a warmup for TRANSPARENCY is designed, skip +   * it when scanning from XPA. */ +  if (!(dev->model->flags & GENESYS_FLAG_SKIP_WARMUP) +    && (dev->settings.scan_method == ScanMethod::FLATBED)) +    { +        genesys_warmup_lamp(dev); +    } + +  /* set top left x and y values by scanning the internals if flatbed scanners */ +    if (!dev->model->is_sheetfed) { +      /* do the geometry detection only once */ +      if ((dev->model->flags & GENESYS_FLAG_SEARCH_START) +      && (dev->model->y_offset_calib_white == 0)) +	{ +        dev->cmd_set->search_start_position (dev); + +            dev->parking = false; +            dev->cmd_set->move_back_home(dev, true); +	} +      else +	{ +	  /* Go home */ +	  /* TODO: check we can drop this since we cannot have the +	     scanner's head wandering here */ +            dev->parking = false; +            dev->cmd_set->move_back_home(dev, true); +	} +    } + +  /* move to calibration area for transparency adapter */ +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        dev->cmd_set->move_to_ta(dev); +    } + +  /* load document if needed (for sheetfed scanner for instance) */ +    if (dev->model->is_sheetfed) { +        dev->cmd_set->load_document(dev); +    } + +    auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres, +                                                       dev->settings.get_channels(), +                                                       dev->settings.scan_method); + +    // send gamma tables. They have been set to device or user value +    // when setting option value */ +    dev->cmd_set->send_gamma_table(dev, sensor); + +  /* try to use cached calibration first */ +  if (!genesys_restore_calibration (dev, sensor)) +    { +       /* calibration : sheetfed scanners can't calibrate before each scan */ +       /* and also those who have the NO_CALIBRATION flag                  */ +        if (!(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION) && !dev->model->is_sheetfed) { +            genesys_scanner_calibration(dev, sensor); +          genesys_save_calibration (dev, sensor); +	} +      else +	{ +          DBG(DBG_warn, "%s: no calibration done\n", __func__); +	} +    } + +  /* build look up table for dynamic lineart */ +    if (dev->settings.scan_mode == ScanColorMode::LINEART) { +        sanei_genesys_load_lut(dev->lineart_lut, 8, 8, 50, 205, dev->settings.threshold_curve, +                               dev->settings.threshold-127); +    } + +    dev->cmd_set->wait_for_motor_stop(dev); + +    if (dev->cmd_set->needs_home_before_init_regs_for_scan(dev)) { +        dev->cmd_set->move_back_home(dev, true); +    } + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        dev->cmd_set->move_to_ta(dev); +    } + +    dev->cmd_set->init_regs_for_scan(dev, sensor); + +  /* no lamp during scan */ +    if (lamp_off) { +        sanei_genesys_set_lamp_power(dev, sensor, dev->reg, false); +    } + +  /* GL124 is using SHDAREA, so we have to wait for scan to be set up before +   * sending shading data */ +    if (dev->cmd_set->has_send_shading_data() && +        !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) +    { +        genesys_send_shading_coefficient(dev, sensor); +    } + +    // now send registers for scan +    dev->interface->write_registers(dev->reg); + +    // start effective scan +    dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("start_scan"); +        return; +    } + +  /*do we really need this? the valid data check should be sufficent -- pierre*/ +  /* waits for head to reach scanning position */ +  expected = dev->reg.get8(0x3d) * 65536 +           + dev->reg.get8(0x3e) * 256 +           + dev->reg.get8(0x3f); +  do +    { +        // wait some time between each test to avoid overloading USB and CPU +        dev->interface->sleep_ms(100); +        sanei_genesys_read_feed_steps (dev, &steps); +    } +  while (steps < expected); + +    wait_until_buffer_non_empty(dev); + +    // we wait for at least one word of valid scan data +    // this is also done in sanei_genesys_read_data_from_scanner -- pierre +    if (!dev->model->is_sheetfed) { +        do { +            dev->interface->sleep_ms(100); +            sanei_genesys_read_valid_words(dev, &steps); +        } +      while (steps < 1); +    } +} + +static void genesys_fill_read_buffer(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +  /* for sheetfed scanner, we must check is document is shorter than +   * the requested scan */ +    if (dev->model->is_sheetfed) { +        dev->cmd_set->detect_document_end(dev); +    } + +    std::size_t size = dev->read_buffer.size() - dev->read_buffer.avail(); + +  /* due to sensors and motors, not all data can be directly used. It +   * may have to be read from another intermediate buffer and then processed. +   * There are currently 3 intermediate stages: +   * - handling of odd/even sensors +   * - handling of line interpolation for motors that can't have low +   *   enough dpi +   * - handling of multi-segments sensors +   * +   * This is also the place where full duplex data will be handled. +   */ +    dev->pipeline_buffer.get_data(size, dev->read_buffer.get_write_pos(size)); + +    dev->read_buffer.produce(size); +} + +/* this function does the effective data read in a manner that suits +   the scanner. It does data reordering and resizing if need. +   It also manages EOF and I/O errors, and line distance correction. +    Returns true on success, false on end-of-file. +*/ +static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destination, size_t* len) +{ +    DBG_HELPER(dbg); +    size_t bytes = 0; +  uint8_t *work_buffer_src; +  Genesys_Buffer *src_buffer; + +    if (!dev->read_active) { +      *len = 0; +        throw SaneException("read is not active"); +    } + +    DBG(DBG_info, "%s: frontend requested %zu bytes\n", __func__, *len); +    DBG(DBG_info, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__, +        dev->total_bytes_to_read, dev->total_bytes_read); + +  /* is there data left to scan */ +  if (dev->total_bytes_read >= dev->total_bytes_to_read) +    { +      /* issue park command immediatly in case scanner can handle it +       * so we save time */ +        if (!dev->model->is_sheetfed && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) && +            !dev->parking) +        { +            dev->cmd_set->move_back_home(dev, false); +            dev->parking = true; +        } +        throw SaneException(SANE_STATUS_EOF, "nothing more to scan: EOF"); +    } + +/* convert data */ +/* +  0. fill_read_buffer +-------------- read_buffer ---------------------- +  1a). (opt)uncis                    (assumes color components to be laid out +                                    planar) +  1b). (opt)reverse_RGB              (assumes pixels to be BGR or BBGGRR)) +-------------- lines_buffer ---------------------- +  2a). (opt)line_distance_correction (assumes RGB or RRGGBB) +  2b). (opt)unstagger                (assumes pixels to be depth*channels/8 +                                      bytes long, unshrinked) +------------- shrink_buffer --------------------- +  3. (opt)shrink_lines             (assumes component separation in pixels) +-------------- out_buffer ----------------------- +  4. memcpy to destination (for lineart with bit reversal) +*/ +/*FIXME: for lineart we need sub byte addressing in buffers, or conversion to +  bytes at 0. and back to bits at 4. +Problems with the first approach: +  - its not clear how to check if we need to output an incomplete byte +    because it is the last one. + */ +/*FIXME: add lineart support for gl646. in the meantime add logic to convert +  from gray to lineart at the end? would suffer the above problem, +  total_bytes_to_read and total_bytes_read help in that case. + */ + +    if (is_testing_mode()) { +        if (dev->total_bytes_read + *len > dev->total_bytes_to_read) { +            *len = dev->total_bytes_to_read - dev->total_bytes_read; +        } +        dev->total_bytes_read += *len; +    } else { +        genesys_fill_read_buffer(dev); + +        src_buffer = &(dev->read_buffer); + +        /* move data to destination */ +        bytes = std::min(src_buffer->avail(), *len); + +        work_buffer_src = src_buffer->get_read_pos(); + +        std::memcpy(destination, work_buffer_src, bytes); +        *len = bytes; + +        /* avoid signaling some extra data because we have treated a full block +        * on the last block */ +        if (dev->total_bytes_read + *len > dev->total_bytes_to_read) { +            *len = dev->total_bytes_to_read - dev->total_bytes_read; +        } + +        /* count bytes sent to frontend */ +        dev->total_bytes_read += *len; + +        src_buffer->consume(bytes); +    } + +  /* end scan if all needed data have been read */ +   if(dev->total_bytes_read >= dev->total_bytes_to_read) +    { +        dev->cmd_set->end_scan(dev, &dev->reg, true); +        if (dev->model->is_sheetfed) { +            dev->cmd_set->eject_document (dev); +        } +    } + +    DBG(DBG_proc, "%s: completed, %zu bytes read\n", __func__, bytes); +} + + + +/* ------------------------------------------------------------------------ */ +/*                  Start of higher level functions                         */ +/* ------------------------------------------------------------------------ */ + +static size_t +max_string_size (const SANE_String_Const strings[]) +{ +  size_t size, max_size = 0; +  SANE_Int i; + +  for (i = 0; strings[i]; ++i) +    { +      size = strlen (strings[i]) + 1; +      if (size > max_size) +	max_size = size; +    } +  return max_size; +} + +static std::size_t max_string_size(const std::vector<const char*>& strings) +{ +    std::size_t max_size = 0; +    for (const auto& s : strings) { +        if (!s) { +            continue; +        } +        max_size = std::max(max_size, std::strlen(s)); +    } +    return max_size; +} + +static unsigned pick_resolution(const std::vector<unsigned>& resolutions, unsigned resolution, +                                const char* direction) +{ +    DBG_HELPER(dbg); + +    if (resolutions.empty()) +        throw SaneException("Empty resolution list"); + +    unsigned best_res = resolutions.front(); +    unsigned min_diff = abs_diff(best_res, resolution); + +    for (auto it = std::next(resolutions.begin()); it != resolutions.end(); ++it) { +        unsigned curr_diff = abs_diff(*it, resolution); +        if (curr_diff < min_diff) { +            min_diff = curr_diff; +            best_res = *it; +        } +    } + +    if (best_res != resolution) { +        DBG(DBG_warn, "%s: using resolution %d that is nearest to %d for direction %s\n", +            __func__, best_res, resolution, direction); +    } +    return best_res; +} + +static void calc_parameters(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +  double tl_x = 0, tl_y = 0, br_x = 0, br_y = 0; + +    tl_x = SANE_UNFIX(s->pos_top_left_x); +    tl_y = SANE_UNFIX(s->pos_top_left_y); +    br_x = SANE_UNFIX(s->pos_bottom_right_x); +    br_y = SANE_UNFIX(s->pos_bottom_right_y); + +    s->params.last_frame = true;	/* only single pass scanning supported */ + +    if (s->mode == SANE_VALUE_SCAN_MODE_GRAY || s->mode == SANE_VALUE_SCAN_MODE_LINEART) { +        s->params.format = SANE_FRAME_GRAY; +    } else { +        s->params.format = SANE_FRAME_RGB; +    } + +    if (s->mode == SANE_VALUE_SCAN_MODE_LINEART) { +        s->params.depth = 1; +    } else { +        s->params.depth = s->bit_depth; +    } + +    s->dev->settings.scan_method = s->scan_method; +    const auto& resolutions = s->dev->model->get_resolution_settings(s->dev->settings.scan_method); + +    s->dev->settings.depth = s->bit_depth; + +  /* interpolation */ +  s->dev->settings.disable_interpolation = s->disable_interpolation; + +  // FIXME: use correct sensor +  const auto& sensor = sanei_genesys_find_sensor_any(s->dev); + +    // hardware settings +    if (static_cast<unsigned>(s->resolution) > sensor.optical_res && +        s->dev->settings.disable_interpolation) +    { +        s->dev->settings.xres = sensor.optical_res; +    } else { +        s->dev->settings.xres = s->resolution; +    } +    s->dev->settings.yres = s->resolution; + +    s->dev->settings.xres = pick_resolution(resolutions.resolutions_x, s->dev->settings.xres, "X"); +    s->dev->settings.yres = pick_resolution(resolutions.resolutions_y, s->dev->settings.yres, "Y"); + +    s->params.lines = static_cast<unsigned>(((br_y - tl_y) * s->dev->settings.yres) / +                                            MM_PER_INCH); +    unsigned pixels_per_line = static_cast<unsigned>(((br_x - tl_x) * s->dev->settings.xres) / +                                                     MM_PER_INCH); + +  /* we need an even pixels number +   * TODO invert test logic or generalize behaviour across all ASICs */ +    if ((s->dev->model->flags & GENESYS_FLAG_SIS_SENSOR) || +        s->dev->model->asic_type == AsicType::GL847 || +        s->dev->model->asic_type == AsicType::GL124 || +        s->dev->model->asic_type == AsicType::GL845 || +        s->dev->model->asic_type == AsicType::GL846 || +        s->dev->model->asic_type == AsicType::GL843) +    { +        if (s->dev->settings.xres <= 1200) { +            pixels_per_line = (pixels_per_line / 4) * 4; +        } else if (s->dev->settings.xres < s->dev->settings.yres) { +            // BUG: this is an artifact of the fact that the resolution was twice as large than +            // the actual resolution when scanning above the supported scanner X resolution +            pixels_per_line = (pixels_per_line / 8) * 8; +        } else { +            pixels_per_line = (pixels_per_line / 16) * 16; +        } +    } + +  /* corner case for true lineart for sensor with several segments +   * or when xres is doubled to match yres */ +    if (s->dev->settings.xres >= 1200 && ( +                s->dev->model->asic_type == AsicType::GL124 || +                s->dev->model->asic_type == AsicType::GL847 || +                s->dev->session.params.xres < s->dev->session.params.yres)) +    { +        if (s->dev->settings.xres < s->dev->settings.yres) { +            // FIXME: this is an artifact of the fact that the resolution was twice as large than +            // the actual resolution when scanning above the supported scanner X resolution +            pixels_per_line = (pixels_per_line / 8) * 8; +        } else { +            pixels_per_line = (pixels_per_line / 16) * 16; +        } +    } + +    unsigned xres_factor = s->resolution / s->dev->settings.xres; + +    unsigned bytes_per_line = 0; + +  if (s->params.depth > 8) +    { +      s->params.depth = 16; +        bytes_per_line = 2 * pixels_per_line; +    } +  else if (s->params.depth == 1) +    { +        // round down pixel number. This will is lossy operation, at most 7 pixels will be lost +        pixels_per_line = (pixels_per_line / 8) * 8; +        bytes_per_line = pixels_per_line / 8; +    } else { +        bytes_per_line = pixels_per_line; +    } + +    if (s->params.format == SANE_FRAME_RGB) { +        bytes_per_line *= 3; +    } + +    s->dev->settings.scan_mode = option_string_to_scan_color_mode(s->mode); + +  s->dev->settings.lines = s->params.lines; +    s->dev->settings.pixels = pixels_per_line; +    s->dev->settings.requested_pixels = pixels_per_line * xres_factor; +    s->params.pixels_per_line = pixels_per_line * xres_factor; +    s->params.bytes_per_line = bytes_per_line * xres_factor; +  s->dev->settings.tl_x = tl_x; +  s->dev->settings.tl_y = tl_y; + +    // threshold setting +    s->dev->settings.threshold = static_cast<int>(2.55 * (SANE_UNFIX(s->threshold))); + +    // color filter +    if (s->color_filter == "Red") { +        s->dev->settings.color_filter = ColorFilter::RED; +    } else if (s->color_filter == "Green") { +        s->dev->settings.color_filter = ColorFilter::GREEN; +    } else if (s->color_filter == "Blue") { +        s->dev->settings.color_filter = ColorFilter::BLUE; +    } else { +        s->dev->settings.color_filter = ColorFilter::NONE; +    } + +    // true gray +    if (s->color_filter == "None") { +        s->dev->settings.true_gray = 1; +    } else { +        s->dev->settings.true_gray = 0; +    } + +    // threshold curve for dynamic rasterization +    s->dev->settings.threshold_curve = s->threshold_curve; + +  /* some digital processing requires the whole picture to be buffered */ +  /* no digital processing takes place when doing preview, or when bit depth is +   * higher than 8 bits */ +  if ((s->swdespeck || s->swcrop || s->swdeskew || s->swderotate ||(SANE_UNFIX(s->swskip)>0)) +    && (!s->preview) +    && (s->bit_depth <= 8)) +    { +        s->dev->buffer_image = true; +    } +  else +    { +        s->dev->buffer_image = false; +    } + +  /* brigthness and contrast only for for 8 bit scans */ +  if(s->bit_depth <= 8) +    { +      s->dev->settings.contrast = (s->contrast * 127) / 100; +      s->dev->settings.brightness = (s->brightness * 127) / 100; +    } +  else +    { +      s->dev->settings.contrast=0; +      s->dev->settings.brightness=0; +    } + +  /* cache expiration time */ +   s->dev->settings.expiration_time = s->expiration_time; +} + + +static void create_bpp_list (Genesys_Scanner * s, const std::vector<unsigned>& bpp) +{ +    s->bpp_list[0] = bpp.size(); +    std::reverse_copy(bpp.begin(), bpp.end(), s->bpp_list + 1); +} + +/** @brief this function initialize a gamma vector based on the ASIC: + * Set up a default gamma table vector based on device description + * gl646: 12 or 14 bits gamma table depending on GENESYS_FLAG_14BIT_GAMMA + * gl84x: 16 bits + * gl12x: 16 bits + * @param scanner pointer to scanner session to get options + * @param option option number of the gamma table to set + */ +static void +init_gamma_vector_option (Genesys_Scanner * scanner, int option) +{ +  /* the option is inactive until the custom gamma control +   * is enabled */ +  scanner->opt[option].type = SANE_TYPE_INT; +  scanner->opt[option].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED; +  scanner->opt[option].unit = SANE_UNIT_NONE; +  scanner->opt[option].constraint_type = SANE_CONSTRAINT_RANGE; +    if (scanner->dev->model->asic_type == AsicType::GL646) { +      if ((scanner->dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) != 0) +	{ +	  scanner->opt[option].size = 16384 * sizeof (SANE_Word); +	  scanner->opt[option].constraint.range = &u14_range; +	} +      else +	{			/* 12 bits gamma tables */ +	  scanner->opt[option].size = 4096 * sizeof (SANE_Word); +	  scanner->opt[option].constraint.range = &u12_range; +	} +    } +  else +    {				/* other asics have 16 bits words gamma table */ +      scanner->opt[option].size = 256 * sizeof (SANE_Word); +      scanner->opt[option].constraint.range = &u16_range; +    } +} + +/** + * allocate a geometry range + * @param size maximum size of the range + * @return a pointer to a valid range or nullptr + */ +static SANE_Range create_range(float size) +{ +    SANE_Range range; +    range.min = SANE_FIX(0.0); +    range.max = SANE_FIX(size); +    range.quant = SANE_FIX(0.0); +    return range; +} + +/** @brief generate calibration cache file nam + * Generates the calibration cache file name to use. + * Tries to store the chache in $HOME/.sane or + * then fallbacks to $TMPDIR or TMP. The filename + * uses the model name if only one scanner is plugged + * else is uses the device name when several identical + * scanners are in use. + * @param currdev current scanner device + * @return an allocated string containing a file name + */ +static std::string calibration_filename(Genesys_Device *currdev) +{ +    std::string ret; +    ret.resize(PATH_MAX); + +  char filename[80]; +  unsigned int count; +  unsigned int i; + +  /* first compute the DIR where we can store cache: +   * 1 - home dir +   * 2 - $TMPDIR +   * 3 - $TMP +   * 4 - tmp dir +   * 5 - temp dir +   * 6 - then resort to current dir +   */ +    char* ptr = std::getenv("HOME"); +    if (ptr == nullptr) { +        ptr = std::getenv("USERPROFILE"); +    } +    if (ptr == nullptr) { +        ptr = std::getenv("TMPDIR"); +    } +    if (ptr == nullptr) { +        ptr = std::getenv("TMP"); +    } + +  /* now choose filename: +   * 1 - if only one scanner, name of the model +   * 2 - if several scanners of the same model, use device name, +   *     replacing special chars +   */ +  count=0; +  /* count models of the same names if several scanners attached */ +    if(s_devices->size() > 1) { +        for (const auto& dev : *s_devices) { +            if (dev.model->model_id == currdev->model->model_id) { +                count++; +            } +        } +    } +  if(count>1) +    { +        std::snprintf(filename, sizeof(filename), "%s.cal", currdev->file_name.c_str()); +      for(i=0;i<strlen(filename);i++) +        { +          if(filename[i]==':'||filename[i]==PATH_SEP) +            { +              filename[i]='_'; +            } +        } +    } +  else +    { +      snprintf(filename,sizeof(filename),"%s.cal",currdev->model->name); +    } + +  /* build final final name : store dir + filename */ +    if (ptr == nullptr) { +        int size = std::snprintf(&ret.front(), ret.size(), "%s", filename); +        ret.resize(size); +    } +  else +    { +        int size = 0; +#ifdef HAVE_MKDIR +        /* make sure .sane directory exists in existing store dir */ +        size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane", ptr, PATH_SEP); +        ret.resize(size); +        mkdir(ret.c_str(), 0700); + +        ret.resize(PATH_MAX); +#endif +        size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane%c%s", +                             ptr, PATH_SEP, PATH_SEP, filename); +        ret.resize(size); +    } + +    DBG(DBG_info, "%s: calibration filename >%s<\n", __func__, ret.c_str()); + +    return ret; +} + +static void set_resolution_option_values(Genesys_Scanner& s, bool reset_resolution_value) +{ +    auto resolutions = s.dev->model->get_resolutions(s.scan_method); + +    s.opt_resolution_values.resize(resolutions.size() + 1, 0); +    s.opt_resolution_values[0] = resolutions.size(); +    std::copy(resolutions.begin(), resolutions.end(), s.opt_resolution_values.begin() + 1); + +    s.opt[OPT_RESOLUTION].constraint.word_list = s.opt_resolution_values.data(); + +    if (reset_resolution_value) { +        s.resolution = *std::min_element(resolutions.begin(), resolutions.end()); +    } +} + +static void set_xy_range_option_values(Genesys_Scanner& s) +{ +    if (s.scan_method == ScanMethod::FLATBED) +    { +        s.opt_x_range = create_range(static_cast<float>(s.dev->model->x_size)); +        s.opt_y_range = create_range(static_cast<float>(s.dev->model->y_size)); +    } +  else +    { +        s.opt_x_range = create_range(static_cast<float>(s.dev->model->x_size_ta)); +        s.opt_y_range = create_range(static_cast<float>(s.dev->model->y_size_ta)); +    } + +    s.opt[OPT_TL_X].constraint.range = &s.opt_x_range; +    s.opt[OPT_TL_Y].constraint.range = &s.opt_y_range; +    s.opt[OPT_BR_X].constraint.range = &s.opt_x_range; +    s.opt[OPT_BR_Y].constraint.range = &s.opt_y_range; + +    s.pos_top_left_x = 0; +    s.pos_top_left_y = 0; +    s.pos_bottom_right_x = s.opt_x_range.max; +    s.pos_bottom_right_y = s.opt_y_range.max; +} + +static void init_options(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +  SANE_Int option; +  Genesys_Model *model = s->dev->model; + +  memset (s->opt, 0, sizeof (s->opt)); + +  for (option = 0; option < NUM_OPTIONS; ++option) +    { +      s->opt[option].size = sizeof (SANE_Word); +      s->opt[option].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; +    } +  s->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS; +  s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; +  s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; +  s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; +  s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + +  /* "Mode" group: */ +  s->opt[OPT_MODE_GROUP].name = "scanmode-group"; +  s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode"); +  s->opt[OPT_MODE_GROUP].desc = ""; +  s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_MODE_GROUP].size = 0; +  s->opt[OPT_MODE_GROUP].cap = 0; +  s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* scan mode */ +  s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE; +  s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE; +  s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE; +  s->opt[OPT_MODE].type = SANE_TYPE_STRING; +  s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; +  s->opt[OPT_MODE].size = max_string_size (mode_list); +  s->opt[OPT_MODE].constraint.string_list = mode_list; +  s->mode = SANE_VALUE_SCAN_MODE_GRAY; + +  /* scan source */ +    s->opt_source_values.clear(); +    for (const auto& resolution_setting : model->resolutions) { +        for (auto method : resolution_setting.methods) { +            s->opt_source_values.push_back(scan_method_to_option_string(method)); +        } +    } +    s->opt_source_values.push_back(nullptr); + +  s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE; +  s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE; +  s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE; +  s->opt[OPT_SOURCE].type = SANE_TYPE_STRING; +  s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; +    s->opt[OPT_SOURCE].size = max_string_size(s->opt_source_values); +    s->opt[OPT_SOURCE].constraint.string_list = s->opt_source_values.data(); +    if (s->opt_source_values.size() < 2) { +        throw SaneException("No scan methods specified for scanner"); +    } +    s->scan_method = model->default_method; + +  /* preview */ +  s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; +  s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; +  s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; +  s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; +  s->opt[OPT_PREVIEW].unit = SANE_UNIT_NONE; +  s->opt[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE; +  s->preview = false; + +  /* bit depth */ +  s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH; +  s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH; +  s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH; +  s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT; +  s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST; +  s->opt[OPT_BIT_DEPTH].size = sizeof (SANE_Word); +  s->opt[OPT_BIT_DEPTH].constraint.word_list = s->bpp_list; +  create_bpp_list (s, model->bpp_gray_values); +    s->bit_depth = model->bpp_gray_values[0]; + +    // resolution +  s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION; +  s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION; +  s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION; +  s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT; +  s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI; +  s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; +    set_resolution_option_values(*s, true); + +  /* "Geometry" group: */ +  s->opt[OPT_GEOMETRY_GROUP].name = SANE_NAME_GEOMETRY; +  s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry"); +  s->opt[OPT_GEOMETRY_GROUP].desc = ""; +  s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_GEOMETRY_GROUP].size = 0; +  s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +    s->opt_x_range = create_range(static_cast<float>(model->x_size)); +    s->opt_y_range = create_range(static_cast<float>(model->y_size)); + +    // scan area +  s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; +  s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; +  s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; +  s->opt[OPT_TL_X].type = SANE_TYPE_FIXED; +  s->opt[OPT_TL_X].unit = SANE_UNIT_MM; +  s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; + +  s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; +  s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; +  s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; +  s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED; +  s->opt[OPT_TL_Y].unit = SANE_UNIT_MM; +  s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; + +  s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X; +  s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; +  s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; +  s->opt[OPT_BR_X].type = SANE_TYPE_FIXED; +  s->opt[OPT_BR_X].unit = SANE_UNIT_MM; +  s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; + +  s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; +  s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; +  s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; +  s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED; +  s->opt[OPT_BR_Y].unit = SANE_UNIT_MM; +  s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; + +    set_xy_range_option_values(*s); + +  /* "Enhancement" group: */ +  s->opt[OPT_ENHANCEMENT_GROUP].name = SANE_NAME_ENHANCEMENT; +  s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement"); +  s->opt[OPT_ENHANCEMENT_GROUP].desc = ""; +  s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_ENHANCEMENT_GROUP].size = 0; +  s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* custom-gamma table */ +  s->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA; +  s->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA; +  s->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA; +  s->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL; +  s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_ADVANCED; +  s->custom_gamma = false; + +  /* grayscale gamma vector */ +  s->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR; +  s->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR; +  s->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR); + +  /* red gamma vector */ +  s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R; +  s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R; +  s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR_R); + +  /* green gamma vector */ +  s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G; +  s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G; +  s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR_G); + +  /* blue gamma vector */ +  s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B; +  s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B; +  s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR_B); + +  /* currently, there are only gamma table options in this group, +   * so if the scanner doesn't support gamma table, disable the +   * whole group */ +  if (!(model->flags & GENESYS_FLAG_CUSTOM_GAMMA)) +    { +      s->opt[OPT_ENHANCEMENT_GROUP].cap |= SANE_CAP_INACTIVE; +      s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE; +      DBG(DBG_info, "%s: custom gamma disabled\n", __func__); +    } + +  /* software base image enhancements, these are consuming as many +   * memory than used by the full scanned image and may fail at high +   * resolution +   */ +  /* software deskew */ +  s->opt[OPT_SWDESKEW].name = "swdeskew"; +  s->opt[OPT_SWDESKEW].title = "Software deskew"; +  s->opt[OPT_SWDESKEW].desc = "Request backend to rotate skewed pages digitally"; +  s->opt[OPT_SWDESKEW].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWDESKEW].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->swdeskew = false; + +  /* software deskew */ +  s->opt[OPT_SWDESPECK].name = "swdespeck"; +  s->opt[OPT_SWDESPECK].title = "Software despeck"; +  s->opt[OPT_SWDESPECK].desc = "Request backend to remove lone dots digitally"; +  s->opt[OPT_SWDESPECK].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWDESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->swdespeck = false; + +  /* software despeckle radius */ +  s->opt[OPT_DESPECK].name = "despeck"; +  s->opt[OPT_DESPECK].title = "Software despeckle diameter"; +  s->opt[OPT_DESPECK].desc = "Maximum diameter of lone dots to remove from scan"; +  s->opt[OPT_DESPECK].type = SANE_TYPE_INT; +  s->opt[OPT_DESPECK].unit = SANE_UNIT_NONE; +  s->opt[OPT_DESPECK].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_DESPECK].constraint.range = &swdespeck_range; +  s->opt[OPT_DESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED | SANE_CAP_INACTIVE; +  s->despeck = 1; + +  /* crop by software */ +  s->opt[OPT_SWCROP].name = "swcrop"; +  s->opt[OPT_SWCROP].title = SANE_I18N ("Software crop"); +  s->opt[OPT_SWCROP].desc = SANE_I18N ("Request backend to remove border from pages digitally"); +  s->opt[OPT_SWCROP].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWCROP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->opt[OPT_SWCROP].unit = SANE_UNIT_NONE; +  s->swcrop = false; + +  /* Software blank page skip */ +  s->opt[OPT_SWSKIP].name = "swskip"; +  s->opt[OPT_SWSKIP].title = SANE_I18N ("Software blank skip percentage"); +  s->opt[OPT_SWSKIP].desc = SANE_I18N("Request driver to discard pages with low numbers of dark pixels"); +  s->opt[OPT_SWSKIP].type = SANE_TYPE_FIXED; +  s->opt[OPT_SWSKIP].unit = SANE_UNIT_PERCENT; +  s->opt[OPT_SWSKIP].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_SWSKIP].constraint.range = &(percentage_range); +  s->opt[OPT_SWSKIP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->swskip = 0;    // disable by default + +  /* Software Derotate */ +  s->opt[OPT_SWDEROTATE].name = "swderotate"; +  s->opt[OPT_SWDEROTATE].title = SANE_I18N ("Software derotate"); +  s->opt[OPT_SWDEROTATE].desc = SANE_I18N("Request driver to detect and correct 90 degree image rotation"); +  s->opt[OPT_SWDEROTATE].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWDEROTATE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->opt[OPT_SWDEROTATE].unit = SANE_UNIT_NONE; +  s->swderotate = false; + +  /* Software brightness */ +  s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; +  s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; +  s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS; +  s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT; +  s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE; +  s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_BRIGHTNESS].constraint.range = &(enhance_range); +  s->opt[OPT_BRIGHTNESS].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; +  s->brightness = 0;    // disable by default + +  /* Sowftware contrast */ +  s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST; +  s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST; +  s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST; +  s->opt[OPT_CONTRAST].type = SANE_TYPE_INT; +  s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE; +  s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_CONTRAST].constraint.range = &(enhance_range); +  s->opt[OPT_CONTRAST].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; +  s->contrast = 0;  // disable by default + +  /* "Extras" group: */ +  s->opt[OPT_EXTRAS_GROUP].name = "extras-group"; +  s->opt[OPT_EXTRAS_GROUP].title = SANE_I18N ("Extras"); +  s->opt[OPT_EXTRAS_GROUP].desc = ""; +  s->opt[OPT_EXTRAS_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_EXTRAS_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_EXTRAS_GROUP].size = 0; +  s->opt[OPT_EXTRAS_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* BW threshold */ +  s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD; +  s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD; +  s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD; +  s->opt[OPT_THRESHOLD].type = SANE_TYPE_FIXED; +  s->opt[OPT_THRESHOLD].unit = SANE_UNIT_PERCENT; +  s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_THRESHOLD].constraint.range = &percentage_range; +  s->threshold = SANE_FIX(50); + +  /* BW threshold curve */ +  s->opt[OPT_THRESHOLD_CURVE].name = "threshold-curve"; +  s->opt[OPT_THRESHOLD_CURVE].title = SANE_I18N ("Threshold curve"); +  s->opt[OPT_THRESHOLD_CURVE].desc = SANE_I18N ("Dynamic threshold curve, from light to dark, normally 50-65"); +  s->opt[OPT_THRESHOLD_CURVE].type = SANE_TYPE_INT; +  s->opt[OPT_THRESHOLD_CURVE].unit = SANE_UNIT_NONE; +  s->opt[OPT_THRESHOLD_CURVE].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_THRESHOLD_CURVE].constraint.range = &threshold_curve_range; +  s->threshold_curve = 50; + +  /* disable_interpolation */ +  s->opt[OPT_DISABLE_INTERPOLATION].name = "disable-interpolation"; +  s->opt[OPT_DISABLE_INTERPOLATION].title = +    SANE_I18N ("Disable interpolation"); +  s->opt[OPT_DISABLE_INTERPOLATION].desc = +    SANE_I18N +    ("When using high resolutions where the horizontal resolution is smaller " +     "than the vertical resolution this disables horizontal interpolation."); +  s->opt[OPT_DISABLE_INTERPOLATION].type = SANE_TYPE_BOOL; +  s->opt[OPT_DISABLE_INTERPOLATION].unit = SANE_UNIT_NONE; +  s->opt[OPT_DISABLE_INTERPOLATION].constraint_type = SANE_CONSTRAINT_NONE; +  s->disable_interpolation = false; + +  /* color filter */ +  s->opt[OPT_COLOR_FILTER].name = "color-filter"; +  s->opt[OPT_COLOR_FILTER].title = SANE_I18N ("Color filter"); +  s->opt[OPT_COLOR_FILTER].desc = +    SANE_I18N +    ("When using gray or lineart this option selects the used color."); +  s->opt[OPT_COLOR_FILTER].type = SANE_TYPE_STRING; +  s->opt[OPT_COLOR_FILTER].constraint_type = SANE_CONSTRAINT_STRING_LIST; +  /* true gray not yet supported for GL847 and GL124 scanners */ +    if (!model->is_cis || model->asic_type==AsicType::GL847 || model->asic_type==AsicType::GL124) { +      s->opt[OPT_COLOR_FILTER].size = max_string_size (color_filter_list); +      s->opt[OPT_COLOR_FILTER].constraint.string_list = color_filter_list; +      s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[1]; +    } +  else +    { +      s->opt[OPT_COLOR_FILTER].size = max_string_size (cis_color_filter_list); +      s->opt[OPT_COLOR_FILTER].constraint.string_list = cis_color_filter_list; +      /* default to "None" ie true gray */ +      s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[3]; +    } + +    // no support for color filter for cis+gl646 scanners +    if (model->asic_type == AsicType::GL646 && model->is_cis) { +      DISABLE (OPT_COLOR_FILTER); +    } + +  /* calibration store file name */ +  s->opt[OPT_CALIBRATION_FILE].name = "calibration-file"; +  s->opt[OPT_CALIBRATION_FILE].title = SANE_I18N ("Calibration file"); +  s->opt[OPT_CALIBRATION_FILE].desc = SANE_I18N ("Specify the calibration file to use"); +  s->opt[OPT_CALIBRATION_FILE].type = SANE_TYPE_STRING; +  s->opt[OPT_CALIBRATION_FILE].unit = SANE_UNIT_NONE; +  s->opt[OPT_CALIBRATION_FILE].size = PATH_MAX; +  s->opt[OPT_CALIBRATION_FILE].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; +  s->opt[OPT_CALIBRATION_FILE].constraint_type = SANE_CONSTRAINT_NONE; +  s->calibration_file.clear(); +  /* disable option if ran as root */ +#ifdef HAVE_GETUID +  if(geteuid()==0) +    { +      DISABLE (OPT_CALIBRATION_FILE); +    } +#endif + +  /* expiration time for calibration cache entries */ +  s->opt[OPT_EXPIRATION_TIME].name = "expiration-time"; +  s->opt[OPT_EXPIRATION_TIME].title = SANE_I18N ("Calibration cache expiration time"); +  s->opt[OPT_EXPIRATION_TIME].desc = SANE_I18N ("Time (in minutes) before a cached calibration expires. " +     "A value of 0 means cache is not used. A negative value means cache never expires."); +  s->opt[OPT_EXPIRATION_TIME].type = SANE_TYPE_INT; +  s->opt[OPT_EXPIRATION_TIME].unit = SANE_UNIT_NONE; +  s->opt[OPT_EXPIRATION_TIME].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_EXPIRATION_TIME].constraint.range = &expiration_range; +  s->expiration_time = 60;  // 60 minutes by default + +  /* Powersave time (turn lamp off) */ +  s->opt[OPT_LAMP_OFF_TIME].name = "lamp-off-time"; +  s->opt[OPT_LAMP_OFF_TIME].title = SANE_I18N ("Lamp off time"); +  s->opt[OPT_LAMP_OFF_TIME].desc = +    SANE_I18N +    ("The lamp will be turned off after the given time (in minutes). " +     "A value of 0 means, that the lamp won't be turned off."); +  s->opt[OPT_LAMP_OFF_TIME].type = SANE_TYPE_INT; +  s->opt[OPT_LAMP_OFF_TIME].unit = SANE_UNIT_NONE; +  s->opt[OPT_LAMP_OFF_TIME].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_LAMP_OFF_TIME].constraint.range = &time_range; +  s->lamp_off_time = 15;    // 15 minutes + +  /* turn lamp off during scan */ +  s->opt[OPT_LAMP_OFF].name = "lamp-off-scan"; +  s->opt[OPT_LAMP_OFF].title = SANE_I18N ("Lamp off during scan"); +  s->opt[OPT_LAMP_OFF].desc = SANE_I18N ("The lamp will be turned off during scan. "); +  s->opt[OPT_LAMP_OFF].type = SANE_TYPE_BOOL; +  s->opt[OPT_LAMP_OFF].unit = SANE_UNIT_NONE; +  s->opt[OPT_LAMP_OFF].constraint_type = SANE_CONSTRAINT_NONE; +  s->lamp_off = false; + +  s->opt[OPT_SENSOR_GROUP].name = SANE_NAME_SENSORS; +  s->opt[OPT_SENSOR_GROUP].title = SANE_TITLE_SENSORS; +  s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS; +  s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_SENSOR_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_SENSOR_GROUP].size = 0; +  s->opt[OPT_SENSOR_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  s->opt[OPT_SCAN_SW].name = SANE_NAME_SCAN; +  s->opt[OPT_SCAN_SW].title = SANE_TITLE_SCAN; +  s->opt[OPT_SCAN_SW].desc = SANE_DESC_SCAN; +  s->opt[OPT_SCAN_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_SCAN_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_SCAN_SW) +    s->opt[OPT_SCAN_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_SCAN_SW].cap = SANE_CAP_INACTIVE; + +  /* SANE_NAME_FILE is not for buttons */ +  s->opt[OPT_FILE_SW].name = "file"; +  s->opt[OPT_FILE_SW].title = SANE_I18N ("File button"); +  s->opt[OPT_FILE_SW].desc = SANE_I18N ("File button"); +  s->opt[OPT_FILE_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_FILE_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_FILE_SW) +    s->opt[OPT_FILE_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_FILE_SW].cap = SANE_CAP_INACTIVE; + +  s->opt[OPT_EMAIL_SW].name = SANE_NAME_EMAIL; +  s->opt[OPT_EMAIL_SW].title = SANE_TITLE_EMAIL; +  s->opt[OPT_EMAIL_SW].desc = SANE_DESC_EMAIL; +  s->opt[OPT_EMAIL_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_EMAIL_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_EMAIL_SW) +    s->opt[OPT_EMAIL_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_EMAIL_SW].cap = SANE_CAP_INACTIVE; + +  s->opt[OPT_COPY_SW].name = SANE_NAME_COPY; +  s->opt[OPT_COPY_SW].title = SANE_TITLE_COPY; +  s->opt[OPT_COPY_SW].desc = SANE_DESC_COPY; +  s->opt[OPT_COPY_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_COPY_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_COPY_SW) +    s->opt[OPT_COPY_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_COPY_SW].cap = SANE_CAP_INACTIVE; + +  s->opt[OPT_PAGE_LOADED_SW].name = SANE_NAME_PAGE_LOADED; +  s->opt[OPT_PAGE_LOADED_SW].title = SANE_TITLE_PAGE_LOADED; +  s->opt[OPT_PAGE_LOADED_SW].desc = SANE_DESC_PAGE_LOADED; +  s->opt[OPT_PAGE_LOADED_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_PAGE_LOADED_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_PAGE_LOADED_SW) +    s->opt[OPT_PAGE_LOADED_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_PAGE_LOADED_SW].cap = SANE_CAP_INACTIVE; + +  /* OCR button */ +  s->opt[OPT_OCR_SW].name = "ocr"; +  s->opt[OPT_OCR_SW].title = SANE_I18N ("OCR button"); +  s->opt[OPT_OCR_SW].desc = SANE_I18N ("OCR button"); +  s->opt[OPT_OCR_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_OCR_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_OCR_SW) +    s->opt[OPT_OCR_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_OCR_SW].cap = SANE_CAP_INACTIVE; + +  /* power button */ +  s->opt[OPT_POWER_SW].name = "power"; +  s->opt[OPT_POWER_SW].title = SANE_I18N ("Power button"); +  s->opt[OPT_POWER_SW].desc = SANE_I18N ("Power button"); +  s->opt[OPT_POWER_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_POWER_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_POWER_SW) +    s->opt[OPT_POWER_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_POWER_SW].cap = SANE_CAP_INACTIVE; + +  /* extra button */ +  s->opt[OPT_EXTRA_SW].name = "extra"; +  s->opt[OPT_EXTRA_SW].title = SANE_I18N ("Extra button"); +  s->opt[OPT_EXTRA_SW].desc = SANE_I18N ("Extra button"); +  s->opt[OPT_EXTRA_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_EXTRA_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_EXTRA_SW) +    s->opt[OPT_EXTRA_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_EXTRA_SW].cap = SANE_CAP_INACTIVE; + +  /* calibration needed */ +  s->opt[OPT_NEED_CALIBRATION_SW].name = "need-calibration"; +  s->opt[OPT_NEED_CALIBRATION_SW].title = SANE_I18N ("Needs calibration"); +  s->opt[OPT_NEED_CALIBRATION_SW].desc = SANE_I18N ("The scanner needs calibration for the current settings"); +  s->opt[OPT_NEED_CALIBRATION_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_NEED_CALIBRATION_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_CALIBRATE) +    s->opt[OPT_NEED_CALIBRATION_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_NEED_CALIBRATION_SW].cap = SANE_CAP_INACTIVE; + +  /* button group */ +  s->opt[OPT_BUTTON_GROUP].name = "buttons"; +  s->opt[OPT_BUTTON_GROUP].title = SANE_I18N ("Buttons"); +  s->opt[OPT_BUTTON_GROUP].desc = ""; +  s->opt[OPT_BUTTON_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_BUTTON_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_BUTTON_GROUP].size = 0; +  s->opt[OPT_BUTTON_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* calibrate button */ +  s->opt[OPT_CALIBRATE].name = "calibrate"; +  s->opt[OPT_CALIBRATE].title = SANE_I18N ("Calibrate"); +  s->opt[OPT_CALIBRATE].desc = +    SANE_I18N ("Start calibration using special sheet"); +  s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON; +  s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_CALIBRATE) +    s->opt[OPT_CALIBRATE].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED | +      SANE_CAP_AUTOMATIC; +  else +    s->opt[OPT_CALIBRATE].cap = SANE_CAP_INACTIVE; + +  /* clear calibration cache button */ +  s->opt[OPT_CLEAR_CALIBRATION].name = "clear-calibration"; +  s->opt[OPT_CLEAR_CALIBRATION].title = SANE_I18N ("Clear calibration"); +  s->opt[OPT_CLEAR_CALIBRATION].desc = SANE_I18N ("Clear calibration cache"); +  s->opt[OPT_CLEAR_CALIBRATION].type = SANE_TYPE_BUTTON; +  s->opt[OPT_CLEAR_CALIBRATION].unit = SANE_UNIT_NONE; +  s->opt[OPT_CLEAR_CALIBRATION].size = 0; +  s->opt[OPT_CLEAR_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE; +  s->opt[OPT_CLEAR_CALIBRATION].cap = +    SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; + +  /* force calibration cache button */ +  s->opt[OPT_FORCE_CALIBRATION].name = "force-calibration"; +  s->opt[OPT_FORCE_CALIBRATION].title = SANE_I18N("Force calibration"); +  s->opt[OPT_FORCE_CALIBRATION].desc = SANE_I18N("Force calibration ignoring all and any calibration caches"); +  s->opt[OPT_FORCE_CALIBRATION].type = SANE_TYPE_BUTTON; +  s->opt[OPT_FORCE_CALIBRATION].unit = SANE_UNIT_NONE; +  s->opt[OPT_FORCE_CALIBRATION].size = 0; +  s->opt[OPT_FORCE_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE; +  s->opt[OPT_FORCE_CALIBRATION].cap = +    SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; + +    // ignore offsets option +    s->opt[OPT_IGNORE_OFFSETS].name = "ignore-internal-offsets"; +    s->opt[OPT_IGNORE_OFFSETS].title = SANE_I18N("Ignore internal offsets"); +    s->opt[OPT_IGNORE_OFFSETS].desc = +        SANE_I18N("Acquires the image including the internal calibration areas of the scanner"); +    s->opt[OPT_IGNORE_OFFSETS].type = SANE_TYPE_BUTTON; +    s->opt[OPT_IGNORE_OFFSETS].unit = SANE_UNIT_NONE; +    s->opt[OPT_IGNORE_OFFSETS].size = 0; +    s->opt[OPT_IGNORE_OFFSETS].constraint_type = SANE_CONSTRAINT_NONE; +    s->opt[OPT_IGNORE_OFFSETS].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | +                                     SANE_CAP_ADVANCED; + +    calc_parameters(s); +} + +static bool present; + +// this function is passed to C API, it must not throw +static SANE_Status +check_present (SANE_String_Const devname) noexcept +{ +    DBG_HELPER_ARGS(dbg, "%s detected.", devname); +    present = true; +  return SANE_STATUS_GOOD; +} + +static Genesys_Device* attach_usb_device(const char* devname, +                                         std::uint16_t vendor_id, std::uint16_t product_id) +{ +    Genesys_USB_Device_Entry* found_usb_dev = nullptr; +    for (auto& usb_dev : *s_usb_devices) { +        if (usb_dev.vendor == vendor_id && +            usb_dev.product == product_id) +        { +            found_usb_dev = &usb_dev; +            break; +        } +    } + +    if (found_usb_dev == nullptr) { +        throw SaneException("vendor 0x%xd product 0x%xd is not supported by this backend", +                            vendor_id, product_id); +    } + +    s_devices->emplace_back(); +    Genesys_Device* dev = &s_devices->back(); +    dev->file_name = devname; + +    dev->model = &found_usb_dev->model; +    dev->vendorId = found_usb_dev->vendor; +    dev->productId = found_usb_dev->product; +    dev->usb_mode = 0; // i.e. unset +    dev->already_initialized = false; +    return dev; +} + +static Genesys_Device* attach_device_by_name(SANE_String_Const devname, bool may_wait) +{ +    DBG_HELPER_ARGS(dbg, " devname: %s, may_wait = %d", devname, may_wait); + +    if (!devname) { +        throw SaneException("devname must not be nullptr"); +    } + +    for (auto& dev : *s_devices) { +        if (dev.file_name == devname) { +            DBG(DBG_info, "%s: device `%s' was already in device list\n", __func__, devname); +            return &dev; +        } +    } + +  DBG(DBG_info, "%s: trying to open device `%s'\n", __func__, devname); + +    UsbDevice usb_dev; + +    usb_dev.open(devname); +    DBG(DBG_info, "%s: device `%s' successfully opened\n", __func__, devname); + +    int vendor, product; +    usb_dev.get_vendor_product(vendor, product); +    usb_dev.close(); + +  /* KV-SS080 is an auxiliary device which requires a master device to be here */ +  if(vendor == 0x04da && product == 0x100f) +    { +        present = false; +      sanei_usb_find_devices (vendor, 0x1006, check_present); +      sanei_usb_find_devices (vendor, 0x1007, check_present); +      sanei_usb_find_devices (vendor, 0x1010, check_present); +        if (present == false) { +            throw SaneException("master device not present"); +        } +    } + +    Genesys_Device* dev = attach_usb_device(devname, vendor, product); + +    DBG(DBG_info, "%s: found %s flatbed scanner %s at %s\n", __func__, dev->model->vendor, +        dev->model->model, dev->file_name.c_str()); + +    return dev; +} + +// this function is passed to C API and must not throw +static SANE_Status attach_one_device(SANE_String_Const devname) noexcept +{ +    DBG_HELPER(dbg); +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        attach_device_by_name(devname, false); +    }); +} + +/* configuration framework functions */ + +// this function is passed to C API, it must not throw +static SANE_Status +config_attach_genesys(SANEI_Config __sane_unused__ *config, const char *devname) noexcept +{ +  /* the devname has been processed and is ready to be used +   * directly. Since the backend is an USB only one, we can +   * call sanei_usb_attach_matching_devices straight */ +  sanei_usb_attach_matching_devices (devname, attach_one_device); + +  return SANE_STATUS_GOOD; +} + +/* probes for scanner to attach to the backend */ +static void probe_genesys_devices() +{ +    DBG_HELPER(dbg); +    if (is_testing_mode()) { +        attach_usb_device(get_testing_device_name().c_str(), +                          get_testing_vendor_id(), get_testing_product_id()); +        return; +    } + +  SANEI_Config config; + +    // set configuration options structure : no option for this backend +    config.descriptors = nullptr; +    config.values = nullptr; +  config.count = 0; + +    TIE(sanei_configure_attach(GENESYS_CONFIG_FILE, &config, config_attach_genesys)); + +    DBG(DBG_info, "%s: %zu devices currently attached\n", __func__, s_devices->size()); +} + +/** + * This should be changed if one of the substructures of +   Genesys_Calibration_Cache change, but it must be changed if there are +   changes that don't change size -- at least for now, as we store most +   of Genesys_Calibration_Cache as is. +*/ +static const char* CALIBRATION_IDENT = "sane_genesys"; +static const int CALIBRATION_VERSION = 21; + +bool read_calibration(std::istream& str, Genesys_Device::Calibration& calibration, +                      const std::string& path) +{ +    DBG_HELPER(dbg); + +    std::string ident; +    serialize(str, ident); + +    if (ident != CALIBRATION_IDENT) { +        DBG(DBG_info, "%s: Incorrect calibration file '%s' header\n", __func__, path.c_str()); +        return false; +    } + +    size_t version; +    serialize(str, version); + +    if (version != CALIBRATION_VERSION) { +        DBG(DBG_info, "%s: Incorrect calibration file '%s' version\n", __func__, path.c_str()); +        return false; +    } + +    calibration.clear(); +    serialize(str, calibration); +    return true; +} + +/** + * reads previously cached calibration data + * from file defined in dev->calib_file + */ +static bool sanei_genesys_read_calibration(Genesys_Device::Calibration& calibration, +                                           const std::string& path) +{ +    DBG_HELPER(dbg); + +    std::ifstream str; +    str.open(path); +    if (!str.is_open()) { +        DBG(DBG_info, "%s: Cannot open %s\n", __func__, path.c_str()); +        return false; +    } + +    return read_calibration(str, calibration, path); +} + +void write_calibration(std::ostream& str, Genesys_Device::Calibration& calibration) +{ +    std::string ident = CALIBRATION_IDENT; +    serialize(str, ident); +    size_t version = CALIBRATION_VERSION; +    serialize(str, version); +    serialize_newline(str); +    serialize(str, calibration); +} + +static void write_calibration(Genesys_Device::Calibration& calibration, const std::string& path) +{ +    DBG_HELPER(dbg); + +    std::ofstream str; +    str.open(path); +    if (!str.is_open()) { +        throw SaneException("Cannot open calibration for writing"); +    } +    write_calibration(str, calibration); +} + +/** @brief buffer scanned picture + * In order to allow digital processing, we must be able to put all the + * scanned picture in a buffer. + */ +static void genesys_buffer_image(Genesys_Scanner *s) +{ +    DBG_HELPER(dbg); +  size_t maximum;     /**> maximum bytes size of the scan */ +  size_t len;	      /**> length of scanned data read */ +  size_t total;	      /**> total of butes read */ +  size_t size;	      /**> size of image buffer */ +  size_t read_size;   /**> size of reads */ +  int lines;	      /** number of lines of the scan */ +  Genesys_Device *dev = s->dev; + +  /* compute maximum number of lines for the scan */ +  if (s->params.lines > 0) +    { +      lines = s->params.lines; +    } +  else +    { +        lines = static_cast<int>((dev->model->y_size * dev->settings.yres) / MM_PER_INCH); +    } +  DBG(DBG_info, "%s: buffering %d lines of %d bytes\n", __func__, lines, +      s->params.bytes_per_line); + +  /* maximum bytes to read */ +  maximum = s->params.bytes_per_line * lines; +    if (s->dev->settings.scan_mode == ScanColorMode::LINEART) { +      maximum *= 8; +    } + +  /* initial size of the read buffer */ +  size = +    ((2048 * 2048) / s->params.bytes_per_line) * s->params.bytes_per_line; + +  /* read size */ +  read_size = size / 2; + +  dev->img_buffer.resize(size); + +  /* loop reading data until we reach maximum or EOF */ +    total = 0; +    while (total < maximum) { +      len = size - maximum; +      if (len > read_size) +	{ +	  len = read_size; +	} + +        try { +            genesys_read_ordered_data(dev, dev->img_buffer.data() + total, &len); +        } catch (const SaneException& e) { +            if (e.status() == SANE_STATUS_EOF) { +                // ideally we shouldn't end up here, but because computations are duplicated and +                // slightly different everywhere in the genesys backend, we have no other choice +                break; +            } +            throw; +        } +      total += len; + +        // do we need to enlarge read buffer ? +        if (total + read_size > size) { +            size += read_size; +            dev->img_buffer.resize(size); +        } +    } + +  /* since digital processing is going to take place, +   * issue head parking command so that the head move while +   * computing so we can save time +   */ +    if (!dev->model->is_sheetfed && !dev->parking) { +        dev->cmd_set->move_back_home(dev, dev->model->flags & GENESYS_FLAG_MUST_WAIT); +      dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT); +    } + +  /* in case of dynamic lineart, we have buffered gray data which +   * must be converted to lineart first */ +    if (s->dev->settings.scan_mode == ScanColorMode::LINEART) { +      total/=8; +      std::vector<uint8_t> lineart(total); + +      genesys_gray_lineart (dev, +                            dev->img_buffer.data(), +                            lineart.data(), +                            dev->settings.pixels, +                            (total*8)/dev->settings.pixels, +                            dev->settings.threshold); +      dev->img_buffer = lineart; +    } + +  /* update counters */ +  dev->total_bytes_to_read = total; +  dev->total_bytes_read = 0; + +  /* update params */ +  s->params.lines = total / s->params.bytes_per_line; +  if (DBG_LEVEL >= DBG_io2) +    { +      sanei_genesys_write_pnm_file("gl_unprocessed.pnm", dev->img_buffer.data(), s->params.depth, +                                   s->params.format==SANE_FRAME_RGB ? 3 : 1, +                                   s->params.pixels_per_line, s->params.lines); +    } +} + +/* -------------------------- SANE API functions ------------------------- */ + +void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize) +{ +  DBG_INIT (); +    DBG_HELPER_ARGS(dbg, "authorize %s null", authorize ? "!=" : "=="); +    DBG(DBG_init, "SANE Genesys backend from %s\n", PACKAGE_STRING); + +    if (!is_testing_mode()) { +#ifdef HAVE_LIBUSB +        DBG(DBG_init, "SANE Genesys backend built with libusb-1.0\n"); +#endif +#ifdef HAVE_LIBUSB_LEGACY +        DBG(DBG_init, "SANE Genesys backend built with libusb\n"); +#endif +    } + +    if (version_code) { +        *version_code = SANE_VERSION_CODE(SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0); +    } + +    if (!is_testing_mode()) { +        sanei_usb_init(); +    } + +  /* init sanei_magic */ +  sanei_magic_init(); + +  s_scanners.init(); +  s_devices.init(); +  s_sane_devices.init(); +    s_sane_devices_data.init(); +  s_sane_devices_ptrs.init(); +  genesys_init_sensor_tables(); +  genesys_init_frontend_tables(); +    genesys_init_gpo_tables(); +    genesys_init_motor_tables(); +    genesys_init_motor_profile_tables(); +    genesys_init_usb_device_tables(); + + +  DBG(DBG_info, "%s: %s endian machine\n", __func__, +#ifdef WORDS_BIGENDIAN +       "big" +#else +       "little" +#endif +    ); + +    // cold-plug case :detection of allready connected scanners +    probe_genesys_devices(); +} + + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_init(SANE_Int * version_code, SANE_Auth_Callback authorize) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_init_impl(version_code, authorize); +    }); +} + +void +sane_exit_impl(void) +{ +    DBG_HELPER(dbg); + +    if (!is_testing_mode()) { +        sanei_usb_exit(); +    } + +  run_functions_at_backend_exit(); +} + +SANE_GENESYS_API_LINKAGE +void sane_exit() +{ +    catch_all_exceptions(__func__, [](){ sane_exit_impl(); }); +} + +void sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_only) +{ +    DBG_HELPER_ARGS(dbg, "local_only = %s", local_only ? "true" : "false"); + +    if (!is_testing_mode()) { +        // hot-plug case : detection of newly connected scanners */ +        sanei_usb_scan_devices(); +    } +    probe_genesys_devices(); + +    s_sane_devices->clear(); +    s_sane_devices_data->clear(); +    s_sane_devices_ptrs->clear(); +    s_sane_devices->reserve(s_devices->size()); +    s_sane_devices_data->reserve(s_devices->size()); +    s_sane_devices_ptrs->reserve(s_devices->size() + 1); + +    for (auto dev_it = s_devices->begin(); dev_it != s_devices->end();) { + +        if (is_testing_mode()) { +            present = true; +        } else { +            present = false; +            sanei_usb_find_devices(dev_it->vendorId, dev_it->productId, check_present); +        } + +        if (present) { +            s_sane_devices->emplace_back(); +            s_sane_devices_data->emplace_back(); +            auto& sane_device = s_sane_devices->back(); +            auto& sane_device_data = s_sane_devices_data->back(); +            sane_device_data.name = dev_it->file_name; +            sane_device.name = sane_device_data.name.c_str(); +            sane_device.vendor = dev_it->model->vendor; +            sane_device.model = dev_it->model->model; +            sane_device.type = "flatbed scanner"; +            s_sane_devices_ptrs->push_back(&sane_device); +            dev_it++; +        } else { +            dev_it = s_devices->erase(dev_it); +        } +    } +    s_sane_devices_ptrs->push_back(nullptr); + +    *const_cast<SANE_Device***>(device_list) = s_sane_devices_ptrs->data(); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_get_devices(const SANE_Device *** device_list, SANE_Bool local_only) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_get_devices_impl(device_list, local_only); +    }); +} + +static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) +{ +    DBG_HELPER_ARGS(dbg, "devicename = %s", devicename); +    Genesys_Device* dev = nullptr; + +  /* devicename="" or devicename="genesys" are default values that use +   * first available device +   */ +    if (devicename[0] && strcmp ("genesys", devicename) != 0) { +      /* search for the given devicename in the device list */ +        for (auto& d : *s_devices) { +            if (d.file_name == devicename) { +                dev = &d; +                break; +            } +        } + +        if (dev) { +            DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name); +        } else if (is_testing_mode()) { +            DBG(DBG_info, "%s: couldn't find `%s' in devlist, not attaching", __func__, devicename); +        } else { +            DBG(DBG_info, "%s: couldn't find `%s' in devlist, trying attach\n", __func__, +                devicename); +            dbg.status("attach_device_by_name"); +            dev = attach_device_by_name(devicename, true); +            dbg.clear(); +        } +    } else { +        // empty devicename or "genesys" -> use first device +        if (!s_devices->empty()) { +            dev = &s_devices->front(); +            DBG(DBG_info, "%s: empty devicename, trying `%s'\n", __func__, dev->file_name.c_str()); +        } +    } + +    if (!dev) { +        throw SaneException("could not find the device to open: %s", devicename); +    } + +  if (dev->model->flags & GENESYS_FLAG_UNTESTED) +    { +      DBG(DBG_error0, "WARNING: Your scanner is not fully supported or at least \n"); +      DBG(DBG_error0, "         had only limited testing. Please be careful and \n"); +      DBG(DBG_error0, "         report any failure/success to \n"); +      DBG(DBG_error0, "         sane-devel@alioth-lists.debian.net. Please provide as many\n"); +      DBG(DBG_error0, "         details as possible, e.g. the exact name of your\n"); +      DBG(DBG_error0, "         scanner and what does (not) work.\n"); +    } + +    dbg.vstatus("open device '%s'", dev->file_name.c_str()); + +    if (is_testing_mode()) { +        auto interface = std::unique_ptr<TestScannerInterface>{new TestScannerInterface{dev}}; +        interface->set_checkpoint_callback(get_testing_checkpoint_callback()); +        dev->interface = std::move(interface); +    } else { +        dev->interface = std::unique_ptr<ScannerInterfaceUsb>{new ScannerInterfaceUsb{dev}}; +    } +    dev->interface->get_usb_device().open(dev->file_name.c_str()); +    dbg.clear(); + +  s_scanners->push_back(Genesys_Scanner()); +  auto* s = &s_scanners->back(); + +  s->dev = dev; +    s->scanning = false; +    s->dev->parking = false; +    s->dev->read_active = false; +  s->dev->force_calibration = 0; +  s->dev->line_count = 0; + +  *handle = s; + +    if (!dev->already_initialized) { +        sanei_genesys_init_structs (dev); +    } + +    init_options(s); + +    sanei_genesys_init_cmd_set(s->dev); + +    // FIXME: we create sensor tables for the sensor, this should happen when we know which sensor +    // we will select +    dev->cmd_set->init(dev); + +    // some hardware capabilities are detected through sensors +    s->dev->cmd_set->update_hardware_sensors (s); + +  /* here is the place to fetch a stored calibration cache */ +  if (s->dev->force_calibration == 0) +    { +        auto path = calibration_filename(s->dev); +        s->calibration_file = path; +        s->dev->calib_file = path; +      DBG(DBG_info, "%s: Calibration filename set to:\n", __func__); +      DBG(DBG_info, "%s: >%s<\n", __func__, s->dev->calib_file.c_str()); + +        catch_all_exceptions(__func__, [&]() +        { +            sanei_genesys_read_calibration(s->dev->calibration_cache, s->dev->calib_file); +        }); +    } +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_open(SANE_String_Const devicename, SANE_Handle* handle) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_open_impl(devicename, handle); +    }); +} + +void +sane_close_impl(SANE_Handle handle) +{ +    DBG_HELPER(dbg); + +  /* remove handle from list of open handles: */ +  auto it = s_scanners->end(); +  for (auto it2 = s_scanners->begin(); it2 != s_scanners->end(); it2++) +    { +      if (&*it2 == handle) { +          it = it2; +          break; +        } +    } +  if (it == s_scanners->end()) +    { +      DBG(DBG_error, "%s: invalid handle %p\n", __func__, handle); +      return;			/* oops, not a handle we know about */ +    } + +  Genesys_Scanner* s = &*it; + +  /* eject document for sheetfed scanners */ +    if (s->dev->model->is_sheetfed) { +        catch_all_exceptions(__func__, [&](){ s->dev->cmd_set->eject_document(s->dev); }); +    } +  else +    { +      /* in case scanner is parking, wait for the head +       * to reach home position */ +        if (s->dev->parking) { +            sanei_genesys_wait_for_home(s->dev); +        } +    } + +    // enable power saving before leaving +    s->dev->cmd_set->save_power(s->dev, true); + +    // here is the place to store calibration cache +    if (s->dev->force_calibration == 0 && !is_testing_mode()) { +        catch_all_exceptions(__func__, [&](){ write_calibration(s->dev->calibration_cache, +                                                                s->dev->calib_file); }); +    } + +    s->dev->already_initialized = false; + +  s->dev->clear(); + +    // LAMP OFF : same register across all the ASICs */ +    s->dev->interface->write_register(0x03, 0x00); + +    catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().clear_halt(); }); + +    // we need this to avoid these ASIC getting stuck in bulk writes +    catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().reset(); }); + +    // not freeing s->dev because it's in the dev list +    catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().close(); }); + +  s_scanners->erase(it); +} + +SANE_GENESYS_API_LINKAGE +void sane_close(SANE_Handle handle) +{ +    catch_all_exceptions(__func__, [=]() +    { +        sane_close_impl(handle); +    }); +} + +const SANE_Option_Descriptor * +sane_get_option_descriptor_impl(SANE_Handle handle, SANE_Int option) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (static_cast<unsigned>(option) >= NUM_OPTIONS) { +        return nullptr; +    } + +  DBG(DBG_io2, "%s: option = %s (%d)\n", __func__, s->opt[option].name, option); +  return s->opt + option; +} + + +SANE_GENESYS_API_LINKAGE +const SANE_Option_Descriptor* sane_get_option_descriptor(SANE_Handle handle, SANE_Int option) +{ +    const SANE_Option_Descriptor* ret = nullptr; +    catch_all_exceptions(__func__, [&]() +    { +        ret = sane_get_option_descriptor_impl(handle, option); +    }); +    return ret; +} + +static void print_option(DebugMessageHelper& dbg, const Genesys_Scanner& s, int option, void* val) +{ +    switch (s.opt[option].type) { +        case SANE_TYPE_INT: { +            dbg.vlog(DBG_proc, "value: %d", *reinterpret_cast<SANE_Word*>(val)); +            return; +        } +        case SANE_TYPE_BOOL: { +            dbg.vlog(DBG_proc, "value: %s", *reinterpret_cast<SANE_Bool*>(val) ? "true" : "false"); +            return; +        } +        case SANE_TYPE_FIXED: { +            dbg.vlog(DBG_proc, "value: %f", SANE_UNFIX(*reinterpret_cast<SANE_Word*>(val))); +            return; +        } +        case SANE_TYPE_STRING: { +            dbg.vlog(DBG_proc, "value: %s", reinterpret_cast<char*>(val)); +            return; +        } +        default: break; +    } +    dbg.log(DBG_proc, "value: (non-printable)"); +} + +static void get_option_value(Genesys_Scanner* s, int option, void* val) +{ +    DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option); +  unsigned int i; +    SANE_Word* table = nullptr; +  std::vector<uint16_t> gamma_table; +  unsigned option_size = 0; + +    const Genesys_Sensor* sensor = nullptr; +    if (sanei_genesys_has_sensor(s->dev, s->dev->settings.xres, s->dev->settings.get_channels(), +                                 s->dev->settings.scan_method)) +    { +        sensor = &sanei_genesys_find_sensor(s->dev, s->dev->settings.xres, +                                            s->dev->settings.get_channels(), +                                            s->dev->settings.scan_method); +    } + +  switch (option) +    { +      /* geometry */ +    case OPT_TL_X: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_x; +        break; +    case OPT_TL_Y: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_y; +        break; +    case OPT_BR_X: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_x; +        break; +    case OPT_BR_Y: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_y; +        break; +      /* word options: */ +    case OPT_NUM_OPTS: +        *reinterpret_cast<SANE_Word*>(val) = NUM_OPTIONS; +        break; +    case OPT_RESOLUTION: +        *reinterpret_cast<SANE_Word*>(val) = s->resolution; +        break; +    case OPT_BIT_DEPTH: +        *reinterpret_cast<SANE_Word*>(val) = s->bit_depth; +        break; +    case OPT_PREVIEW: +        *reinterpret_cast<SANE_Word*>(val) = s->preview; +        break; +    case OPT_THRESHOLD: +        *reinterpret_cast<SANE_Word*>(val) = s->threshold; +        break; +    case OPT_THRESHOLD_CURVE: +        *reinterpret_cast<SANE_Word*>(val) = s->threshold_curve; +        break; +    case OPT_DISABLE_INTERPOLATION: +        *reinterpret_cast<SANE_Word*>(val) = s->disable_interpolation; +        break; +    case OPT_LAMP_OFF: +        *reinterpret_cast<SANE_Word*>(val) = s->lamp_off; +        break; +    case OPT_LAMP_OFF_TIME: +        *reinterpret_cast<SANE_Word*>(val) = s->lamp_off_time; +        break; +    case OPT_SWDESKEW: +        *reinterpret_cast<SANE_Word*>(val) = s->swdeskew; +        break; +    case OPT_SWCROP: +        *reinterpret_cast<SANE_Word*>(val) = s->swcrop; +        break; +    case OPT_SWDESPECK: +        *reinterpret_cast<SANE_Word*>(val) = s->swdespeck; +        break; +    case OPT_SWDEROTATE: +        *reinterpret_cast<SANE_Word*>(val) = s->swderotate; +        break; +    case OPT_SWSKIP: +        *reinterpret_cast<SANE_Word*>(val) = s->swskip; +        break; +    case OPT_DESPECK: +        *reinterpret_cast<SANE_Word*>(val) = s->despeck; +        break; +    case OPT_CONTRAST: +        *reinterpret_cast<SANE_Word*>(val) = s->contrast; +        break; +    case OPT_BRIGHTNESS: +        *reinterpret_cast<SANE_Word*>(val) = s->brightness; +        break; +    case OPT_EXPIRATION_TIME: +        *reinterpret_cast<SANE_Word*>(val) = s->expiration_time; +        break; +    case OPT_CUSTOM_GAMMA: +        *reinterpret_cast<SANE_Word*>(val) = s->custom_gamma; +        break; + +      /* string options: */ +    case OPT_MODE: +        std::strcpy(reinterpret_cast<char*>(val), s->mode.c_str()); +        break; +    case OPT_COLOR_FILTER: +        std::strcpy(reinterpret_cast<char*>(val), s->color_filter.c_str()); +        break; +    case OPT_CALIBRATION_FILE: +        std::strcpy(reinterpret_cast<char*>(val), s->calibration_file.c_str()); +        break; +    case OPT_SOURCE: +        std::strcpy(reinterpret_cast<char*>(val), scan_method_to_option_string(s->scan_method)); +        break; + +      /* word array options */ +    case OPT_GAMMA_VECTOR: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        if (s->color_filter == "Red") { +            gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED); +        } else if (s->color_filter == "Blue") { +            gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE); +        } else { +            gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN); +        } +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +        } +        break; +    case OPT_GAMMA_VECTOR_R: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +	} +      break; +    case OPT_GAMMA_VECTOR_G: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_B: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +        } +      break; +      /* sensors */ +    case OPT_SCAN_SW: +    case OPT_FILE_SW: +    case OPT_EMAIL_SW: +    case OPT_COPY_SW: +    case OPT_PAGE_LOADED_SW: +    case OPT_OCR_SW: +    case OPT_POWER_SW: +    case OPT_EXTRA_SW: +        s->dev->cmd_set->update_hardware_sensors(s); +        *reinterpret_cast<SANE_Bool*>(val) = s->buttons[genesys_option_to_button(option)].read(); +        break; + +        case OPT_NEED_CALIBRATION_SW: { +            if (!sensor) { +                throw SaneException("Unsupported scanner mode selected"); +            } + +            // scanner needs calibration for current mode unless a matching calibration cache is +            // found + +            bool result = true; + +            auto session = s->dev->cmd_set->calculate_scan_session(s->dev, *sensor, +                                                                   s->dev->settings); + +            for (auto& cache : s->dev->calibration_cache) { +                if (sanei_genesys_is_compatible_calibration(s->dev, session, &cache, false)) { +                    *reinterpret_cast<SANE_Bool*>(val) = SANE_FALSE; +                } +            } +            *reinterpret_cast<SANE_Bool*>(val) = result; +            break; +        } +    default: +      DBG(DBG_warn, "%s: can't get unknown option %d\n", __func__, option); +    } +    print_option(dbg, *s, option, val); +} + +/** @brief set calibration file value + * Set calibration file value. Load new cache values from file if it exists, + * else creates the file*/ +static void set_calibration_value(Genesys_Scanner* s, const char* val) +{ +    DBG_HELPER(dbg); + +    std::string new_calib_path = val; +    Genesys_Device::Calibration new_calibration; + +    bool is_calib_success = false; +    catch_all_exceptions(__func__, [&]() +    { +        is_calib_success = sanei_genesys_read_calibration(new_calibration, new_calib_path); +    }); + +    if (!is_calib_success) { +        return; +    } + +    s->dev->calibration_cache = std::move(new_calibration); +    s->dev->calib_file = new_calib_path; +    s->calibration_file = new_calib_path; +    DBG(DBG_info, "%s: Calibration filename set to '%s':\n", __func__, new_calib_path.c_str()); +} + +/* sets an option , called by sane_control_option */ +static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int* myinfo) +{ +    DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option); +    print_option(dbg, *s, option, val); + +  SANE_Word *table; +  unsigned int i; +  unsigned option_size = 0; + +  switch (option) +    { +    case OPT_TL_X: +        s->pos_top_left_x = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_TL_Y: +        s->pos_top_left_y = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_BR_X: +        s->pos_bottom_right_x = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_BR_Y: +        s->pos_bottom_right_y = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_RESOLUTION: +        s->resolution = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_THRESHOLD: +        s->threshold = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_THRESHOLD_CURVE: +        s->threshold_curve = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWCROP: +        s->swcrop = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWDESKEW: +        s->swdeskew = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_DESPECK: +        s->despeck = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWDEROTATE: +        s->swderotate = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWSKIP: +        s->swskip = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_DISABLE_INTERPOLATION: +        s->disable_interpolation = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_LAMP_OFF: +        s->lamp_off = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_PREVIEW: +        s->preview = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_BRIGHTNESS: +        s->brightness = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_CONTRAST: +        s->contrast = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWDESPECK: +        s->swdespeck = *reinterpret_cast<SANE_Word*>(val); +        if (s->swdespeck) { +            ENABLE(OPT_DESPECK); +        } else { +            DISABLE(OPT_DESPECK); +        } +        calc_parameters(s); +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    /* software enhancement functions only apply to 8 or 1 bits data */ +    case OPT_BIT_DEPTH: +        s->bit_depth = *reinterpret_cast<SANE_Word*>(val); +        if(s->bit_depth>8) +        { +          DISABLE(OPT_SWDESKEW); +          DISABLE(OPT_SWDESPECK); +          DISABLE(OPT_SWCROP); +          DISABLE(OPT_DESPECK); +          DISABLE(OPT_SWDEROTATE); +          DISABLE(OPT_SWSKIP); +          DISABLE(OPT_CONTRAST); +          DISABLE(OPT_BRIGHTNESS); +        } +      else +        { +          ENABLE(OPT_SWDESKEW); +          ENABLE(OPT_SWDESPECK); +          ENABLE(OPT_SWCROP); +          ENABLE(OPT_DESPECK); +          ENABLE(OPT_SWDEROTATE); +          ENABLE(OPT_SWSKIP); +          ENABLE(OPT_CONTRAST); +          ENABLE(OPT_BRIGHTNESS); +        } +        calc_parameters(s); +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    case OPT_SOURCE: { +        auto scan_method = option_string_to_scan_method(reinterpret_cast<const char*>(val)); +        if (s->scan_method != scan_method) { +            s->scan_method = scan_method; + +            set_xy_range_option_values(*s); +            set_resolution_option_values(*s, false); + +            *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +        } +        break; +    } +    case OPT_MODE: +      s->mode = reinterpret_cast<const char*>(val); + +      if (s->mode == SANE_VALUE_SCAN_MODE_LINEART) +	{ +	  ENABLE (OPT_THRESHOLD); +	  ENABLE (OPT_THRESHOLD_CURVE); +	  DISABLE (OPT_BIT_DEPTH); +                if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) { +                    ENABLE(OPT_COLOR_FILTER); +                } +	} +      else +	{ +	  DISABLE (OPT_THRESHOLD); +	  DISABLE (OPT_THRESHOLD_CURVE); +          if (s->mode == SANE_VALUE_SCAN_MODE_GRAY) +	    { +                    if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) { +                        ENABLE(OPT_COLOR_FILTER); +                    } +	      create_bpp_list (s, s->dev->model->bpp_gray_values); +                    s->bit_depth = s->dev->model->bpp_gray_values[0]; +	    } +	  else +	    { +	      DISABLE (OPT_COLOR_FILTER); +	      create_bpp_list (s, s->dev->model->bpp_color_values); +                    s->bit_depth = s->dev->model->bpp_color_values[0]; +	    } +	} +        calc_parameters(s); + +      /* if custom gamma, toggle gamma table options according to the mode */ +      if (s->custom_gamma) +	{ +          if (s->mode == SANE_VALUE_SCAN_MODE_COLOR) +	    { +	      DISABLE (OPT_GAMMA_VECTOR); +	      ENABLE (OPT_GAMMA_VECTOR_R); +	      ENABLE (OPT_GAMMA_VECTOR_G); +	      ENABLE (OPT_GAMMA_VECTOR_B); +	    } +	  else +	    { +	      ENABLE (OPT_GAMMA_VECTOR); +	      DISABLE (OPT_GAMMA_VECTOR_R); +	      DISABLE (OPT_GAMMA_VECTOR_G); +	      DISABLE (OPT_GAMMA_VECTOR_B); +	    } +	} + +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    case OPT_COLOR_FILTER: +      s->color_filter = reinterpret_cast<const char*>(val); +        calc_parameters(s); +      break; +    case OPT_CALIBRATION_FILE: +            if (s->dev->force_calibration == 0) { +                set_calibration_value(s, reinterpret_cast<const char*>(val)); +            } +            break; +    case OPT_LAMP_OFF_TIME: +        if (*reinterpret_cast<SANE_Word*>(val) != s->lamp_off_time) { +            s->lamp_off_time = *reinterpret_cast<SANE_Word*>(val); +                s->dev->cmd_set->set_powersaving(s->dev, s->lamp_off_time); +        } +        break; +    case OPT_EXPIRATION_TIME: +        if (*reinterpret_cast<SANE_Word*>(val) != s->expiration_time) { +            s->expiration_time = *reinterpret_cast<SANE_Word*>(val); +            // BUG: this is most likely not intended behavior, found out during refactor +                s->dev->cmd_set->set_powersaving(s->dev, s->expiration_time); +	} +        break; + +    case OPT_CUSTOM_GAMMA: +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +        s->custom_gamma = *reinterpret_cast<SANE_Bool*>(val); + +        if (s->custom_gamma) { +          if (s->mode == SANE_VALUE_SCAN_MODE_COLOR) +	    { +	      DISABLE (OPT_GAMMA_VECTOR); +	      ENABLE (OPT_GAMMA_VECTOR_R); +	      ENABLE (OPT_GAMMA_VECTOR_G); +	      ENABLE (OPT_GAMMA_VECTOR_B); +	    } +	  else +	    { +	      ENABLE (OPT_GAMMA_VECTOR); +	      DISABLE (OPT_GAMMA_VECTOR_R); +	      DISABLE (OPT_GAMMA_VECTOR_G); +	      DISABLE (OPT_GAMMA_VECTOR_B); +	    } +	} +      else +	{ +	  DISABLE (OPT_GAMMA_VECTOR); +	  DISABLE (OPT_GAMMA_VECTOR_R); +	  DISABLE (OPT_GAMMA_VECTOR_G); +	  DISABLE (OPT_GAMMA_VECTOR_B); +            for (auto& table : s->dev->gamma_override_tables) { +                table.clear(); +            } +	} +      break; + +    case OPT_GAMMA_VECTOR: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); + +        s->dev->gamma_override_tables[GENESYS_RED].resize(option_size); +        s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size); +        s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_RED][i] = table[i]; +            s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i]; +            s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_R: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        s->dev->gamma_override_tables[GENESYS_RED].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_RED][i] = table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_G: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_B: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i]; +        } +      break; +        case OPT_CALIBRATE: { +            auto& sensor = sanei_genesys_find_sensor_for_write(s->dev, s->dev->settings.xres, +                                                               s->dev->settings.get_channels(), +                                                               s->dev->settings.scan_method); +            catch_all_exceptions(__func__, [&]() +            { +                s->dev->cmd_set->save_power(s->dev, false); +                genesys_scanner_calibration(s->dev, sensor); +            }); +            catch_all_exceptions(__func__, [&]() +            { +                s->dev->cmd_set->save_power(s->dev, true); +            }); +            *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +            break; +        } +    case OPT_CLEAR_CALIBRATION: +      s->dev->calibration_cache.clear(); + +      /* remove file */ +      unlink(s->dev->calib_file.c_str()); +      /* signals that sensors will have to be read again */ +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    case OPT_FORCE_CALIBRATION: +      s->dev->force_calibration = 1; +      s->dev->calibration_cache.clear(); +      s->dev->calib_file.clear(); + +      /* signals that sensors will have to be read again */ +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; + +        case OPT_IGNORE_OFFSETS: { +            s->dev->ignore_offsets = true; +            break; +        } +    default: +      DBG(DBG_warn, "%s: can't set unknown option %d\n", __func__, option); +    } +} + + +/* sets and gets scanner option values */ +void sane_control_option_impl(SANE_Handle handle, SANE_Int option, +                              SANE_Action action, void *val, SANE_Int * info) +{ +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); +    auto action_str = (action == SANE_ACTION_GET_VALUE) ? "get" : +                      (action == SANE_ACTION_SET_VALUE) ? "set" : +                      (action == SANE_ACTION_SET_AUTO) ? "set_auto" : "unknown"; +    DBG_HELPER_ARGS(dbg, "action = %s, option = %s (%d)", action_str, +                    s->opt[option].name, option); + +  SANE_Word cap; +  SANE_Int myinfo = 0; + +    if (info) { +        *info = 0; +    } + +    if (s->scanning) { +        throw SaneException(SANE_STATUS_DEVICE_BUSY, +                            "don't call this function while scanning (option = %s (%d))", +                            s->opt[option].name, option); +    } +    if (option >= NUM_OPTIONS || option < 0) { +        throw SaneException("option %d >= NUM_OPTIONS || option < 0", option); +    } + +  cap = s->opt[option].cap; + +    if (!SANE_OPTION_IS_ACTIVE (cap)) { +        throw SaneException("option %d is inactive", option); +    } + +    switch (action) { +        case SANE_ACTION_GET_VALUE: +            get_option_value(s, option, val); +            break; + +        case SANE_ACTION_SET_VALUE: +            if (!SANE_OPTION_IS_SETTABLE (cap)) { +                throw SaneException("option %d is not settable", option); +            } + +            TIE(sanei_constrain_value(s->opt + option, val, &myinfo)); + +            set_option_value(s, option, val, &myinfo); +            break; + +        case SANE_ACTION_SET_AUTO: +            throw SaneException("SANE_ACTION_SET_AUTO unsupported since no option " +                                "has SANE_CAP_AUTOMATIC"); +        default: +            throw SaneException("unknown action %d for option %d", action, option); +    } + +  if (info) +    *info = myinfo; +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_control_option(SANE_Handle handle, SANE_Int option, +                                           SANE_Action action, void *val, SANE_Int * info) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_control_option_impl(handle, option, action, val, info); +    }); +} + +void sane_get_parameters_impl(SANE_Handle handle, SANE_Parameters* params) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +  /* don't recompute parameters once data reading is active, ie during scan */ +    if (!s->dev->read_active) { +        calc_parameters(s); +    } +  if (params) +    { +      *params = s->params; + +      /* in the case of a sheetfed scanner, when full height is specified +       * we override the computed line number with -1 to signal that we +       * don't know the real document height. +       * We don't do that doing buffering image for digital processing +       */ +        if (s->dev->model->is_sheetfed && !s->dev->buffer_image && +            s->pos_bottom_right_y == s->opt[OPT_BR_Y].constraint.range->max) +        { +	  params->lines = -1; +	} +    } +    debug_dump(DBG_proc, *params); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_get_parameters(SANE_Handle handle, SANE_Parameters* params) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_get_parameters_impl(handle, params); +    }); +} + +void sane_start_impl(SANE_Handle handle) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (s->pos_top_left_x >= s->pos_bottom_right_x) { +        throw SaneException("top left x >= bottom right x"); +    } +    if (s->pos_top_left_y >= s->pos_bottom_right_y) { +        throw SaneException("top left y >= bottom right y"); +    } + +  /* First make sure we have a current parameter set.  Some of the +     parameters will be overwritten below, but that's OK.  */ + +    calc_parameters(s); +    genesys_start_scan(s->dev, s->lamp_off); + +    s->scanning = true; + +  /* allocate intermediate buffer when doing dynamic lineart */ +    if (s->dev->settings.scan_mode == ScanColorMode::LINEART) { +        s->dev->binarize_buffer.clear(); +        s->dev->binarize_buffer.alloc(s->dev->settings.pixels); +        s->dev->local_buffer.clear(); +        s->dev->local_buffer.alloc(s->dev->binarize_buffer.size() * 8); +    } + +  /* if one of the software enhancement option is selected, +   * we do the scan internally, process picture then put it an internal +   * buffer. Since cropping may change scan parameters, we recompute them +   * at the end */ +  if (s->dev->buffer_image) +    { +        genesys_buffer_image(s); + +      /* check if we need to skip this page, sheetfed scanners +       * can go to next doc while flatbed ones can't */ +        if (s->swskip > 0 && IS_ACTIVE(OPT_SWSKIP)) { +            auto status = sanei_magic_isBlank(&s->params, +                                              s->dev->img_buffer.data(), +                                              SANE_UNFIX(s->swskip)); + +            if (status == SANE_STATUS_NO_DOCS && s->dev->model->is_sheetfed) { +                DBG(DBG_info, "%s: blank page, recurse\n", __func__); +                sane_start(handle); +                return; +            } + +            if (status != SANE_STATUS_GOOD) { +                throw SaneException(status); +            } +        } + +        if (s->swdeskew) { +            const auto& sensor = sanei_genesys_find_sensor(s->dev, s->dev->settings.xres, +                                                           s->dev->settings.get_channels(), +                                                           s->dev->settings.scan_method); +            catch_all_exceptions(__func__, [&](){ genesys_deskew(s, sensor); }); +        } + +        if (s->swdespeck) { +            catch_all_exceptions(__func__, [&](){ genesys_despeck(s); }); +        } + +        if(s->swcrop) { +            catch_all_exceptions(__func__, [&](){ genesys_crop(s); }); +        } + +        if(s->swderotate) { +            catch_all_exceptions(__func__, [&](){ genesys_derotate(s); }); +        } +    } +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_start(SANE_Handle handle) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_start_impl(handle); +    }); +} + +void sane_read_impl(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); +  Genesys_Device *dev; +  size_t local_len; + +    if (!s) { +        throw SaneException("handle is nullptr"); +    } + +  dev=s->dev; +    if (!dev) { +        throw SaneException("dev is nullptr"); +    } + +    if (!buf) { +        throw SaneException("buf is nullptr"); +    } + +    if (!len) { +        throw SaneException("len is nullptr"); +    } + +  *len = 0; + +    if (!s->scanning) { +        throw SaneException(SANE_STATUS_CANCELLED, +                            "scan was cancelled, is over or has not been initiated yet"); +    } + +  DBG(DBG_proc, "%s: start, %d maximum bytes required\n", __func__, max_len); +    DBG(DBG_io2, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__, +        dev->total_bytes_to_read, dev->total_bytes_read); + +  if(dev->total_bytes_read>=dev->total_bytes_to_read) +    { +      DBG(DBG_proc, "%s: nothing more to scan: EOF\n", __func__); + +      /* issue park command immediatly in case scanner can handle it +       * so we save time */ +        if (!dev->model->is_sheetfed && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) && +            !dev->parking) +        { +            dev->cmd_set->move_back_home(dev, false); +            dev->parking = true; +        } +        throw SaneException(SANE_STATUS_EOF); +    } + +  local_len = max_len; + +  /* in case of image processing, all data has been stored in +   * buffer_image. So read data from it if it exists, else from scanner */ +  if(!dev->buffer_image) +    { +      /* dynamic lineart is another kind of digital processing that needs +       * another layer of buffering on top of genesys_read_ordered_data */ +        if (dev->settings.scan_mode == ScanColorMode::LINEART) { +          /* if buffer is empty, fill it with genesys_read_ordered_data */ +          if(dev->binarize_buffer.avail() == 0) +            { +              /* store gray data */ +              local_len=dev->local_buffer.size(); +              dev->local_buffer.reset(); +                genesys_read_ordered_data(dev, dev->local_buffer.get_write_pos(local_len), +                                          &local_len); +              dev->local_buffer.produce(local_len); + +                dev->binarize_buffer.reset(); +                if (!is_testing_mode()) { +                    genesys_gray_lineart(dev, dev->local_buffer.get_read_pos(), +                                         dev->binarize_buffer.get_write_pos(local_len / 8), +                                         dev->settings.pixels, +                                         local_len / dev->settings.pixels, +                                         dev->settings.threshold); +                } +                dev->binarize_buffer.produce(local_len / 8); +            } + +          /* return data from lineart buffer if any, up to the available amount */ +          local_len = max_len; +          if (static_cast<std::size_t>(max_len) > dev->binarize_buffer.avail()) +            { +              local_len=dev->binarize_buffer.avail(); +            } +          if(local_len) +            { +              memcpy(buf, dev->binarize_buffer.get_read_pos(), local_len); +              dev->binarize_buffer.consume(local_len); +            } +        } +      else +        { +            // most usual case, direct read of data from scanner */ +            genesys_read_ordered_data(dev, buf, &local_len); +        } +    } +  else /* read data from buffer */ +    { +      if(dev->total_bytes_read+local_len>dev->total_bytes_to_read) +        { +          local_len=dev->total_bytes_to_read-dev->total_bytes_read; +        } +      memcpy(buf, dev->img_buffer.data() + dev->total_bytes_read, local_len); +      dev->total_bytes_read+=local_len; +    } + +  *len = local_len; +    if (local_len > static_cast<std::size_t>(max_len)) { +      fprintf (stderr, "[genesys] sane_read: returning incorrect length!!\n"); +    } +  DBG(DBG_proc, "%s: %d bytes returned\n", __func__, *len); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_read(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_read_impl(handle, buf, max_len, len); +    }); +} + +void sane_cancel_impl(SANE_Handle handle) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    s->scanning = false; +    s->dev->read_active = false; +  s->dev->img_buffer.clear(); + +  /* no need to end scan if we are parking the head */ +    if (!s->dev->parking) { +        s->dev->cmd_set->end_scan(s->dev, &s->dev->reg, true); +    } + +  /* park head if flatbed scanner */ +    if (!s->dev->model->is_sheetfed) { +        if (!s->dev->parking) { +            s->dev->cmd_set->move_back_home (s->dev, s->dev->model->flags & +                                                    GENESYS_FLAG_MUST_WAIT); + +          s->dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT); +        } +    } +  else +    {				/* in case of sheetfed scanners, we have to eject the document if still present */ +        s->dev->cmd_set->eject_document(s->dev); +    } + +  /* enable power saving mode unless we are parking .... */ +    if (!s->dev->parking) { +        s->dev->cmd_set->save_power(s->dev, true); +    } + +  return; +} + +SANE_GENESYS_API_LINKAGE +void sane_cancel(SANE_Handle handle) +{ +    catch_all_exceptions(__func__, [=]() { sane_cancel_impl(handle); }); +} + +void sane_set_io_mode_impl(SANE_Handle handle, SANE_Bool non_blocking) +{ +    DBG_HELPER_ARGS(dbg, "handle = %p, non_blocking = %s", handle, +                    non_blocking == SANE_TRUE ? "true" : "false"); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (!s->scanning) { +        throw SaneException("not scanning"); +    } +    if (non_blocking) { +        throw SaneException(SANE_STATUS_UNSUPPORTED); +    } +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_set_io_mode(SANE_Handle handle, SANE_Bool non_blocking) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_set_io_mode_impl(handle, non_blocking); +    }); +} + +void sane_get_select_fd_impl(SANE_Handle handle, SANE_Int* fd) +{ +    DBG_HELPER_ARGS(dbg, "handle = %p, fd = %p", handle, reinterpret_cast<void*>(fd)); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (!s->scanning) { +        throw SaneException("not scanning"); +    } +    throw SaneException(SANE_STATUS_UNSUPPORTED); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_get_select_fd(SANE_Handle handle, SANE_Int* fd) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_get_select_fd_impl(handle, fd); +    }); +} + +GenesysButtonName genesys_option_to_button(int option) +{ +    switch (option) { +    case OPT_SCAN_SW: return BUTTON_SCAN_SW; +    case OPT_FILE_SW: return BUTTON_FILE_SW; +    case OPT_EMAIL_SW: return BUTTON_EMAIL_SW; +    case OPT_COPY_SW: return BUTTON_COPY_SW; +    case OPT_PAGE_LOADED_SW: return BUTTON_PAGE_LOADED_SW; +    case OPT_OCR_SW: return BUTTON_OCR_SW; +    case OPT_POWER_SW: return BUTTON_POWER_SW; +    case OPT_EXTRA_SW: return BUTTON_EXTRA_SW; +    default: throw std::runtime_error("Unknown option to convert to button index"); +    } +} + +} // namespace genesys diff --git a/backend/genesys/genesys.h b/backend/genesys/genesys.h new file mode 100644 index 0000000..255bf76 --- /dev/null +++ b/backend/genesys/genesys.h @@ -0,0 +1,258 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2005-2013 Stephane Voltz <stef.dev@free.fr> +   Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com> +   Copyright (C) 2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> + +   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. +*/ + +#ifndef GENESYS_H +#define GENESYS_H + +#ifndef BACKEND_NAME +# define BACKEND_NAME genesys +#endif + +#include "low.h" +#include <queue> + +#ifndef PATH_MAX +# define PATH_MAX	1024 +#endif + +#if defined(_WIN32) || defined(HAVE_OS2_H) +# define PATH_SEP	'\\' +#else +# define PATH_SEP	'/' +#endif + + +#define ENABLE(OPTION)  s->opt[OPTION].cap &= ~SANE_CAP_INACTIVE +#define DISABLE(OPTION) s->opt[OPTION].cap |=  SANE_CAP_INACTIVE +#define IS_ACTIVE(OPTION) (((s->opt[OPTION].cap) & SANE_CAP_INACTIVE) == 0) + +#define GENESYS_CONFIG_FILE "genesys.conf" + +#ifndef SANE_I18N +#define SANE_I18N(text) text +#endif + +#define STR_FLATBED SANE_I18N("Flatbed") +#define STR_TRANSPARENCY_ADAPTER SANE_I18N("Transparency Adapter") +#define STR_TRANSPARENCY_ADAPTER_INFRARED SANE_I18N("Transparency Adapter Infrared") + +namespace genesys { + +/** List of SANE options + */ +enum Genesys_Option +{ +  OPT_NUM_OPTS = 0, + +  OPT_MODE_GROUP, +  OPT_MODE, +  OPT_SOURCE, +  OPT_PREVIEW, +  OPT_BIT_DEPTH, +  OPT_RESOLUTION, + +  OPT_GEOMETRY_GROUP, +  OPT_TL_X,			/* top-left x */ +  OPT_TL_Y,			/* top-left y */ +  OPT_BR_X,			/* bottom-right x */ +  OPT_BR_Y,			/* bottom-right y */ + +  /* advanced image enhancement options */ +  OPT_ENHANCEMENT_GROUP, +  OPT_CUSTOM_GAMMA,		/* toggle to enable custom gamma tables */ +  OPT_GAMMA_VECTOR, +  OPT_GAMMA_VECTOR_R, +  OPT_GAMMA_VECTOR_G, +  OPT_GAMMA_VECTOR_B, +  OPT_SWDESKEW, +  OPT_SWCROP, +  OPT_SWDESPECK, +  OPT_DESPECK, +  OPT_SWSKIP, +  OPT_SWDEROTATE, +  OPT_BRIGHTNESS, +  OPT_CONTRAST, + +  OPT_EXTRAS_GROUP, +  OPT_LAMP_OFF_TIME, +  OPT_LAMP_OFF, +  OPT_THRESHOLD, +  OPT_THRESHOLD_CURVE, +  OPT_DISABLE_INTERPOLATION, +  OPT_COLOR_FILTER, +  OPT_CALIBRATION_FILE, +  OPT_EXPIRATION_TIME, + +  OPT_SENSOR_GROUP, +  OPT_SCAN_SW, +  OPT_FILE_SW, +  OPT_EMAIL_SW, +  OPT_COPY_SW, +  OPT_PAGE_LOADED_SW, +  OPT_OCR_SW, +  OPT_POWER_SW, +  OPT_EXTRA_SW, +  OPT_NEED_CALIBRATION_SW, +  OPT_BUTTON_GROUP, +  OPT_CALIBRATE, +  OPT_CLEAR_CALIBRATION, +  OPT_FORCE_CALIBRATION, +  OPT_IGNORE_OFFSETS, + +  /* must come last: */ +  NUM_OPTIONS +}; + +enum GenesysButtonName : unsigned { +    BUTTON_SCAN_SW = 0, +    BUTTON_FILE_SW, +    BUTTON_EMAIL_SW, +    BUTTON_COPY_SW, +    BUTTON_PAGE_LOADED_SW, +    BUTTON_OCR_SW, +    BUTTON_POWER_SW, +    BUTTON_EXTRA_SW, +    NUM_BUTTONS +}; + +GenesysButtonName genesys_option_to_button(int option); + +class GenesysButton { +public: +    void write(bool value) +    { +        if (value == value_) { +            return; +        } +        values_to_read_.push(value); +        value_ = value; +    } + +    bool read() +    { +        if (values_to_read_.empty()) { +            return value_; +        } +        bool ret = values_to_read_.front(); +        values_to_read_.pop(); +        return ret; +    } + +private: +    bool value_ = false; +    std::queue<bool> values_to_read_; +}; + +/** Scanner object. Should have better be called Session than Scanner + */ +struct Genesys_Scanner +{ +    Genesys_Scanner() = default; +    ~Genesys_Scanner() = default; + +    // Next scanner in list +    struct Genesys_Scanner *next; + +    // Low-level device object +    Genesys_Device* dev = nullptr; + +    // SANE data +    // We are currently scanning +    bool scanning; +    // Option descriptors +    SANE_Option_Descriptor opt[NUM_OPTIONS]; + +    std::vector<SANE_Word> opt_resolution_values; +    SANE_Range opt_x_range = {}; +    SANE_Range opt_y_range = {}; +    std::vector<const char*> opt_source_values; + +    // Option values +    SANE_Word bit_depth = 0; +    SANE_Word resolution = 0; +    bool preview = false; +    SANE_Word threshold = 0; +    SANE_Word threshold_curve = 0; +    bool disable_interpolation = false; +    bool lamp_off = false; +    SANE_Word lamp_off_time = 0; +    bool swdeskew = false; +    bool swcrop = false; +    bool swdespeck = false; +    bool swderotate = false; +    SANE_Word swskip = 0; +    SANE_Word despeck = 0; +    SANE_Word contrast = 0; +    SANE_Word brightness = 0; +    SANE_Word expiration_time = 0; +    bool custom_gamma = false; + +    SANE_Word pos_top_left_y = 0; +    SANE_Word pos_top_left_x = 0; +    SANE_Word pos_bottom_right_y = 0; +    SANE_Word pos_bottom_right_x = 0; + +    std::string mode, color_filter; + +    // the value of the source option +    ScanMethod scan_method = ScanMethod::FLATBED; + +    std::string calibration_file; +    // Button states +    GenesysButton buttons[NUM_BUTTONS]; + +    // SANE Parameters +    SANE_Parameters params = {}; +    SANE_Int bpp_list[5] = {}; +}; + +void write_calibration(std::ostream& str, Genesys_Device::Calibration& cache); +bool read_calibration(std::istream& str, Genesys_Device::Calibration& cache, +                      const std::string& path); + +} // namespace genesys + +#endif /* not GENESYS_H */ diff --git a/backend/genesys/gl124.cpp b/backend/genesys/gl124.cpp new file mode 100644 index 0000000..054f1ef --- /dev/null +++ b/backend/genesys/gl124.cpp @@ -0,0 +1,2269 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2016 Stéphane Voltz <stef.dev@free.fr> + + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "gl124.h" +#include "gl124_registers.h" +#include "test_settings.h" + +#include <vector> + +namespace genesys { +namespace gl124 { + +/** @brief set all registers to default values . + * This function is called only once at the beginning and + * fills register startup values for registers reused across scans. + * Those that are rarely modified or not modified are written + * individually. + * @param dev device structure holding register set to initialize + */ +static void +gl124_init_registers (Genesys_Device * dev) +{ +    DBG_HELPER(dbg); + +    dev->reg.clear(); + +    // default to LiDE 110 +    dev->reg.init_reg(0x01, 0xa2); // + REG_0x01_SHDAREA +    dev->reg.init_reg(0x02, 0x90); +    dev->reg.init_reg(0x03, 0x50); +    dev->reg.init_reg(0x04, 0x03); +    dev->reg.init_reg(0x05, 0x00); + +    if(dev->model->sensor_id == SensorId::CIS_CANON_LIDE_120) { +    dev->reg.init_reg(0x06, 0x50); +    dev->reg.init_reg(0x07, 0x00); +    } else { +        dev->reg.init_reg(0x03, 0x50 & ~REG_0x03_AVEENB); +        dev->reg.init_reg(0x06, 0x50 | REG_0x06_GAIN4); +    } +    dev->reg.init_reg(0x09, 0x00); +    dev->reg.init_reg(0x0a, 0xc0); +    dev->reg.init_reg(0x0b, 0x2a); +    dev->reg.init_reg(0x0c, 0x12); +    dev->reg.init_reg(0x11, 0x00); +    dev->reg.init_reg(0x12, 0x00); +    dev->reg.init_reg(0x13, 0x0f); +    dev->reg.init_reg(0x14, 0x00); +    dev->reg.init_reg(0x15, 0x80); +    dev->reg.init_reg(0x16, 0x10); // SENSOR_DEF +    dev->reg.init_reg(0x17, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x18, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x19, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x1a, 0x30); // SENSOR_DEF +    dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x1c, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x1d, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x1e, 0x10); +    dev->reg.init_reg(0x1f, 0x00); +    dev->reg.init_reg(0x20, 0x15); // SENSOR_DEF +    dev->reg.init_reg(0x21, 0x00); +    if(dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) { +        dev->reg.init_reg(0x22, 0x02); +    } else { +        dev->reg.init_reg(0x22, 0x14); +    } +    dev->reg.init_reg(0x23, 0x00); +    dev->reg.init_reg(0x24, 0x00); +    dev->reg.init_reg(0x25, 0x00); +    dev->reg.init_reg(0x26, 0x0d); +    dev->reg.init_reg(0x27, 0x48); +    dev->reg.init_reg(0x28, 0x00); +    dev->reg.init_reg(0x29, 0x56); +    dev->reg.init_reg(0x2a, 0x5e); +    dev->reg.init_reg(0x2b, 0x02); +    dev->reg.init_reg(0x2c, 0x02); +    dev->reg.init_reg(0x2d, 0x58); +    dev->reg.init_reg(0x3b, 0x00); +    dev->reg.init_reg(0x3c, 0x00); +    dev->reg.init_reg(0x3d, 0x00); +    dev->reg.init_reg(0x3e, 0x00); +    dev->reg.init_reg(0x3f, 0x02); +    dev->reg.init_reg(0x40, 0x00); +    dev->reg.init_reg(0x41, 0x00); +    dev->reg.init_reg(0x42, 0x00); +    dev->reg.init_reg(0x43, 0x00); +    dev->reg.init_reg(0x44, 0x00); +    dev->reg.init_reg(0x45, 0x00); +    dev->reg.init_reg(0x46, 0x00); +    dev->reg.init_reg(0x47, 0x00); +    dev->reg.init_reg(0x48, 0x00); +    dev->reg.init_reg(0x49, 0x00); +    dev->reg.init_reg(0x4f, 0x00); +    dev->reg.init_reg(0x52, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x53, 0x02); // SENSOR_DEF +    dev->reg.init_reg(0x54, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x55, 0x06); // SENSOR_DEF +    dev->reg.init_reg(0x56, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x57, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x58, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x59, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x5a, 0x1a); // SENSOR_DEF +    dev->reg.init_reg(0x5b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x5c, 0xc0); // SENSOR_DEF +    dev->reg.init_reg(0x5f, 0x00); +    dev->reg.init_reg(0x60, 0x02); +    dev->reg.init_reg(0x61, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x62, 0x00); +    dev->reg.init_reg(0x63, 0x00); +    dev->reg.init_reg(0x64, 0x00); +    dev->reg.init_reg(0x65, 0x00); +    dev->reg.init_reg(0x66, 0x00); +    dev->reg.init_reg(0x67, 0x00); +    dev->reg.init_reg(0x68, 0x00); +    dev->reg.init_reg(0x69, 0x00); +    dev->reg.init_reg(0x6a, 0x00); +    dev->reg.init_reg(0x6b, 0x00); +    dev->reg.init_reg(0x6c, 0x00); +    dev->reg.init_reg(0x6e, 0x00); +    dev->reg.init_reg(0x6f, 0x00); + +    if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) { +        dev->reg.init_reg(0x6d, 0xd0); +        dev->reg.init_reg(0x71, 0x08); +    } else { +        dev->reg.init_reg(0x6d, 0x00); +        dev->reg.init_reg(0x71, 0x1f); +    } +    dev->reg.init_reg(0x70, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x71, 0x08); // SENSOR_DEF +    dev->reg.init_reg(0x72, 0x08); // SENSOR_DEF +    dev->reg.init_reg(0x73, 0x0a); // SENSOR_DEF + +    // CKxMAP +    dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF +    dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF +    dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF + +    dev->reg.init_reg(0x7d, 0x00); +    dev->reg.init_reg(0x7e, 0x08); +    dev->reg.init_reg(0x7f, 0x58); + +    if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) { +        dev->reg.init_reg(0x80, 0x00); +        dev->reg.init_reg(0x81, 0x14); +    } else { +        dev->reg.init_reg(0x80, 0x00); +        dev->reg.init_reg(0x81, 0x10); +    } + +    // STRPIXEL +    dev->reg.init_reg(0x82, 0x00); +    dev->reg.init_reg(0x83, 0x00); +    dev->reg.init_reg(0x84, 0x00); + +    // ENDPIXEL +    dev->reg.init_reg(0x85, 0x00); +    dev->reg.init_reg(0x86, 0x00); +    dev->reg.init_reg(0x87, 0x00); + +    dev->reg.init_reg(0x88, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x89, 0x65); // SENSOR_DEF +    dev->reg.init_reg(0x8a, 0x00); +    dev->reg.init_reg(0x8b, 0x00); +    dev->reg.init_reg(0x8c, 0x00); +    dev->reg.init_reg(0x8d, 0x00); +    dev->reg.init_reg(0x8e, 0x00); +    dev->reg.init_reg(0x8f, 0x00); +    dev->reg.init_reg(0x90, 0x00); +    dev->reg.init_reg(0x91, 0x00); +    dev->reg.init_reg(0x92, 0x00); +    dev->reg.init_reg(0x93, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x94, 0x14); // SENSOR_DEF +    dev->reg.init_reg(0x95, 0x30); // SENSOR_DEF +    dev->reg.init_reg(0x96, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x97, 0x90); // SENSOR_DEF +    dev->reg.init_reg(0x98, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x99, 0x1f); +    dev->reg.init_reg(0x9a, 0x00); +    dev->reg.init_reg(0x9b, 0x80); +    dev->reg.init_reg(0x9c, 0x80); +    dev->reg.init_reg(0x9d, 0x3f); +    dev->reg.init_reg(0x9e, 0x00); +    dev->reg.init_reg(0x9f, 0x00); +    dev->reg.init_reg(0xa0, 0x20); +    dev->reg.init_reg(0xa1, 0x30); +    dev->reg.init_reg(0xa2, 0x00); +    dev->reg.init_reg(0xa3, 0x20); +    dev->reg.init_reg(0xa4, 0x01); +    dev->reg.init_reg(0xa5, 0x00); +    dev->reg.init_reg(0xa6, 0x00); +    dev->reg.init_reg(0xa7, 0x08); +    dev->reg.init_reg(0xa8, 0x00); +    dev->reg.init_reg(0xa9, 0x08); +    dev->reg.init_reg(0xaa, 0x01); +    dev->reg.init_reg(0xab, 0x00); +    dev->reg.init_reg(0xac, 0x00); +    dev->reg.init_reg(0xad, 0x40); +    dev->reg.init_reg(0xae, 0x01); +    dev->reg.init_reg(0xaf, 0x00); +    dev->reg.init_reg(0xb0, 0x00); +    dev->reg.init_reg(0xb1, 0x40); +    dev->reg.init_reg(0xb2, 0x00); +    dev->reg.init_reg(0xb3, 0x09); +    dev->reg.init_reg(0xb4, 0x5b); +    dev->reg.init_reg(0xb5, 0x00); +    dev->reg.init_reg(0xb6, 0x10); +    dev->reg.init_reg(0xb7, 0x3f); +    dev->reg.init_reg(0xb8, 0x00); +    dev->reg.init_reg(0xbb, 0x00); +    dev->reg.init_reg(0xbc, 0xff); +    dev->reg.init_reg(0xbd, 0x00); +    dev->reg.init_reg(0xbe, 0x07); +    dev->reg.init_reg(0xc3, 0x00); +    dev->reg.init_reg(0xc4, 0x00); + +    /* gamma +    dev->reg.init_reg(0xc5, 0x00); +    dev->reg.init_reg(0xc6, 0x00); +    dev->reg.init_reg(0xc7, 0x00); +    dev->reg.init_reg(0xc8, 0x00); +    dev->reg.init_reg(0xc9, 0x00); +    dev->reg.init_reg(0xca, 0x00); +    dev->reg.init_reg(0xcb, 0x00); +    dev->reg.init_reg(0xcc, 0x00); +    dev->reg.init_reg(0xcd, 0x00); +    dev->reg.init_reg(0xce, 0x00); +     */ + +    if (dev->model->sensor_id == SensorId::CIS_CANON_LIDE_120) { +        dev->reg.init_reg(0xc5, 0x20); +        dev->reg.init_reg(0xc6, 0xeb); +        dev->reg.init_reg(0xc7, 0x20); +        dev->reg.init_reg(0xc8, 0xeb); +        dev->reg.init_reg(0xc9, 0x20); +        dev->reg.init_reg(0xca, 0xeb); +    } + +    // memory layout +    /* +    dev->reg.init_reg(0xd0, 0x0a); +    dev->reg.init_reg(0xd1, 0x1f); +    dev->reg.init_reg(0xd2, 0x34); +    */ +    dev->reg.init_reg(0xd3, 0x00); +    dev->reg.init_reg(0xd4, 0x00); +    dev->reg.init_reg(0xd5, 0x00); +    dev->reg.init_reg(0xd6, 0x00); +    dev->reg.init_reg(0xd7, 0x00); +    dev->reg.init_reg(0xd8, 0x00); +    dev->reg.init_reg(0xd9, 0x00); + +    // memory layout +    /* +    dev->reg.init_reg(0xe0, 0x00); +    dev->reg.init_reg(0xe1, 0x48); +    dev->reg.init_reg(0xe2, 0x15); +    dev->reg.init_reg(0xe3, 0x90); +    dev->reg.init_reg(0xe4, 0x15); +    dev->reg.init_reg(0xe5, 0x91); +    dev->reg.init_reg(0xe6, 0x2a); +    dev->reg.init_reg(0xe7, 0xd9); +    dev->reg.init_reg(0xe8, 0x2a); +    dev->reg.init_reg(0xe9, 0xad); +    dev->reg.init_reg(0xea, 0x40); +    dev->reg.init_reg(0xeb, 0x22); +    dev->reg.init_reg(0xec, 0x40); +    dev->reg.init_reg(0xed, 0x23); +    dev->reg.init_reg(0xee, 0x55); +    dev->reg.init_reg(0xef, 0x6b); +    dev->reg.init_reg(0xf0, 0x55); +    dev->reg.init_reg(0xf1, 0x6c); +    dev->reg.init_reg(0xf2, 0x6a); +    dev->reg.init_reg(0xf3, 0xb4); +    dev->reg.init_reg(0xf4, 0x6a); +    dev->reg.init_reg(0xf5, 0xb5); +    dev->reg.init_reg(0xf6, 0x7f); +    dev->reg.init_reg(0xf7, 0xfd); +    */ + +    dev->reg.init_reg(0xf8, 0x01);   // other value is 0x05 +    dev->reg.init_reg(0xf9, 0x00); +    dev->reg.init_reg(0xfa, 0x00); +    dev->reg.init_reg(0xfb, 0x00); +    dev->reg.init_reg(0xfc, 0x00); +    dev->reg.init_reg(0xff, 0x00); + +    // fine tune upon device description +    const auto& sensor = sanei_genesys_find_sensor_any(dev); +    sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res); + +  dev->calib_reg = dev->reg; +} + +/**@brief send slope table for motor movement + * Send slope_table in machine byte order + * @param dev device to send slope table + * @param table_nr index of the slope table in ASIC memory + * Must be in the [0-4] range. + * @param slope_table pointer to 16 bit values array of the slope table + * @param steps number of elemnts in the slope table + */ +static void gl124_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps) +{ +    DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps); +  int i; +  char msg[10000]; + +  /* sanity check */ +  if(table_nr<0 || table_nr>4) +    { +        throw SaneException("invalid table number"); +    } + +  std::vector<uint8_t> table(steps * 2); +  for (i = 0; i < steps; i++) +    { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +    } + +  if (DBG_LEVEL >= DBG_io) +    { +        std::sprintf(msg, "write slope %d (%d)=", table_nr, steps); +        for (i = 0; i < steps; i++) { +            std::sprintf(msg + std::strlen(msg), ",%d", slope_table[i]); +        } +      DBG (DBG_io, "%s: %s\n", __func__, msg); +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } +    // slope table addresses are fixed +    dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, steps * 2, table.data()); +} + +/** @brief * Set register values of 'special' ti type frontend + * Registers value are taken from the frontend register data + * set. + * @param dev device owning the AFE + * @param set flag AFE_INIT to specify the AFE must be reset before writing data + * */ +static void gl124_set_ti_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; +    } + +    // start writing to DAC +    dev->interface->write_fe_register(0x00, 0x80); + +  /* write values to analog frontend */ +  for (uint16_t addr = 0x01; addr < 0x04; addr++) +    { +        dev->interface->write_fe_register(addr, dev->frontend.regs.get_value(addr)); +    } + +    dev->interface->write_fe_register(0x04, 0x00); + +  /* these are not really sign for this AFE */ +  for (i = 0; i < 3; i++) +    { +        dev->interface->write_fe_register(0x05 + i, dev->frontend.regs.get_value(0x24 + i)); +    } + +    if (dev->model->adc_id == AdcId::CANON_LIDE_120) { +        dev->interface->write_fe_register(0x00, 0x01); +    } +  else +    { +        dev->interface->write_fe_register(0x00, 0x11); +    } +} + + +// Set values of analog frontend +void CommandSetGl124::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" : +                               set == AFE_SET ? "set" : +                               set == AFE_POWER_SAVE ? "powersave" : "huh?"); +    (void) sensor; +  uint8_t val; + +  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; +    } + +    val = dev->interface->read_register(REG_0x0A); + +  /* route to correct analog FE */ +    switch ((val & REG_0x0A_SIFSEL) >> REG_0x0AS_SIFSEL) { +    case 3: +            gl124_set_ti_fe(dev, set); +      break; +    case 0: +    case 1: +    case 2: +    default: +            throw SaneException("unsupported analog FE 0x%02x", val); +    } +} + +static void gl124_init_motor_regs_scan(Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       Genesys_Register_Set* reg, +                                       const Motor_Profile& motor_profile, +                                       unsigned int scan_exposure_time, +                                       unsigned scan_yres, +                                       unsigned int scan_lines, +                                       unsigned int scan_dummy, +                                       unsigned int feed_steps, +                                       ScanColorMode scan_mode, +                                       MotorFlag flags) +{ +    DBG_HELPER(dbg); +  int use_fast_fed; +  unsigned int lincnt, fast_dpi; +  unsigned int feedl,dist; +  uint32_t z1, z2; +    unsigned yres; +    unsigned min_speed; +  unsigned int linesel; + +    DBG(DBG_info, "%s : scan_exposure_time=%d, scan_yres=%d, step_type=%d, scan_lines=%d, " +      "scan_dummy=%d, feed_steps=%d, scan_mode=%d, flags=%x\n", __func__, scan_exposure_time, +        scan_yres, static_cast<unsigned>(motor_profile.step_type), scan_lines, scan_dummy, +        feed_steps, static_cast<unsigned>(scan_mode), +        static_cast<unsigned>(flags)); + +  /* we never use fast fed since we do manual feed for the scans */ +  use_fast_fed=0; + +  /* enforce motor minimal scan speed +   * @TODO extend motor struct for this value */ +  if (scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +    { +      min_speed = 900; +    } +  else +    { +      switch(dev->model->motor_id) +        { +          case MotorId::CANON_LIDE_110: +	    min_speed = 600; +            break; +          case MotorId::CANON_LIDE_120: +            min_speed = 900; +            break; +          default: +            min_speed = 900; +            break; +        } +    } + +  /* compute min_speed and linesel */ +  if(scan_yres<min_speed) +    { +      yres=min_speed; +        linesel = yres / scan_yres - 1; +      /* limit case, we need a linesel > 0 */ +      if(linesel==0) +        { +          linesel=1; +          yres=scan_yres*2; +        } +    } +  else +    { +      yres=scan_yres; +      linesel=0; +    } + +    DBG(DBG_io2, "%s: final yres=%d, linesel=%d\n", __func__, yres, linesel); + +  lincnt=scan_lines*(linesel+1); +    reg->set24(REG_LINCNT, lincnt); +  DBG (DBG_io, "%s: lincnt=%d\n", __func__, lincnt); + +  /* compute register 02 value */ +    uint8_t r02 = REG_0x02_NOTHOME; + +    if (use_fast_fed) { +        r02 |= REG_0x02_FASTFED; +    } else { +        r02 &= ~REG_0x02_FASTFED; +    } + +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        r02 |= REG_0x02_AGOHOME; +    } + +    if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) || (yres >= sensor.optical_res)) +    { +        r02 |= REG_0x02_ACDCDIS; +    } +    if (has_flag(flags, MotorFlag::REVERSE)) { +        r02 |= REG_0x02_MTRREV; +    } + +    reg->set8(REG_0x02, r02); +    sanei_genesys_set_motor_power(*reg, true); + +    reg->set16(REG_SCANFED, 4); + +  /* scan and backtracking slope table */ +    auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, yres, scan_exposure_time, +                                                dev->motor.base_ydpi, 1, +                                                motor_profile); +    gl124_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count); +    gl124_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count); + +    reg->set16(REG_STEPNO, scan_table.steps_count); + +  /* fast table */ +  fast_dpi=yres; + +  /* +  if (scan_mode != ScanColorMode::COLOR_SINGLE_PASS) +    { +      fast_dpi*=3; +    } +    */ +    auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_dpi, +                                                scan_exposure_time, dev->motor.base_ydpi, +                                                1, motor_profile); +    gl124_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count); +    gl124_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count); + +    reg->set16(REG_FASTNO, fast_table.steps_count); +    reg->set16(REG_FSHDEC, fast_table.steps_count); +    reg->set16(REG_FMOVNO, fast_table.steps_count); + +  /* substract acceleration distance from feedl */ +  feedl=feed_steps; +    feedl <<= static_cast<unsigned>(motor_profile.step_type); + +    dist = scan_table.steps_count; +    if (has_flag(flags, MotorFlag::FEED)) { +        dist *= 2; +    } +    if (use_fast_fed) { +        dist += fast_table.steps_count * 2; +    } +  DBG (DBG_io2, "%s: acceleration distance=%d\n", __func__, dist); + +  /* get sure we don't use insane value */ +    if (dist < feedl) { +        feedl -= dist; +    } else { +        feedl = 0; +    } + +    reg->set24(REG_FEEDL, feedl); +  DBG (DBG_io, "%s: feedl=%d\n", __func__, feedl); + +  /* doesn't seem to matter that much */ +    sanei_genesys_calculate_zmod(use_fast_fed, +				  scan_exposure_time, +                                 scan_table.table, +                                 scan_table.steps_count, +				  feedl, +                                 scan_table.steps_count, +                                  &z1, +                                  &z2); + +    reg->set24(REG_Z1MOD, z1); +  DBG(DBG_info, "%s: z1 = %d\n", __func__, z1); + +    reg->set24(REG_Z2MOD, z2); +  DBG(DBG_info, "%s: z2 = %d\n", __func__, z2); + +  /* LINESEL */ +    reg->set8_mask(REG_0x1D, linesel, REG_0x1D_LINESEL); +    reg->set8(REG_0xA0, (static_cast<unsigned>(motor_profile.step_type) << REG_0xA0S_STEPSEL) | +                        (static_cast<unsigned>(motor_profile.step_type) << REG_0xA0S_FSTPSEL)); + +    reg->set16(REG_FMOVDEC, fast_table.steps_count); +} + + +/** @brief copy sensor specific settings + * Set up register set for the given sensor resolution. Values are from the device table + * in genesys_devices.c for registers: + *       [0x16 ... 0x1d] + *       [0x52 ... 0x5e] + * Other come from the specific device sensor table in genesys_gl124.h: + *      0x18, 0x20, 0x61, 0x98 and + * @param dev device to set up + * @param regs register set to modify + * @param dpi resolution of the sensor during scan + * @param ccd_size_divisor flag for half ccd mode + * */ +static void gl124_setup_sensor(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set* regs) +{ +    DBG_HELPER(dbg); + +    for (const auto& reg : sensor.custom_regs) { +        regs->set8(reg.address, reg.value); +    } + +    regs->set24(REG_EXPR, sensor.exposure.red); +    regs->set24(REG_EXPG, sensor.exposure.green); +    regs->set24(REG_EXPB, sensor.exposure.blue); + +    dev->segment_order = sensor.segment_order; +} + +/** @brief setup optical related registers + * start and pixels are expressed in optical sensor resolution coordinate + * space. + * @param dev scanner device to use + * @param reg registers to set up + * @param exposure_time exposure time to use + * @param used_res scanning resolution used, may differ from + *        scan's one + * @param start logical start pixel coordinate + * @param pixels logical number of pixels to use + * @param channels number of color channels (currently 1 or 3) + * @param depth bit depth of the scan (1, 8 or 16) + * @param ccd_size_divisor whether sensor's timings are such that x coordinates must be halved + * @param color_filter color channel to use as gray data + * @param flags optical flags (@see ) + */ +static void gl124_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set* reg, unsigned int exposure_time, +                                         const ScanSession& session) +{ +    DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time); +    unsigned int dpihw; +  GenesysRegister *r; +  uint32_t expmax; + +    // resolution is divided according to ccd_pixels_per_system_pixel +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); +    DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel); + +    // to manage high resolution device while keeping good low resolution scanning speed, we +    // make hardware dpi vary +    dpihw = sensor.get_register_hwdpi(session.output_resolution * ccd_pixels_per_system_pixel); +    DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw); + +    gl124_setup_sensor(dev, sensor, reg); + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); + +  /* enable shading */ +    regs_set_optical_off(dev->model->asic_type, *reg); +    r = sanei_genesys_get_address (reg, REG_0x01); +    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) || +        (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) +    { +        r->value &= ~REG_0x01_DVDSET; +    } else { +        r->value |= REG_0x01_DVDSET; +    } + +    r = sanei_genesys_get_address(reg, REG_0x03); +    if ((dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) && (session.params.xres>=600)) { +        r->value &= ~REG_0x03_AVEENB; +      DBG (DBG_io, "%s: disabling AVEENB\n", __func__); +    } +  else +    { +        r->value |= ~REG_0x03_AVEENB; +      DBG (DBG_io, "%s: enabling AVEENB\n", __func__); +    } + +    sanei_genesys_set_lamp_power(dev, sensor, *reg, +                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP)); + +    // BW threshold +    dev->interface->write_register(REG_0x114, dev->settings.threshold); +    dev->interface->write_register(REG_0x115, dev->settings.threshold); + +  /* monochrome / color scan */ +    r = sanei_genesys_get_address (reg, REG_0x04); +    switch (session.params.depth) { +    case 8: +            r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +            break; +    case 16: +            r->value &= ~REG_0x04_LINEART; +            r->value |= REG_0x04_BITSET; +            break; +    } + +    r->value &= ~REG_0x04_FILTER; +  if (session.params.channels == 1) +    { +      switch (session.params.color_filter) +	{ +            case ColorFilter::RED: +                r->value |= 0x10; +                break; +            case ColorFilter::BLUE: +                r->value |= 0x30; +                break; +            case ColorFilter::GREEN: +                r->value |= 0x20; +                break; +            default: +                break; // should not happen +	} +    } + +    sanei_genesys_set_dpihw(*reg, sensor, dpihw); + +    if (should_enable_gamma(session, sensor)) { +        reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB; +    } else { +        reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB; +    } + +    unsigned dpiset_reg = session.output_resolution * ccd_pixels_per_system_pixel * +            session.ccd_size_divisor; +    if (sensor.dpiset_override != 0) { +        dpiset_reg = sensor.dpiset_override; +    } + +    reg->set16(REG_DPISET, dpiset_reg); +    DBG (DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset_reg); + +    r = sanei_genesys_get_address(reg, REG_0x06); +    r->value |= REG_0x06_GAIN4; + +  /* CIS scanners can do true gray by setting LEDADD */ +  /* we set up LEDADD only when asked */ +    if (dev->model->is_cis) { +        r = sanei_genesys_get_address (reg, REG_0x60); +        r->value &= ~REG_0x60_LEDADD; +        if (session.enable_ledadd) { +            r->value |= REG_0x60_LEDADD; +            expmax = reg->get24(REG_EXPR); +            expmax = std::max(expmax, reg->get24(REG_EXPG)); +            expmax = std::max(expmax, reg->get24(REG_EXPB)); + +            dev->reg.set24(REG_EXPR, expmax); +            dev->reg.set24(REG_EXPG, expmax); +            dev->reg.set24(REG_EXPB, expmax); +        } +      /* RGB weighting, REG_TRUER,G and B are to be set  */ +        r = sanei_genesys_get_address (reg, 0x01); +        r->value &= ~REG_0x01_TRUEGRAY; +        if (session.enable_ledadd) { +            r->value |= REG_0x01_TRUEGRAY; +            dev->interface->write_register(REG_TRUER, 0x80); +            dev->interface->write_register(REG_TRUEG, 0x80); +            dev->interface->write_register(REG_TRUEB, 0x80); +        } +    } + +    reg->set24(REG_STRPIXEL, session.pixel_startx); +    reg->set24(REG_ENDPIXEL, session.pixel_endx); + +  dev->line_count = 0; + +    build_image_pipeline(dev, session); + +    // MAXWD is expressed in 2 words unit + +    // BUG: we shouldn't multiply by channels here +    reg->set24(REG_MAXWD, session.output_line_bytes_raw / session.ccd_size_divisor * session.params.channels); + +    reg->set24(REG_LPERIOD, exposure_time); +  DBG (DBG_io2, "%s: exposure_time used=%d\n", __func__, exposure_time); + +    reg->set16(REG_DUMMY, sensor.dummy_pixel); +} + +void CommandSetGl124::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* reg, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +  int move; +  int exposure_time; + +  int dummy = 0; +  int slope_dpi = 0; + +    /* cis color scan is effectively a gray scan with 3 gray lines per color line and a FILTER of 0 */ +    if (dev->model->is_cis) { +        slope_dpi = session.params.yres * session.params.channels; +    } else { +        slope_dpi = session.params.yres; +    } + +    if (has_flag(session.params.flags, ScanFlag::FEEDING)) { +        exposure_time = 2304; +    } else { +        exposure_time = sensor.exposure_lperiod; +    } +    const auto& motor_profile = sanei_genesys_get_motor_profile(*gl124_motor_profiles, +                                                                dev->model->motor_id, +                                                                exposure_time); + +  DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time); +  DBG(DBG_info, "%s : scan_step_type=%d\n", __func__, static_cast<unsigned>(motor_profile.step_type)); + +  /* we enable true gray for cis scanners only, and just when doing +   * scan since color calibration is OK for this mode +   */ + +    // now _LOGICAL_ optical values used are known, setup registers +    gl124_init_optical_regs_scan(dev, sensor, reg, exposure_time, session); + +  /* add tl_y to base movement */ +    move = session.params.starty; +  DBG(DBG_info, "%s: move=%d steps\n", __func__, move); + +    MotorFlag mflags = MotorFlag::NONE; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) { +        mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE; +    } +    if (has_flag(session.params.flags, ScanFlag::FEEDING)) { +        mflags |= MotorFlag::FEED; +    } +    if (has_flag(session.params.flags, ScanFlag::REVERSE)) { +        mflags |= MotorFlag::REVERSE; +    } +    gl124_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure_time, slope_dpi, +                               dev->model->is_cis ? session.output_line_count * session.params.channels : +                                                    session.output_line_count, +                               dummy, move, session.params.scan_mode, mflags); + +  /*** prepares data reordering ***/ + +    dev->read_buffer.clear(); +    dev->read_buffer.alloc(session.buffer_size_read); + +    dev->read_active = true; + +    dev->session = session; + +    dev->total_bytes_read = 0; +    dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines; + +    DBG(DBG_info, "%s: total bytes to send to frontend = %zu\n", __func__, +        dev->total_bytes_to_read); +} + +ScanSession CommandSetGl124::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +  int start; + +    DBG(DBG_info, "%s ", __func__); +    debug_dump(DBG_info, settings); + +  /* start */ +    start = static_cast<int>(dev->model->x_offset); +    start += static_cast<int>(settings.tl_x); +    start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = start; +    session.params.starty = 0; // not used +    session.params.pixels = settings.pixels; +    session.params.requested_pixels = settings.requested_pixels; +    session.params.lines = settings.lines; +    session.params.depth = settings.depth; +    session.params.channels = settings.get_channels(); +    session.params.scan_method = settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; + +    compute_session(dev, session, sensor); + +    return session; +} + +/** + * for fast power saving methods only, like disabling certain amplifiers + * @param dev device to use + * @param enable true to set inot powersaving + * */ +void CommandSetGl124::save_power(Genesys_Device* dev, bool enable) const +{ +    (void) dev; +    DBG_HELPER_ARGS(dbg, "enable = %d", enable); +} + +void CommandSetGl124::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    DBG_HELPER_ARGS(dbg,  "delay = %d",  delay); +  GenesysRegister *r; + +    r = sanei_genesys_get_address(&dev->reg, REG_0x03); +  r->value &= ~0xf0; +  if(delay<15) +    { +      r->value |= delay; +    } +  else +    { +      r->value |= 0x0f; +    } +} + +/** @brief setup GPIOs for scan + * Setup GPIO values to drive motor (or light) needed for the + * target resolution + * @param *dev device to set up + * @param resolution dpi of the target scan + */ +void gl124_setup_scan_gpio(Genesys_Device* dev, int resolution) +{ +    DBG_HELPER(dbg); + +    uint8_t val = dev->interface->read_register(REG_0x32); + +  /* LiDE 110, 210 and 220 cases */ +    if(dev->model->gpio_id != GpioId::CANON_LIDE_120) { +      if(resolution>=dev->motor.base_ydpi/2) +	{ +	  val &= 0xf7; +	} +      else if(resolution>=dev->motor.base_ydpi/4) +	{ +	  val &= 0xef; +	} +      else +	{ +	  val |= 0x10; +	} +    } +  /* 120 : <=300 => 0x53 */ +  else +    { /* base_ydpi is 4800 */ +      if(resolution<=300) +	{ +	  val &= 0xf7; +	} +      else if(resolution<=600) +	{ +	  val |= 0x08; +	} +      else if(resolution<=1200) +	{ +	  val &= 0xef; +	  val |= 0x08; +	} +      else +	{ +	  val &= 0xf7; +	} +    } +  val |= 0x02; +    dev->interface->write_register(REG_0x32, val); +} + +// Send the low-level scan command +// todo: is this that useful ? +void CommandSetGl124::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set* reg, bool start_motor) const +{ +    DBG_HELPER(dbg); +    (void) sensor; +    (void) reg; + +    // set up GPIO for scan +    gl124_setup_scan_gpio(dev,dev->settings.yres); + +    // clear scan and feed count +    dev->interface->write_register(REG_0x0D, REG_0x0D_CLRLNCNT | REG_0x0D_CLRMCNT); + +    // enable scan and motor +    uint8_t val = dev->interface->read_register(REG_0x01); +    val |= REG_0x01_SCAN; +    dev->interface->write_register(REG_0x01, val); + +    scanner_start_action(*dev, start_motor); + +    dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +} + + +// Send the stop scan command +void CommandSetGl124::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg, +                               bool check_stop) const +{ +    (void) reg; +    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop); + +    if (!dev->model->is_sheetfed) { +        scanner_stop_action(*dev); +    } +} + + +/** Park head + * Moves the slider to the home (top) position slowly + * @param dev device to park + * @param wait_until_home true to make the function waiting for head + * to be home before returning, if fals returne immediately + */ +void CommandSetGl124::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    scanner_move_back_home(*dev, wait_until_home); +} + +// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi +// from very top of scanner +void CommandSetGl124::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  int size; +  Genesys_Register_Set local_reg = dev->reg; + +  int pixels = 600; +  int dpi = 300; + +  /* sets for a 200 lines * 600 pixels */ +  /* normal scan with no shading */ + +    // FIXME: the current approach of doing search only for one resolution does not work on scanners +    // whith employ different sensors with potentially different settings. +    const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, ScanMethod::FLATBED); + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0;        /*we should give a small offset here~60 steps */ +    session.params.pixels = 600; +    session.params.lines = dev->model->search_lines; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::GREEN; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::IGNORE_LINE_DISTANCE | +                           ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    // send to scanner +    dev->interface->write_registers(local_reg); + +  size = pixels * dev->model->search_lines; + +  std::vector<uint8_t> data(size); + +    begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_start_position"); +        end_scan(dev, &local_reg, true); +        dev->reg = local_reg; +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl124_search_position.pnm", data.data(), 8, 1, pixels, +                                     dev->model->search_lines); +    } + +    end_scan(dev, &local_reg, true); + +  /* update regs to copy ASIC internal state */ +  dev->reg = local_reg; + +    for (auto& sensor_update : +             sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method)) +    { +        sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, dpi, pixels, +                                             dev->model->search_lines); +    } +} + +// sets up register for coarse gain calibration +// todo: check it for scanners using it +void CommandSetGl124::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel(); +    session.params.lines = 20; +    session.params.depth = 16; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::FEEDING | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +  DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__, +      sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres); + +    dev->interface->write_registers(regs); +} + + +// init registers for shading calibration shading calibration is done at dpihw +void CommandSetGl124::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int move, resolution, dpihw, factor; + +  /* initial calibration reg values */ +  regs = dev->reg; + +  dev->calib_channels = 3; +  dev->calib_lines = dev->model->shading_lines; +    dpihw = sensor.get_register_hwdpi(dev->settings.xres); +  if(dpihw>=2400) +    { +      dev->calib_lines *= 2; +    } +  resolution=dpihw; + +    unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres); + +    resolution /= ccd_size_divisor; +    dev->calib_lines /= ccd_size_divisor; // reducing just because we reduced the resolution + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, +                                                         dev->calib_channels, +                                                         dev->settings.scan_method); +  dev->calib_resolution = resolution; +  dev->calib_total_bytes_to_read = 0; +    factor = calib_sensor.optical_res / resolution; +    dev->calib_pixels = calib_sensor.sensor_pixels / factor; + +  /* distance to move to reach white target at high resolution */ +  move=0; +    if (dev->settings.yres >= 1200) { +        move = static_cast<int>(dev->model->y_offset_calib_white); +        move = static_cast<int>((move * (dev->motor.base_ydpi/4)) / MM_PER_INCH); +    } +  DBG (DBG_io, "%s: move=%d steps\n", __func__, move); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = move; +    session.params.pixels = dev->calib_pixels; +    session.params.lines = dev->calib_lines; +    session.params.depth = 16; +    session.params.channels = dev->calib_channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::DISABLE_BUFFER_FULL_MOVE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    try { +        init_regs_for_scan_session(dev, calib_sensor, ®s, session); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); +        throw; +    } +    sanei_genesys_set_motor_power(regs, false); + +    dev->interface->write_registers(regs); +} + +void CommandSetGl124::wait_for_motor_stop(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); + +    auto status = scanner_read_status(*dev); +    uint8_t val40 = dev->interface->read_register(REG_0x100); + +    if (!status.is_motor_enabled && (val40 & REG_0x100_MOTMFLG) == 0) { +        return; +    } + +    do { +        dev->interface->sleep_ms(10); +        status = scanner_read_status(*dev); +        val40 = dev->interface->read_register(REG_0x100); +    } while (status.is_motor_enabled ||(val40 & REG_0x100_MOTMFLG)); +    dev->interface->sleep_ms(50); +} + +/** @brief set up registers for the actual scan + */ +void CommandSetGl124::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  float move; +  int move_dpi; +  float start; + +    debug_dump(DBG_info, dev->settings); + +  /* y (motor) distance to move to reach scanned area */ +  move_dpi = dev->motor.base_ydpi/4; +    move = static_cast<float>(dev->model->y_offset); +    move += static_cast<float>(dev->settings.tl_y); +    move = static_cast<float>((move * move_dpi) / MM_PER_INCH); +  DBG (DBG_info, "%s: move=%f steps\n", __func__, move); + +    if (dev->settings.get_channels() * dev->settings.yres >= 600 && move > 700) { +        scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move - 500), +                     Direction::FORWARD); +      move=500; +    } +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* start */ +    start = static_cast<float>(dev->model->x_offset); +    start += static_cast<float>(dev->settings.tl_x); +    start /= sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres); +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->settings.pixels; +    session.params.requested_pixels = dev->settings.requested_pixels; +    session.params.lines = dev->settings.lines; +    session.params.depth = dev->settings.depth; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::NONE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); +} + +/** + * Send shading calibration data. The buffer is considered to always hold values + * for all the channels. + */ +void CommandSetGl124::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        std::uint8_t* data, int size) const +{ +    DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size); +    uint32_t addr, length, x, factor, segcnt, pixels, i; +  uint16_t dpiset,dpihw; +    uint8_t *ptr, *src; + +  /* logical size of a color as seen by generic code of the frontend */ +    length = size / 3; +    std::uint32_t strpixel = dev->session.pixel_startx; +    std::uint32_t endpixel = dev->session.pixel_endx; +    segcnt = dev->reg.get24(REG_SEGCNT); +  if(endpixel==0) +    { +      endpixel=segcnt; +    } + +  /* compute deletion factor */ +    dpiset = dev->reg.get16(REG_DPISET); +    dpihw = sensor.get_register_hwdpi(dpiset); +  factor=dpihw/dpiset; +  DBG( DBG_io2, "%s: factor=%d\n",__func__,factor); + +  /* turn pixel value into bytes 2x16 bits words */ +  strpixel*=2*2; /* 2 words of 2 bytes */ +  endpixel*=2*2; +  segcnt*=2*2; +  pixels=endpixel-strpixel; + +    dev->interface->record_key_value("shading_start_pixel", std::to_string(strpixel)); +    dev->interface->record_key_value("shading_pixels", std::to_string(pixels)); +    dev->interface->record_key_value("shading_length", std::to_string(length)); +    dev->interface->record_key_value("shading_factor", std::to_string(factor)); +    dev->interface->record_key_value("shading_segcnt", std::to_string(segcnt)); +    dev->interface->record_key_value("shading_segment_count", +                                     std::to_string(dev->session.segment_count)); + +  DBG( DBG_io2, "%s: using chunks of %d bytes (%d shading data pixels)\n",__func__,length, length/4); +    std::vector<uint8_t> buffer(pixels * dev->session.segment_count, 0); + +  /* write actual red data */ +  for(i=0;i<3;i++) +    { +      /* copy data to work buffer and process it */ +          /* coefficent destination */ +      ptr = buffer.data(); + +      /* iterate on both sensor segment */ +      for(x=0;x<pixels;x+=4*factor) +        { +          /* coefficient source */ +          src=data+x+strpixel+i*length; + +          /* iterate over all the segments */ +            switch (dev->session.segment_count) { +            case 1: +              ptr[0+pixels*0]=src[0+segcnt*0]; +              ptr[1+pixels*0]=src[1+segcnt*0]; +              ptr[2+pixels*0]=src[2+segcnt*0]; +              ptr[3+pixels*0]=src[3+segcnt*0]; +              break; +            case 2: +              ptr[0+pixels*0]=src[0+segcnt*0]; +              ptr[1+pixels*0]=src[1+segcnt*0]; +              ptr[2+pixels*0]=src[2+segcnt*0]; +              ptr[3+pixels*0]=src[3+segcnt*0]; +              ptr[0+pixels*1]=src[0+segcnt*1]; +              ptr[1+pixels*1]=src[1+segcnt*1]; +              ptr[2+pixels*1]=src[2+segcnt*1]; +              ptr[3+pixels*1]=src[3+segcnt*1]; +              break; +            case 4: +              ptr[0+pixels*0]=src[0+segcnt*0]; +              ptr[1+pixels*0]=src[1+segcnt*0]; +              ptr[2+pixels*0]=src[2+segcnt*0]; +              ptr[3+pixels*0]=src[3+segcnt*0]; +              ptr[0+pixels*1]=src[0+segcnt*2]; +              ptr[1+pixels*1]=src[1+segcnt*2]; +              ptr[2+pixels*1]=src[2+segcnt*2]; +              ptr[3+pixels*1]=src[3+segcnt*2]; +              ptr[0+pixels*2]=src[0+segcnt*1]; +              ptr[1+pixels*2]=src[1+segcnt*1]; +              ptr[2+pixels*2]=src[2+segcnt*1]; +              ptr[3+pixels*2]=src[3+segcnt*1]; +              ptr[0+pixels*3]=src[0+segcnt*3]; +              ptr[1+pixels*3]=src[1+segcnt*3]; +              ptr[2+pixels*3]=src[2+segcnt*3]; +              ptr[3+pixels*3]=src[3+segcnt*3]; +              break; +            } + +          /* next shading coefficient */ +          ptr+=4; +        } +        uint8_t val = dev->interface->read_register(0xd0+i); +      addr = val * 8192 + 0x10000000; +        dev->interface->write_ahb(addr, pixels * dev->session.segment_count, buffer.data()); +    } +} + + +/** @brief move to calibration area + * This functions moves scanning head to calibration area + * by doing a 600 dpi scan + * @param dev scanner device + */ +static void move_to_calibration_area(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                     Genesys_Register_Set& regs) +{ +    (void) sensor; + +    DBG_HELPER(dbg); +  int pixels; +  int size; + +    unsigned resolution = 600; +    unsigned channels = 3; +    const auto& move_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         dev->settings.scan_method); +    pixels = (move_sensor.sensor_pixels * 600) / move_sensor.optical_res; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = 1; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, move_sensor); + +    dev->cmd_set->init_regs_for_scan_session(dev, move_sensor, ®s, session); + +  size = pixels * 3; +  std::vector<uint8_t> line(size); + +    // write registers and scan data +    dev->interface->write_registers(regs); + +  DBG (DBG_info, "%s: starting line reading\n", __func__); +    dev->cmd_set->begin_scan(dev, move_sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("move_to_calibration_area"); +        scanner_stop_action(*dev); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, line.data(), size); + +    // stop scanning +    scanner_stop_action(*dev); + +  if (DBG_LEVEL >= DBG_data) +    { +      sanei_genesys_write_pnm_file("gl124_movetocalarea.pnm", line.data(), 8, 3, pixels, 1); +    } +} + +/* this function does the led calibration by scanning one line of the calibration +   area below scanner's top on white strip. + +-needs working coarse/gain +*/ +SensorExposure CommandSetGl124::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int total_size; +  int resolution; +  int dpihw; +  int i, j; +  int val; +  int channels; +  int avg[3]; +  int turn; +  uint16_t exp[3],target; + +  /* move to calibration area */ +  move_to_calibration_area(dev, sensor, regs); + +  /* offset calibration is always done in 16 bit depth color mode */ +  channels = 3; +    dpihw = sensor.get_register_hwdpi(dev->settings.xres); +    resolution = dpihw; +    unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres); +    resolution /= ccd_size_divisor; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         dev->settings.scan_method); +    num_pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    total_size = num_pixels * channels * (session.params.depth / 8) * 1; +  std::vector<uint8_t> line(total_size); + +    // initial loop values and boundaries +    exp[0] = calib_sensor.exposure.red; +    exp[1] = calib_sensor.exposure.green; +    exp[2] = calib_sensor.exposure.blue; +  target=sensor.gain_white_ref*256; + +  turn = 0; + +  /* no move during led calibration */ +  sanei_genesys_set_motor_power(regs, false); +    bool acceptable = false; +  do +    { +        // set up exposure +        regs.set24(REG_EXPR, exp[0]); +        regs.set24(REG_EXPG, exp[1]); +        regs.set24(REG_EXPB, exp[2]); + +        // write registers and scan data +        dev->interface->write_registers(regs); + +      DBG(DBG_info, "%s: starting line reading\n", __func__); +        begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("led_calibration"); +            scanner_stop_action(*dev); +            return calib_sensor.exposure; +        } + +        sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +        // stop scanning +        scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[30]; +          std::snprintf(fn, 30, "gl124_led_%02d.pnm", turn); +          sanei_genesys_write_pnm_file(fn, line.data(), session.params.depth, channels, num_pixels, +                                       1); +	} + +      /* compute average */ +      for (j = 0; j < channels; j++) +	{ +	  avg[j] = 0; +	  for (i = 0; i < num_pixels; i++) +	    { +	      if (dev->model->is_cis) +		val = +		  line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		  line[i * 2 + j * 2 * num_pixels]; +	      else +		val = +		  line[i * 2 * channels + 2 * j + 1] * 256 + +		  line[i * 2 * channels + 2 * j]; +	      avg[j] += val; +	    } + +	  avg[j] /= num_pixels; +	} + +      DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +      /* check if exposure gives average within the boundaries */ +        acceptable = true; +      for(i=0;i<3;i++) +        { +          /* we accept +- 2% delta from target */ +          if(abs(avg[i]-target)>target/50) +            { +                float prev_weight = 0.5; +                exp[i] = exp[i] * prev_weight + ((exp[i] * target) / avg[i]) * (1 - prev_weight); +                acceptable = false; +            } +        } + +      turn++; +    } +  while (!acceptable && turn < 100); + +  DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]); + +    // set these values as final ones for scan +    dev->reg.set24(REG_EXPR, exp[0]); +    dev->reg.set24(REG_EXPG, exp[1]); +    dev->reg.set24(REG_EXPB, exp[2]); + +    return { exp[0], exp[1], exp[2] }; +} + +/** + * average dark pixels of a 8 bits 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; +} + + +void CommandSetGl124::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    unsigned channels; +  int pass = 0, avg, total_size; +    int topavg, bottomavg, lines; +  int top, bottom, black_pixels, pixels; + +    // no gain nor offset for TI AFE +    uint8_t reg0a = dev->interface->read_register(REG_0x0A); +    if (((reg0a & REG_0x0A_SIFSEL) >> REG_0x0AS_SIFSEL) == 3) { +      return; +    } + +  /* offset calibration is always done in color mode */ +  channels = 3; +  dev->calib_pixels = sensor.sensor_pixels; +  lines=1; +    pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res; +    black_pixels = (sensor.black_pixels * sensor.optical_res) / sensor.optical_res; +  DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels); + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = sensor.optical_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +  /* allocate memory for scans */ +    total_size = pixels * channels * lines * (session.params.depth / 8); + +  std::vector<uint8_t> first_line(total_size); +  std::vector<uint8_t> second_line(total_size); + +  /* init gain */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); + +  /* scan with no move */ +  bottom = 10; +  dev->frontend.set_offset(0, bottom); +  dev->frontend.set_offset(1, bottom); +  dev->frontend.set_offset(2, bottom); + +    set_fe(dev, sensor, AFE_SET); +    dev->interface->write_registers(regs); +  DBG(DBG_info, "%s: starting first line reading\n", __func__); +    begin_scan(dev, sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("offset_calibration"); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +  if (DBG_LEVEL >= DBG_data) +   { +      char title[30]; +        std::snprintf(title, 30, "gl124_offset%03d.pnm", bottom); +        sanei_genesys_write_pnm_file(title, first_line.data(), session.params.depth, +                                     channels, pixels, lines); +   } + +  bottomavg = dark_average(first_line.data(), pixels, lines, channels, black_pixels); +  DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg); + +  /* now top value */ +  top = 255; +  dev->frontend.set_offset(0, top); +  dev->frontend.set_offset(1, top); +  dev->frontend.set_offset(2, top); +    set_fe(dev, sensor, AFE_SET); +    dev->interface->write_registers(regs); +  DBG(DBG_info, "%s: starting second line reading\n", __func__); +    begin_scan(dev, sensor, ®s, true); +    sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +  topavg = dark_average(second_line.data(), pixels, lines, channels, black_pixels); +  DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg); + +  /* 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 +        set_fe(dev, sensor, AFE_SET); +        dev->interface->write_registers(regs); +      DBG(DBG_info, "%s: starting second line reading\n", __func__); +        begin_scan(dev, sensor, ®s, true); +        sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char title[30]; +          std::snprintf(title, 30, "gl124_offset%03d.pnm", dev->frontend.get_offset(1)); +          sanei_genesys_write_pnm_file(title, second_line.data(), session.params.depth, +                                       channels, pixels, lines); +	} + +      avg = dark_average(second_line.data(), pixels, 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)); +} + + +/* alternative coarse gain calibration +   this on uses the settings from offset_calibration and +   uses only one scanline + */ +/* +  with offset and coarse calibration we only want to get our input range into +  a reasonable shape. the fine calibration of the upper and lower bounds will +  be done with shading. + */ +void CommandSetGl124::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER_ARGS(dbg, "dpi = %d", dpi); +  int pixels; +  int total_size; +  int i, j, channels; +  int max[3]; +  float gain[3],coeff; +  int val, code, lines; + +    // no gain nor offset for TI AFE +    uint8_t reg0a = dev->interface->read_register(REG_0x0A); +    if (((reg0a & REG_0x0A_SIFSEL) >> REG_0x0AS_SIFSEL) == 3) { +      return; +    } + +  /* coarse gain calibration is always done in color mode */ +  channels = 3; + +  if(dev->settings.xres<sensor.optical_res) +    { +        coeff = 0.9f; +    } else { +        coeff = 1.0f; +    } +  lines=10; +     pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res; + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = sensor.optical_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    try { +        init_regs_for_scan_session(dev, sensor, ®s, session); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); +        throw; +    } + +    sanei_genesys_set_motor_power(regs, false); + +    dev->interface->write_registers(regs); + +    total_size = pixels * channels * (16 / session.params.depth) * lines; + +  std::vector<uint8_t> line(total_size); + +    set_fe(dev, sensor, AFE_SET); +    begin_scan(dev, sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("coarse_gain_calibration"); +        scanner_stop_action(*dev); +        move_back_home(dev, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl124_gain.pnm", line.data(), session.params.depth, +                                     channels, pixels, lines); +    } + +  /* average value on each channel */ +  for (j = 0; j < channels; j++) +    { +      max[j] = 0; +      for (i = pixels/4; i < (pixels*3/4); i++) +	{ +            if (dev->model->is_cis) { +                val = line[i + j * pixels]; +            } else { +                val = line[i * channels + j]; +            } + +	    max[j] += val; +	} +      max[j] = max[j] / (pixels/2); + +      gain[j] = (static_cast<float>(sensor.gain_white_ref) * coeff) / max[j]; + +      /* turn logical gain value into gain code, checking for overflow */ +        code = static_cast<int>(283 - 208 / gain[j]); +      if (code > 255) +	code = 255; +      else if (code < 0) +	code = 0; +      dev->frontend.set_gain(j, code); + +      DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], +          gain[j], dev->frontend.get_gain(j)); +    } + +    if (dev->model->is_cis) { +        uint8_t gain0 = dev->frontend.get_gain(0); +        if (gain0 > dev->frontend.get_gain(1)) { +            gain0 = dev->frontend.get_gain(1); +        } +        if (gain0 > dev->frontend.get_gain(2)) { +            gain0 = dev->frontend.get_gain(2); +        } +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); +    } + +    if (channels == 1) { +        dev->frontend.set_gain(0, dev->frontend.get_gain(1)); +        dev->frontend.set_gain(2, dev->frontend.get_gain(1)); +    } + +    scanner_stop_action(*dev); + +    move_back_home(dev, true); +} + +// wait for lamp warmup by scanning the same line until difference +// between 2 scans is below a threshold +void CommandSetGl124::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* reg, int* channels, +                                           int* total_size) const +{ +    DBG_HELPER(dbg); +  int num_pixels; + +  *channels=3; + +  *reg = dev->reg; + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = dev->motor.base_ydpi; +    session.params.startx = sensor.sensor_pixels / 4; +    session.params.starty = 0; +    session.params.pixels = sensor.sensor_pixels / 2; +    session.params.lines = 1; +    session.params.depth = 8; +    session.params.channels = *channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, reg, session); + +    num_pixels = session.output_pixels; + +  *total_size = num_pixels * 3 * 1;        /* colors * bytes_per_color * scan lines */ + +  sanei_genesys_set_motor_power(*reg, false); +    dev->interface->write_registers(*reg); +} + +/** @brief default GPIO values + * set up GPIO/GPOE for idle state + * @param dev device to set up + */ +static void gl124_init_gpio(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  int idx; + +  /* per model GPIO layout */ +    if (dev->model->model_id == ModelId::CANON_LIDE_110) { +      idx = 0; +    } else if (dev->model->model_id == ModelId::CANON_LIDE_120) { +      idx = 2; +    } +  else +    {                                /* canon LiDE 210 and 220 case */ +      idx = 1; +    } + +    dev->interface->write_register(REG_0x31, gpios[idx].r31); +    dev->interface->write_register(REG_0x32, gpios[idx].r32); +    dev->interface->write_register(REG_0x33, gpios[idx].r33); +    dev->interface->write_register(REG_0x34, gpios[idx].r34); +    dev->interface->write_register(REG_0x35, gpios[idx].r35); +    dev->interface->write_register(REG_0x36, gpios[idx].r36); +    dev->interface->write_register(REG_0x38, gpios[idx].r38); +} + +/** + * set memory layout by filling values in dedicated registers + */ +static void gl124_init_memory_layout(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  int idx = 0; + +  /* point to per model memory layout */ +    if (dev->model->model_id == ModelId::CANON_LIDE_110 || +        dev->model->model_id == ModelId::CANON_LIDE_120) +    { +      idx = 0; +    } +  else +    {                                /* canon LiDE 210 and 220 case */ +      idx = 1; +    } + +  /* setup base address for shading data. */ +  /* values must be multiplied by 8192=0x4000 to give address on AHB */ +  /* R-Channel shading bank0 address setting for CIS */ +    dev->interface->write_register(0xd0, layouts[idx].rd0); +  /* G-Channel shading bank0 address setting for CIS */ +    dev->interface->write_register(0xd1, layouts[idx].rd1); +  /* B-Channel shading bank0 address setting for CIS */ +    dev->interface->write_register(0xd2, layouts[idx].rd2); + +  /* setup base address for scanned data. */ +  /* values must be multiplied by 1024*2=0x0800 to give address on AHB */ +  /* R-Channel ODD image buffer 0x0124->0x92000 */ +  /* size for each buffer is 0x16d*1k word */ +    dev->interface->write_register(0xe0, layouts[idx].re0); +    dev->interface->write_register(0xe1, layouts[idx].re1); +  /* R-Channel ODD image buffer end-address 0x0291->0x148800 => size=0xB6800*/ +    dev->interface->write_register(0xe2, layouts[idx].re2); +    dev->interface->write_register(0xe3, layouts[idx].re3); + +  /* R-Channel EVEN image buffer 0x0292 */ +    dev->interface->write_register(0xe4, layouts[idx].re4); +    dev->interface->write_register(0xe5, layouts[idx].re5); +  /* R-Channel EVEN image buffer end-address 0x03ff*/ +    dev->interface->write_register(0xe6, layouts[idx].re6); +    dev->interface->write_register(0xe7, layouts[idx].re7); + +  /* same for green, since CIS, same addresses */ +    dev->interface->write_register(0xe8, layouts[idx].re0); +    dev->interface->write_register(0xe9, layouts[idx].re1); +    dev->interface->write_register(0xea, layouts[idx].re2); +    dev->interface->write_register(0xeb, layouts[idx].re3); +    dev->interface->write_register(0xec, layouts[idx].re4); +    dev->interface->write_register(0xed, layouts[idx].re5); +    dev->interface->write_register(0xee, layouts[idx].re6); +    dev->interface->write_register(0xef, layouts[idx].re7); + +/* same for blue, since CIS, same addresses */ +    dev->interface->write_register(0xf0, layouts[idx].re0); +    dev->interface->write_register(0xf1, layouts[idx].re1); +    dev->interface->write_register(0xf2, layouts[idx].re2); +    dev->interface->write_register(0xf3, layouts[idx].re3); +    dev->interface->write_register(0xf4, layouts[idx].re4); +    dev->interface->write_register(0xf5, layouts[idx].re5); +    dev->interface->write_register(0xf6, layouts[idx].re6); +    dev->interface->write_register(0xf7, layouts[idx].re7); +} + +/** + * initialize backend and ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + */ +void CommandSetGl124::init(Genesys_Device* dev) const +{ +  DBG_INIT (); +    DBG_HELPER(dbg); + +    sanei_genesys_asic_init(dev, 0); +} + + +/* * + * initialize ASIC from power on condition + */ +void CommandSetGl124::asic_boot(Genesys_Device* dev, bool cold) const +{ +    DBG_HELPER(dbg); + +    // reset ASIC in case of cold boot +    if (cold) { +        dev->interface->write_register(0x0e, 0x01); +        dev->interface->write_register(0x0e, 0x00); +    } + +    // enable GPOE 17 +    dev->interface->write_register(0x36, 0x01); + +    // set GPIO 17 +    uint8_t val = dev->interface->read_register(0x33); +    val |= 0x01; +    dev->interface->write_register(0x33, val); + +    // test CHKVER +    val = dev->interface->read_register(REG_0x100); +    if (val & REG_0x100_CHKVER) { +        val = dev->interface->read_register(0x00); +        DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val); +    } + +  /* Set default values for registers */ +  gl124_init_registers (dev); + +    // Write initial registers +    dev->interface->write_registers(dev->reg); + +    // tune reg 0B +    dev->interface->write_register(REG_0x0B, REG_0x0B_30MHZ | REG_0x0B_ENBDRAM | REG_0x0B_64M); +  dev->reg.remove_reg(0x0b); + +    //set up end access +    dev->interface->write_0x8c(0x10, 0x0b); +    dev->interface->write_0x8c(0x13, 0x0e); + +  /* CIS_LINE */ +    dev->reg.init_reg(0x08, REG_0x08_CIS_LINE); +    dev->interface->write_register(0x08, dev->reg.find_reg(0x08).value); + +    // setup gpio +    gl124_init_gpio(dev); + +    // setup internal memory layout +    gl124_init_memory_layout(dev); +} + + +void CommandSetGl124::update_hardware_sensors(Genesys_Scanner* s) const +{ +  /* do what is needed to get a new set of events, but try to not loose +     any of them. +   */ +    DBG_HELPER(dbg); +    uint8_t val = s->dev->interface->read_register(REG_0x31); + +  /* TODO : for the next scanner special case, +   * add another per scanner button profile struct to avoid growing +   * hard-coded button mapping here. +   */ +    if ((s->dev->model->gpio_id == GpioId::CANON_LIDE_110) || +        (s->dev->model->gpio_id == GpioId::CANON_LIDE_120)) +    { +        s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0); +        s->buttons[BUTTON_FILE_SW].write((val & 0x08) == 0); +        s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0); +        s->buttons[BUTTON_COPY_SW].write((val & 0x02) == 0); +    } +  else +    { /* LiDE 210 case */ +        s->buttons[BUTTON_EXTRA_SW].write((val & 0x01) == 0); +        s->buttons[BUTTON_SCAN_SW].write((val & 0x02) == 0); +        s->buttons[BUTTON_COPY_SW].write((val & 0x04) == 0); +        s->buttons[BUTTON_EMAIL_SW].write((val & 0x08) == 0); +        s->buttons[BUTTON_FILE_SW].write((val & 0x10) == 0); +    } +} + +void CommandSetGl124::update_home_sensor_gpio(Genesys_Device& dev) const +{ +    DBG_HELPER(dbg); + +    std::uint8_t val = dev.interface->read_register(REG_0x32); +    val &= ~REG_0x32_GPIO10; +    dev.interface->write_register(REG_0x32, val); +} + +bool CommandSetGl124::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    (void) dev; +    return true; +} + +void CommandSetGl124::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    sanei_genesys_send_gamma_table(dev, sensor); +} + +void CommandSetGl124::load_document(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl124::detect_document_end(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl124::eject_document(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl124::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   bool forward, bool black) const +{ +    (void) dev; +    (void) sensor; +    (void) forward; +    (void) black; +    throw SaneException("not implemented"); +} + +void CommandSetGl124::move_to_ta(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +std::unique_ptr<CommandSet> create_gl124_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl124{}); +} + +} // namespace gl124 +} // namespace genesys diff --git a/backend/genesys/gl124.h b/backend/genesys/gl124.h new file mode 100644 index 0000000..cdf8faf --- /dev/null +++ b/backend/genesys/gl124.h @@ -0,0 +1,205 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2016 Stéphane Voltz <stef.dev@free.fr> + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#ifndef BACKEND_GENESYS_GL124_H +#define BACKEND_GENESYS_GL124_H + +#include "genesys.h" +#include "command_set.h" + +namespace genesys { +namespace gl124 { + +typedef struct +{ +  uint8_t r31; +  uint8_t r32; +  uint8_t r33; +  uint8_t r34; +  uint8_t r35; +  uint8_t r36; +  uint8_t r38; +} Gpio_layout; + +/** @brief gpio layout + * describes initial gpio settings for a given model + * registers 0x31 to 0x38 + */ +static Gpio_layout gpios[]={ +	/* LiDE 110 */ +	{ /*    0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x38 */ +		0x9f, 0x59, 0x01, 0x80, 0x5f, 0x01, 0x00 +	}, +	/* LiDE 210 */ +	{ +		0x9f, 0x59, 0x01, 0x80, 0x5f, 0x01, 0x00 +	}, +	/* LiDE 120 */ +	{ +		0x9f, 0x53, 0x01, 0x80, 0x5f, 0x01, 0x00 +	}, +}; + +typedef struct +{ +  uint8_t rd0; +  uint8_t rd1; +  uint8_t rd2; +  uint8_t re0; +  uint8_t re1; +  uint8_t re2; +  uint8_t re3; +  uint8_t re4; +  uint8_t re5; +  uint8_t re6; +  uint8_t re7; +} Memory_layout; + +static Memory_layout layouts[]={ +	/* LIDE 110, 120 */ +	{    /* 0xd0 0xd1 0xd2 */ +		0x0a, 0x15, 0x20, +	     /* 0xe0  0xe1  0xe2  0xe3  0xe4  0xe5  0xe6  0xe7 */ +		0x00, 0xac, 0x08, 0x55, 0x08, 0x56, 0x0f, 0xff +	}, +	/* LIDE 210, 220 */ +	{ +		0x0a, 0x1f, 0x34, +		0x01, 0x24, 0x08, 0x91, 0x08, 0x92, 0x0f, 0xff +	} +}; + +static void gl124_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, int steps); + +class CommandSetGl124 : public CommandSet +{ +public: +    ~CommandSetGl124() override = default; + +    bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override; + +    void init(Genesys_Device* dev) const override; + +    void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              Genesys_Register_Set* regs, int* channels, +                              int* total_size) const override; + +    void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs) const override; + +    void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set& regs) const override; + +    void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set* reg, +                                    const ScanSession& session) const override; + +    void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override; +    void set_powersaving(Genesys_Device* dev, int delay) const override; +    void save_power(Genesys_Device* dev, bool enable) const override; + +    void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                    Genesys_Register_Set* regs, bool start_motor) const override; + +    void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override; + +    void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void search_start_position(Genesys_Device* dev) const override; + +    void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set& regs) const override; + +    void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set& regs, int dpi) const override; + +    SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   Genesys_Register_Set& regs) const override; + +    void wait_for_motor_stop(Genesys_Device* dev) const override; + +    void move_back_home(Genesys_Device* dev, bool wait_until_home) const override; + +    void update_hardware_sensors(struct Genesys_Scanner* s) const override; + +    bool needs_update_home_sensor_gpio() const override { return true; } + +    void update_home_sensor_gpio(Genesys_Device& dev) const override; + +    void load_document(Genesys_Device* dev) const override; + +    void detect_document_end(Genesys_Device* dev) const override; + +    void eject_document(Genesys_Device* dev) const override; + +    void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                      bool forward, bool black) const override; + +    void move_to_ta(Genesys_Device* dev) const override; + +    void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data, +                           int size) const override; + +    ScanSession calculate_scan_session(const Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       const Genesys_Settings& settings) const override; + +    void asic_boot(Genesys_Device* dev, bool cold) const override; +}; + +enum SlopeTable +{ +    SCAN_TABLE = 0, // table 1 at 0x4000 +    BACKTRACK_TABLE = 1, // table 2 at 0x4800 +    STOP_TABLE = 2, // table 3 at 0x5000 +    FAST_TABLE = 3, // table 4 at 0x5800 +    HOME_TABLE = 4, // table 5 at 0x6000 +}; + +} // namespace gl124 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL124_H diff --git a/backend/genesys/gl124_registers.h b/backend/genesys/gl124_registers.h new file mode 100644 index 0000000..9b42084 --- /dev/null +++ b/backend/genesys/gl124_registers.h @@ -0,0 +1,316 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL124_REGISTERS_H +#define BACKEND_GENESYS_GL124_REGISTERS_H + +#include <cstdint> + +namespace genesys { +namespace gl124 { + +using RegAddr = std::uint16_t; +using RegMask = std::uint8_t; +using RegShift = unsigned; + +static constexpr RegAddr REG_0x01 = 0x01; +static constexpr RegMask REG_0x01_CISSET = 0x80; +static constexpr RegMask REG_0x01_DOGENB = 0x40; +static constexpr RegMask REG_0x01_DVDSET = 0x20; +static constexpr RegMask REG_0x01_STAGGER = 0x10; +static constexpr RegMask REG_0x01_COMPENB = 0x08; +static constexpr RegMask REG_0x01_TRUEGRAY = 0x04; +static constexpr RegMask REG_0x01_SHDAREA = 0x02; +static constexpr RegMask REG_0x01_SCAN = 0x01; + +static constexpr RegAddr REG_0x02 = 0x02; +static constexpr RegMask REG_0x02_NOTHOME = 0x80; +static constexpr RegMask REG_0x02_ACDCDIS = 0x40; +static constexpr RegMask REG_0x02_AGOHOME = 0x20; +static constexpr RegMask REG_0x02_MTRPWR = 0x10; +static constexpr RegMask REG_0x02_FASTFED = 0x08; +static constexpr RegMask REG_0x02_MTRREV = 0x04; +static constexpr RegMask REG_0x02_HOMENEG = 0x02; +static constexpr RegMask REG_0x02_LONGCURV = 0x01; + +static constexpr RegAddr REG_0x03 = 0x03; +static constexpr RegMask REG_0x03_LAMPDOG = 0x80; +static constexpr RegMask REG_0x03_AVEENB = 0x40; +static constexpr RegMask REG_0x03_XPASEL = 0x20; +static constexpr RegMask REG_0x03_LAMPPWR = 0x10; +static constexpr RegMask REG_0x03_LAMPTIM = 0x0f; + +static constexpr RegAddr REG_0x04 = 0x04; +static constexpr RegMask REG_0x04_LINEART = 0x80; +static constexpr RegMask REG_0x04_BITSET = 0x40; +static constexpr RegMask REG_0x04_FILTER = 0x30; +static constexpr RegMask REG_0x04_AFEMOD = 0x07; + +static constexpr RegAddr REG_0x05 = 0x05; +static constexpr RegMask REG_0x05_DPIHW = 0xc0; +static constexpr RegMask REG_0x05_DPIHW_600 = 0x00; +static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40; +static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80; +static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0; +static constexpr RegMask REG_0x05_MTLLAMP = 0x30; +static constexpr RegMask REG_0x05_GMMENB = 0x08; +static constexpr RegMask REG_0x05_ENB20M = 0x04; +static constexpr RegMask REG_0x05_MTLBASE = 0x03; + +static constexpr RegAddr REG_0x06 = 0x06; +static constexpr RegMask REG_0x06_SCANMOD = 0xe0; +static constexpr RegMask REG_0x06S_SCANMOD = 5; +static constexpr RegMask REG_0x06_PWRBIT = 0x10; +static constexpr RegMask REG_0x06_GAIN4 = 0x08; +static constexpr RegMask REG_0x06_OPTEST = 0x07; + +static constexpr RegMask REG_0x07_LAMPSIM = 0x80; + +static constexpr RegMask REG_0x08_DRAM2X = 0x80; +static constexpr RegMask REG_0x08_MPENB = 0x20; +static constexpr RegMask REG_0x08_CIS_LINE = 0x10; +static constexpr RegMask REG_0x08_IR2_ENB = 0x08; +static constexpr RegMask REG_0x08_IR1_ENB = 0x04; +static constexpr RegMask REG_0x08_ENB24M = 0x01; + +static constexpr RegMask REG_0x09_MCNTSET = 0xc0; +static constexpr RegMask REG_0x09_EVEN1ST = 0x20; +static constexpr RegMask REG_0x09_BLINE1ST = 0x10; +static constexpr RegMask REG_0x09_BACKSCAN = 0x08; +static constexpr RegMask REG_0x09_OUTINV = 0x04; +static constexpr RegMask REG_0x09_SHORTTG = 0x02; + +static constexpr RegShift REG_0x09S_MCNTSET = 6; +static constexpr RegShift REG_0x09S_CLKSET = 4; + +static constexpr RegAddr REG_0x0A = 0x0a; +static constexpr RegMask REG_0x0A_SIFSEL = 0xc0; +static constexpr RegShift REG_0x0AS_SIFSEL = 6; +static constexpr RegMask REG_0x0A_SHEETFED = 0x20; +static constexpr RegMask REG_0x0A_LPWMEN = 0x10; + +static constexpr RegAddr REG_0x0B = 0x0b; +static constexpr RegMask REG_0x0B_DRAMSEL = 0x07; +static constexpr RegMask REG_0x0B_16M = 0x01; +static constexpr RegMask REG_0x0B_64M = 0x02; +static constexpr RegMask REG_0x0B_128M = 0x03; +static constexpr RegMask REG_0x0B_256M = 0x04; +static constexpr RegMask REG_0x0B_512M = 0x05; +static constexpr RegMask REG_0x0B_1G = 0x06; +static constexpr RegMask REG_0x0B_ENBDRAM = 0x08; +static constexpr RegMask REG_0x0B_RFHDIS = 0x10; +static constexpr RegMask REG_0x0B_CLKSET = 0xe0; +static constexpr RegMask REG_0x0B_24MHZ = 0x00; +static constexpr RegMask REG_0x0B_30MHZ = 0x20; +static constexpr RegMask REG_0x0B_40MHZ = 0x40; +static constexpr RegMask REG_0x0B_48MHZ = 0x60; +static constexpr RegMask REG_0x0B_60MHZ = 0x80; + +static constexpr RegAddr REG_0x0D = 0x0d; +static constexpr RegMask REG_0x0D_MTRP_RDY = 0x80; +static constexpr RegMask REG_0x0D_FULLSTP = 0x10; +static constexpr RegMask REG_0x0D_CLRMCNT = 0x04; +static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02; +static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01; + +static constexpr RegAddr REG_0x0F = 0x0f; + +static constexpr RegMask REG_0x16_CTRLHI = 0x80; +static constexpr RegMask REG_0x16_TOSHIBA = 0x40; +static constexpr RegMask REG_0x16_TGINV = 0x20; +static constexpr RegMask REG_0x16_CK1INV = 0x10; +static constexpr RegMask REG_0x16_CK2INV = 0x08; +static constexpr RegMask REG_0x16_CTRLINV = 0x04; +static constexpr RegMask REG_0x16_CKDIS = 0x02; +static constexpr RegMask REG_0x16_CTRLDIS = 0x01; + +static constexpr RegMask REG_0x17_TGMODE = 0xc0; +static constexpr RegMask REG_0x17_SNRSYN = 0x0f; + +static constexpr RegAddr REG_0x18 = 0x18; +static constexpr RegMask REG_0x18_CNSET = 0x80; +static constexpr RegMask REG_0x18_DCKSEL = 0x60; +static constexpr RegMask REG_0x18_CKTOGGLE = 0x10; +static constexpr RegMask REG_0x18_CKDELAY = 0x0c; +static constexpr RegMask REG_0x18_CKSEL = 0x03; + +static constexpr RegMask REG_0x1A_SW2SET = 0x80; +static constexpr RegMask REG_0x1A_SW1SET = 0x40; +static constexpr RegMask REG_0x1A_MANUAL3 = 0x02; +static constexpr RegMask REG_0x1A_MANUAL1 = 0x01; +static constexpr RegMask REG_0x1A_CK4INV = 0x08; +static constexpr RegMask REG_0x1A_CK3INV = 0x04; +static constexpr RegMask REG_0x1A_LINECLP = 0x02; + +static constexpr RegMask REG_0x1C_TBTIME = 0x07; + +static constexpr RegAddr REG_0x1D = 0x1d; +static constexpr RegMask REG_0x1D_CK4LOW = 0x80; +static constexpr RegMask REG_0x1D_CK3LOW = 0x40; +static constexpr RegMask REG_0x1D_CK1LOW = 0x20; +static constexpr RegMask REG_0x1D_LINESEL = 0x1f; +static constexpr RegShift REG_0x1DS_LINESEL = 0; + +static constexpr RegAddr REG_0x1E = 0x1e; +static constexpr RegMask REG_0x1E_WDTIME = 0xf0; +static constexpr RegShift REG_0x1ES_WDTIME = 4; + +static constexpr RegAddr REG_0x30 = 0x30; +static constexpr RegAddr REG_0x31 = 0x31; +static constexpr RegAddr REG_0x32 = 0x32; +static constexpr RegMask REG_0x32_GPIO16 = 0x80; +static constexpr RegMask REG_0x32_GPIO15 = 0x40; +static constexpr RegMask REG_0x32_GPIO14 = 0x20; +static constexpr RegMask REG_0x32_GPIO13 = 0x10; +static constexpr RegMask REG_0x32_GPIO12 = 0x08; +static constexpr RegMask REG_0x32_GPIO11 = 0x04; +static constexpr RegMask REG_0x32_GPIO10 = 0x02; +static constexpr RegMask REG_0x32_GPIO9 = 0x01; +static constexpr RegAddr REG_0x33 = 0x33; +static constexpr RegAddr REG_0x34 = 0x34; +static constexpr RegAddr REG_0x35 = 0x35; +static constexpr RegAddr REG_0x36 = 0x36; +static constexpr RegAddr REG_0x37 = 0x37; +static constexpr RegAddr REG_0x38 = 0x38; +static constexpr RegAddr REG_0x39 = 0x39; + +static constexpr RegAddr REG_0x60 = 0x60; +static constexpr RegMask REG_0x60_LED4TG = 0x80; +static constexpr RegMask REG_0x60_YENB = 0x40; +static constexpr RegMask REG_0x60_YBIT = 0x20; +static constexpr RegMask REG_0x60_ACYNCNRLC = 0x10; +static constexpr RegMask REG_0x60_ENOFFSET = 0x08; +static constexpr RegMask REG_0x60_LEDADD = 0x04; +static constexpr RegMask REG_0x60_CK4ADC = 0x02; +static constexpr RegMask REG_0x60_AUTOCONF = 0x01; + +static constexpr RegAddr REG_0x80 = 0x80; +static constexpr RegAddr REG_0x81 = 0x81; + +static constexpr RegAddr REG_0xA0 = 0xa0; +static constexpr RegMask REG_0xA0_FSTPSEL = 0x38; +static constexpr RegShift REG_0xA0S_FSTPSEL = 3; +static constexpr RegMask REG_0xA0_STEPSEL = 0x07; +static constexpr RegShift REG_0xA0S_STEPSEL = 0; + +static constexpr RegAddr REG_0xA1 = 0xa1; +static constexpr RegAddr REG_0xA2 = 0xa2; +static constexpr RegAddr REG_0xA3 = 0xa3; +static constexpr RegAddr REG_0xA4 = 0xa4; +static constexpr RegAddr REG_0xA5 = 0xa5; +static constexpr RegAddr REG_0xA6 = 0xa6; +static constexpr RegAddr REG_0xA7 = 0xa7; +static constexpr RegAddr REG_0xA8 = 0xa8; +static constexpr RegAddr REG_0xA9 = 0xa9; +static constexpr RegAddr REG_0xAA = 0xaa; +static constexpr RegAddr REG_0xAB = 0xab; +static constexpr RegAddr REG_0xAC = 0xac; +static constexpr RegAddr REG_0xAD = 0xad; +static constexpr RegAddr REG_0xAE = 0xae; +static constexpr RegAddr REG_0xAF = 0xaf; +static constexpr RegAddr REG_0xB0 = 0xb0; +static constexpr RegAddr REG_0xB1 = 0xb1; + +static constexpr RegAddr REG_0xB2 = 0xb2; +static constexpr RegMask REG_0xB2_Z1MOD = 0x1f; +static constexpr RegAddr REG_0xB3 = 0xb3; +static constexpr RegMask REG_0xB3_Z1MOD = 0xff; +static constexpr RegAddr REG_0xB4 = 0xb4; +static constexpr RegMask REG_0xB4_Z1MOD = 0xff; + +static constexpr RegAddr REG_0xB5 = 0xb5; +static constexpr RegMask REG_0xB5_Z2MOD = 0x1f; +static constexpr RegAddr REG_0xB6 = 0xb6; +static constexpr RegMask REG_0xB6_Z2MOD = 0xff; +static constexpr RegAddr REG_0xB7 = 0xb7; +static constexpr RegMask REG_0xB7_Z2MOD = 0xff; + +static constexpr RegAddr REG_0x100 = 0x100; +static constexpr RegMask REG_0x100_DOCSNR = 0x80; +static constexpr RegMask REG_0x100_ADFSNR = 0x40; +static constexpr RegMask REG_0x100_COVERSNR = 0x20; +static constexpr RegMask REG_0x100_CHKVER = 0x10; +static constexpr RegMask REG_0x100_DOCJAM = 0x08; +static constexpr RegMask REG_0x100_HISPDFLG = 0x04; +static constexpr RegMask REG_0x100_MOTMFLG = 0x02; +static constexpr RegMask REG_0x100_DATAENB = 0x01; + +static constexpr RegAddr REG_0x114 = 0x114; +static constexpr RegAddr REG_0x115 = 0x115; + +static constexpr RegAddr REG_LINCNT = 0x25; +static constexpr RegAddr REG_MAXWD = 0x28; +static constexpr RegAddr REG_DPISET = 0x2c; +static constexpr RegAddr REG_FEEDL = 0x3d; +static constexpr RegAddr REG_CK1MAP = 0x74; +static constexpr RegAddr REG_CK3MAP = 0x77; +static constexpr RegAddr REG_CK4MAP = 0x7a; +static constexpr RegAddr REG_LPERIOD = 0x7d; +static constexpr RegAddr REG_DUMMY = 0x80; +static constexpr RegAddr REG_STRPIXEL = 0x82; +static constexpr RegAddr REG_ENDPIXEL = 0x85; +static constexpr RegAddr REG_EXPDMY = 0x88; +static constexpr RegAddr REG_EXPR = 0x8a; +static constexpr RegAddr REG_EXPG = 0x8d; +static constexpr RegAddr REG_EXPB = 0x90; +static constexpr RegAddr REG_SEGCNT = 0x93; +static constexpr RegAddr REG_TG0CNT = 0x96; +static constexpr RegAddr REG_SCANFED = 0xa2; +static constexpr RegAddr REG_STEPNO = 0xa4; +static constexpr RegAddr REG_FWDSTEP = 0xa6; +static constexpr RegAddr REG_BWDSTEP = 0xa8; +static constexpr RegAddr REG_FASTNO = 0xaa; +static constexpr RegAddr REG_FSHDEC = 0xac; +static constexpr RegAddr REG_FMOVNO = 0xae; +static constexpr RegAddr REG_FMOVDEC = 0xb0; +static constexpr RegAddr REG_Z1MOD = 0xb2; +static constexpr RegAddr REG_Z2MOD = 0xb5; + +static constexpr RegAddr REG_TRUER = 0x110; +static constexpr RegAddr REG_TRUEG = 0x111; +static constexpr RegAddr REG_TRUEB = 0x112; + +} // namespace gl124 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL843_REGISTERS_H 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 diff --git a/backend/genesys/gl646.h b/backend/genesys/gl646.h new file mode 100644 index 0000000..afcfa05 --- /dev/null +++ b/backend/genesys/gl646.h @@ -0,0 +1,521 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003-2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004-2005 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> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL646_H +#define BACKEND_GENESYS_GL646_H + +#include "genesys.h" +#include "command_set.h" +#include "motor.h" + +namespace genesys { +namespace gl646 { + +static void gl646_set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set, int dpi); + +/** + * sets up the scanner for a scan, registers, gamma tables, shading tables + * and slope tables, based on the parameter struct. + * @param dev         device to set up + * @param regs        registers to set up + * @param settings    settings of the scan + * @param split       true if move before scan has to be done + * @param xcorrection true if scanner's X geometry must be taken into account to + * 		     compute X, ie add left margins + * @param ycorrection true if scanner's Y geometry must be taken into account to + * 		     compute Y, ie add top margins + */ +static void setup_for_scan(Genesys_Device* device, +                           const Genesys_Sensor& sensor, +                           Genesys_Register_Set*regs, +                           Genesys_Settings settings, +                           bool split, +                           bool xcorrection, +                           bool ycorrection, +                           bool reverse); + +/** + * 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); + +/** + * Does a simple scan of the area given by the settings. Scanned data + * it put in an allocated area which must be freed by the caller. + * and slope tables, based on the parameter struct. There is no shading + * correction while gamma correction is active. + * @param dev      device to set up + * @param settings settings of the scan + * @param move     flag to enable scanhead to move + * @param forward  flag to tell movement direction + * @param shading  flag to tell if shading correction should be done + * @param data     pointer that will point to the scanned 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* test_identifier); + +/** + * Send the stop scan command + * */ +static void end_scan_impl(Genesys_Device* dev, Genesys_Register_Set* reg, bool check_stop, +                          bool eject); +/** + * writes control data to an area behind the last motor table. + */ +static void write_control(Genesys_Device* dev, const Genesys_Sensor& sensor, int resolution); + + +/** + * initialize scanner's registers at SANE init time + */ +static void gl646_init_regs (Genesys_Device * dev); + +/** + * master motor settings table entry + */ +typedef struct +{ +  /* key */ +    MotorId motor_id; +    unsigned dpi; +  unsigned channels; + +  /* settings */ +    StepType steptype; +    bool fastmod; // fast scanning +    bool fastfed; // fast fed slope tables +  SANE_Int mtrpwm; +    MotorSlope slope1; +    MotorSlope slope2; +  SANE_Int fwdbwd;		/* forward/backward steps */ +} Motor_Master; + +/** + * master motor settings, for a given motor and dpi, + * it gives steps and speed informations + */ +static Motor_Master motor_master[] = { +    /* HP3670 motor settings */ +    {MotorId::HP3670, 50, 3, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(2329, 120, 229), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 75, 3, StepType::FULL, false, true, 1, +     MotorSlope::create_from_steps(3429, 305, 200), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 100, 3, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(2905, 187, 143), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 150, 3, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(3429, 305, 73), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 300, 3, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(1055, 563, 11), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 600, 3, StepType::FULL, false, true, 0, +     MotorSlope::create_from_steps(10687, 5126, 3), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670,1200, 3, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(15937, 6375, 3), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 50, 1, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(2329, 120, 229), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 75, 1, StepType::FULL, false, true, 1, +     MotorSlope::create_from_steps(3429, 305, 200), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 100, 1, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(2905, 187, 143), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 150, 1, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(3429, 305, 73), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 300, 1, StepType::HALF, false, true, 1, +     MotorSlope::create_from_steps(1055, 563, 11), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670, 600, 1, StepType::FULL, false, true, 0, +     MotorSlope::create_from_steps(10687, 5126, 3), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    {MotorId::HP3670,1200, 1, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(15937, 6375, 3), +     MotorSlope::create_from_steps(3399, 337, 192), 192}, + +    /* HP2400/G2410 motor settings base motor dpi = 600 */ +    {MotorId::HP2400, 50, 3, StepType::FULL, false, true, 63, +     MotorSlope::create_from_steps(8736, 601, 120), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 100, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(8736, 601, 120), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 150, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(15902, 902, 67), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 300, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(16703, 2188, 32), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 600, 3, StepType::FULL, false, true, 63, +     MotorSlope::create_from_steps(18761, 18761, 3), +     MotorSlope::create_from_steps(4905, 627, 192), 192}, + +    {MotorId::HP2400,1200, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(43501, 43501, 3), +     MotorSlope::create_from_steps(4905, 627, 192), 192}, + +    {MotorId::HP2400, 50, 1, StepType::FULL, false, true, 63, +     MotorSlope::create_from_steps(8736, 601, 120), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 100, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(8736, 601, 120), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 150, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(15902, 902, 67), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 300, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(16703, 2188, 32), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400, 600, 1, StepType::FULL, false, true, 63, +     MotorSlope::create_from_steps(18761, 18761, 3), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    {MotorId::HP2400,1200, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(43501, 43501, 3), +     MotorSlope::create_from_steps(4905, 337, 192), 192}, + +    /* XP 200 motor settings */ +    {MotorId::XP200, 75, 3, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6000, 2136, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 100, 3, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6000, 2850, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 200, 3, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6999, 5700, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 250, 3, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6999, 6999, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 300, 3, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(13500, 13500, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 600, 3, StepType::HALF, true, true, 0, +     MotorSlope::create_from_steps(31998, 31998, 4), +     MotorSlope::create_from_steps(12000, 1200, 2), 1}, + +    {MotorId::XP200, 75, 1, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6000, 2000, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 100, 1, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6000, 1300, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 200, 1, StepType::HALF, true, true, 0, +     MotorSlope::create_from_steps(6000, 3666, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 300, 1, StepType::HALF, true, false, 0, +     MotorSlope::create_from_steps(6500, 6500, 4), +     MotorSlope::create_from_steps(12000, 1200, 8), 1}, + +    {MotorId::XP200, 600, 1, StepType::HALF, true, true, 0, +     MotorSlope::create_from_steps(24000, 24000, 4), +     MotorSlope::create_from_steps(12000, 1200, 2), 1}, + +    /* HP scanjet 2300c */ +    {MotorId::HP2300, 75, 3, StepType::FULL, false, true, 63, +     MotorSlope::create_from_steps(8139, 560, 120), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 150, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(7903, 543, 67), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 300, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(2175, 1087, 3), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 600, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(8700, 4350, 3), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300,1200, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(17400, 8700, 3), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 75, 1, StepType::FULL, false, true, 63, +     MotorSlope::create_from_steps(8139, 560, 120), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 150, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(7903, 543, 67), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 300, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(2175, 1087, 3), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 600, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(8700, 4350, 3), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300,1200, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(17400, 8700, 3), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    /* non half ccd settings for 300 dpi +    {MotorId::HP2300, 300, 3, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(5386, 2175, 44), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, + +    {MotorId::HP2300, 300, 1, StepType::HALF, false, true, 63, +     MotorSlope::create_from_steps(5386, 2175, 44), +     MotorSlope::create_from_steps(4905, 337, 120), 16}, +    */ + +    /* MD5345/6471 motor settings */ +    /* vfinal=(exposure/(1200/dpi))/step_type */ +    {MotorId::MD_5345, 50, 3, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 250, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 75, 3, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 343, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 100, 3, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 458, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 150, 3, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 687, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 200, 3, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 916, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 300, 3, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 1375, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 400, 3, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(2000, 1833, 32), +     MotorSlope::create_from_steps(2000, 300, 255), 32}, + +    {MotorId::MD_5345, 500, 3, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(2291, 2291, 32), +     MotorSlope::create_from_steps(2000, 300, 255), 32}, + +    {MotorId::MD_5345, 600, 3, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(2750, 2750, 32), +     MotorSlope::create_from_steps(2000, 300, 255), 32}, + +    {MotorId::MD_5345, 1200, 3, StepType::QUARTER, false, true, 0, +     MotorSlope::create_from_steps(2750, 2750, 16), +     MotorSlope::create_from_steps(2000, 300, 255), 146}, + +    {MotorId::MD_5345, 2400, 3, StepType::QUARTER, false, true, 0, +     MotorSlope::create_from_steps(5500, 5500, 16), +     MotorSlope::create_from_steps(2000, 300, 255), 146}, + +    {MotorId::MD_5345, 50, 1, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 250, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 75, 1, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 343, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 100, 1, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 458, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 150, 1, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 687, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 200, 1, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 916, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 300, 1, StepType::HALF, false, true, 2, +     MotorSlope::create_from_steps(2500, 1375, 255), +     MotorSlope::create_from_steps(2000, 300, 255), 64}, + +    {MotorId::MD_5345, 400, 1, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(2000, 1833, 32), +     MotorSlope::create_from_steps(2000, 300, 255), 32}, + +    {MotorId::MD_5345, 500, 1, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(2291, 2291, 32), +     MotorSlope::create_from_steps(2000, 300, 255), 32}, + +    {MotorId::MD_5345, 600, 1, StepType::HALF, false, true, 0, +     MotorSlope::create_from_steps(2750, 2750, 32), +     MotorSlope::create_from_steps(2000, 300, 255), 32}, + +    {MotorId::MD_5345, 1200, 1, StepType::QUARTER, false, true, 0, +     MotorSlope::create_from_steps(2750, 2750, 16), +     MotorSlope::create_from_steps(2000, 300, 255), 146}, + +    {MotorId::MD_5345, 2400, 1, StepType::QUARTER, false, true, 0, +     MotorSlope::create_from_steps(5500, 5500, 16), +     MotorSlope::create_from_steps(2000, 300, 255), 146}, /* 5500 guessed */ +}; + +class CommandSetGl646 : public CommandSet +{ +public: +    ~CommandSetGl646() override = default; + +    bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override; + +    void init(Genesys_Device* dev) const override; + +    void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              Genesys_Register_Set* regs, int* channels, +                              int* total_size) const override; + +    void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs) const override; + +    void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set& regs) const override; + +    void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set* reg, +                                    const ScanSession& session) const override; + +    void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override; +    void set_powersaving(Genesys_Device* dev, int delay) const override; +    void save_power(Genesys_Device* dev, bool enable) const override; + +    void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                    Genesys_Register_Set* regs, bool start_motor) const override; + +    void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override; + +    void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void search_start_position(Genesys_Device* dev) const override; + +    void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set& regs) const override; + +    void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set& regs, int dpi) const override; + +    SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   Genesys_Register_Set& regs) const override; + +    void wait_for_motor_stop(Genesys_Device* dev) const override; + +    void move_back_home(Genesys_Device* dev, bool wait_until_home) const override; + +    void update_hardware_sensors(struct Genesys_Scanner* s) const override; + +    void load_document(Genesys_Device* dev) const override; + +    void detect_document_end(Genesys_Device* dev) const override; + +    void eject_document(Genesys_Device* dev) const override; + +    void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                      bool forward, bool black) const override; + +    void move_to_ta(Genesys_Device* dev) const override; + +    void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data, +                           int size) const override; + +    bool has_send_shading_data() const override +    { +        return false; +    } + +    ScanSession calculate_scan_session(const Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       const Genesys_Settings& settings) const override; + +    void asic_boot(Genesys_Device* dev, bool cold) const override; +}; + +} // namespace gl646 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL646_H diff --git a/backend/genesys/gl646_registers.h b/backend/genesys/gl646_registers.h new file mode 100644 index 0000000..2fe8f19 --- /dev/null +++ b/backend/genesys/gl646_registers.h @@ -0,0 +1,176 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL646_REGISTERS_H +#define BACKEND_GENESYS_GL646_REGISTERS_H + +#include <cstdint> + +namespace genesys { +namespace gl646 { + +using RegAddr = std::uint16_t; +using RegMask = std::uint8_t; +using RegShift = unsigned; + +static constexpr RegAddr REG_0x01 = 0x01; +static constexpr RegMask REG_0x01_CISSET = 0x80; +static constexpr RegMask REG_0x01_DOGENB = 0x40; +static constexpr RegMask REG_0x01_DVDSET = 0x20; +static constexpr RegMask REG_0x01_FASTMOD = 0x10; +static constexpr RegMask REG_0x01_COMPENB = 0x08; +static constexpr RegMask REG_0x01_DRAMSEL = 0x04; +static constexpr RegMask REG_0x01_SHDAREA = 0x02; +static constexpr RegMask REG_0x01_SCAN = 0x01; + +static constexpr RegMask REG_0x02_NOTHOME = 0x80; +static constexpr RegMask REG_0x02_ACDCDIS = 0x40; +static constexpr RegMask REG_0x02_AGOHOME = 0x20; +static constexpr RegMask REG_0x02_MTRPWR = 0x10; +static constexpr RegMask REG_0x02_FASTFED = 0x08; +static constexpr RegMask REG_0x02_MTRREV = 0x04; +static constexpr RegMask REG_0x02_STEPSEL = 0x03; + +static constexpr RegMask REG_0x02_FULLSTEP = 0x00; +static constexpr RegMask REG_0x02_HALFSTEP = 0x01; +static constexpr RegMask REG_0x02_QUATERSTEP = 0x02; + +static constexpr RegMask REG_0x03_TG3 = 0x80; +static constexpr RegMask REG_0x03_AVEENB = 0x40; +static constexpr RegMask REG_0x03_XPASEL = 0x20; +static constexpr RegMask REG_0x03_LAMPPWR = 0x10; +static constexpr RegMask REG_0x03_LAMPDOG = 0x08; +static constexpr RegMask REG_0x03_LAMPTIM = 0x07; + +static constexpr RegMask REG_0x04_LINEART = 0x80; +static constexpr RegMask REG_0x04_BITSET = 0x40; +static constexpr RegMask REG_0x04_ADTYPE = 0x30; +static constexpr RegMask REG_0x04_FILTER = 0x0c; +static constexpr RegMask REG_0x04_FESET = 0x03; + +static constexpr RegMask REG_0x05_DPIHW = 0xc0; +static constexpr RegMask REG_0x05_DPIHW_600 = 0x00; +static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40; +static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80; +static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0; +static constexpr RegMask REG_0x05_GMMTYPE = 0x30; +static constexpr RegMask REG_0x05_GMM14BIT = 0x10; +static constexpr RegMask REG_0x05_GMMENB = 0x08; +static constexpr RegMask REG_0x05_LEDADD = 0x04; +static constexpr RegMask REG_0x05_BASESEL = 0x03; + +static constexpr RegAddr REG_0x06 = 0x06; +static constexpr RegMask REG_0x06_PWRBIT = 0x10; +static constexpr RegMask REG_0x06_GAIN4 = 0x08; +static constexpr RegMask REG_0x06_OPTEST = 0x07; + +static constexpr RegMask REG_0x07_DMASEL = 0x02; +static constexpr RegMask REG_0x07_DMARDWR = 0x01; + +static constexpr RegMask REG_0x16_CTRLHI = 0x80; +static constexpr RegMask REG_0x16_SELINV = 0x40; +static constexpr RegMask REG_0x16_TGINV = 0x20; +static constexpr RegMask REG_0x16_CK1INV = 0x10; +static constexpr RegMask REG_0x16_CK2INV = 0x08; +static constexpr RegMask REG_0x16_CTRLINV = 0x04; +static constexpr RegMask REG_0x16_CKDIS = 0x02; +static constexpr RegMask REG_0x16_CTRLDIS = 0x01; + +static constexpr RegMask REG_0x17_TGMODE = 0xc0; +static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00; +static constexpr RegMask REG_0x17_TGMODE_REF = 0x40; +static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80; +static constexpr RegMask REG_0x17_TGW = 0x3f; + +static constexpr RegMask REG_0x18_CNSET = 0x80; +static constexpr RegMask REG_0x18_DCKSEL = 0x60; +static constexpr RegMask REG_0x18_CKTOGGLE = 0x10; +static constexpr RegMask REG_0x18_CKDELAY = 0x0c; +static constexpr RegMask REG_0x18_CKSEL = 0x03; + +static constexpr RegMask REG_0x1D_CKMANUAL = 0x80; + +static constexpr RegMask REG_0x1E_WDTIME = 0xf0; +static constexpr RegMask REG_0x1E_LINESEL = 0x0f; + +static constexpr RegMask REG_0x41_PWRBIT = 0x80; +static constexpr RegMask REG_0x41_BUFEMPTY = 0x40; +static constexpr RegMask REG_0x41_FEEDFSH = 0x20; +static constexpr RegMask REG_0x41_SCANFSH = 0x10; +static constexpr RegMask REG_0x41_HOMESNR = 0x08; +static constexpr RegMask REG_0x41_LAMPSTS = 0x04; +static constexpr RegMask REG_0x41_FEBUSY = 0x02; +static constexpr RegMask REG_0x41_MOTMFLG = 0x01; + +static constexpr RegMask REG_0x66_LOW_CURRENT = 0x10; + +static constexpr RegMask REG_0x6A_FSTPSEL = 0xc0; +static constexpr RegMask REG_0x6A_FASTPWM = 0x3f; + +static constexpr RegMask REG_0x6C_TGTIME = 0xc0; +static constexpr RegMask REG_0x6C_Z1MOD = 0x38; +static constexpr RegMask REG_0x6C_Z2MOD = 0x07; + +static constexpr RegAddr REG_EXPR = 0x10; +static constexpr RegAddr REG_EXPG = 0x12; +static constexpr RegAddr REG_EXPB = 0x14; +static constexpr RegAddr REG_SCANFED = 0x1f; +static constexpr RegAddr REG_BUFSEL = 0x20; +static constexpr RegAddr REG_LINCNT = 0x25; +static constexpr RegAddr REG_DPISET = 0x2c; +static constexpr RegAddr REG_STRPIXEL = 0x30; +static constexpr RegAddr REG_ENDPIXEL = 0x32; +static constexpr RegAddr REG_DUMMY = 0x34; +static constexpr RegAddr REG_MAXWD = 0x35; +static constexpr RegAddr REG_LPERIOD = 0x38; +static constexpr RegAddr REG_FEEDL = 0x3d; +static constexpr RegAddr REG_VALIDWORD = 0x42; +static constexpr RegAddr REG_FEDCNT = 0x48; +static constexpr RegAddr REG_SCANCNT = 0x4b; +static constexpr RegAddr REG_Z1MOD = 0x60; +static constexpr RegAddr REG_Z2MOD = 0x62; + +} // namespace gl646 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL646_REGISTERS_H diff --git a/backend/genesys/gl841.cpp b/backend/genesys/gl841.cpp new file mode 100644 index 0000000..470f9ba --- /dev/null +++ b/backend/genesys/gl841.cpp @@ -0,0 +1,4010 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003 Oliver Rauch +   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de> +   Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr> +   Copyright (C) 2005 Philipp Schmid <philipp8288@web.de> +   Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> +   Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com> +   Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de> +                 for Plustek Opticbook 3600 support + + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, 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 "gl841.h" +#include "gl841_registers.h" +#include "test_settings.h" + +#include <vector> + +namespace genesys { +namespace gl841 { + + +static int gl841_exposure_time(Genesys_Device *dev, const Genesys_Sensor& sensor, +                               float slope_dpi, +                               StepType scan_step_type, +                               int start, +                               int used_pixels); + +/** copy sensor specific settings */ +/* *dev  : device infos +   *regs : registers to be set +   extended : do extended set up +   ccd_size_divisor: set up for half ccd resolution +   all registers 08-0B, 10-1D, 52-59 are set up. They shouldn't +   appear anywhere else but in register_ini + +Responsible for signals to CCD/CIS: +  CCD_CK1X (CK1INV(0x16),CKDIS(0x16),CKTOGGLE(0x18),CKDELAY(0x18),MANUAL1(0x1A),CK1MTGL(0x1C),CK1LOW(0x1D),CK1MAP(0x74,0x75,0x76),CK1NEG(0x7D)) +  CCD_CK2X (CK2INV(0x16),CKDIS(0x16),CKTOGGLE(0x18),CKDELAY(0x18),MANUAL1(0x1A),CK1LOW(0x1D),CK1NEG(0x7D)) +  CCD_CK3X (MANUAL3(0x1A),CK3INV(0x1A),CK3MTGL(0x1C),CK3LOW(0x1D),CK3MAP(0x77,0x78,0x79),CK3NEG(0x7D)) +  CCD_CK4X (MANUAL3(0x1A),CK4INV(0x1A),CK4MTGL(0x1C),CK4LOW(0x1D),CK4MAP(0x7A,0x7B,0x7C),CK4NEG(0x7D)) +  CCD_CPX  (CTRLHI(0x16),CTRLINV(0x16),CTRLDIS(0x16),CPH(0x72),CPL(0x73),CPNEG(0x7D)) +  CCD_RSX  (CTRLHI(0x16),CTRLINV(0x16),CTRLDIS(0x16),RSH(0x70),RSL(0x71),RSNEG(0x7D)) +  CCD_TGX  (TGINV(0x16),TGMODE(0x17),TGW(0x17),EXPR(0x10,0x11),TGSHLD(0x1D)) +  CCD_TGG  (TGINV(0x16),TGMODE(0x17),TGW(0x17),EXPG(0x12,0x13),TGSHLD(0x1D)) +  CCD_TGB  (TGINV(0x16),TGMODE(0x17),TGW(0x17),EXPB(0x14,0x15),TGSHLD(0x1D)) +  LAMP_SW  (EXPR(0x10,0x11),XPA_SEL(0x03),LAMP_PWR(0x03),LAMPTIM(0x03),MTLLAMP(0x04),LAMPPWM(0x29)) +  XPA_SW   (EXPG(0x12,0x13),XPA_SEL(0x03),LAMP_PWR(0x03),LAMPTIM(0x03),MTLLAMP(0x04),LAMPPWM(0x29)) +  LAMP_B   (EXPB(0x14,0x15),LAMP_PWR(0x03)) + +other registers: +  CISSET(0x01),CNSET(0x18),DCKSEL(0x18),SCANMOD(0x18),EXPDMY(0x19),LINECLP(0x1A),CKAREA(0x1C),TGTIME(0x1C),LINESEL(0x1E),DUMMY(0x34) + +Responsible for signals to AFE: +  VSMP  (VSMP(0x58),VSMPW(0x58)) +  BSMP  (BSMP(0x59),BSMPW(0x59)) + +other register settings depending on this: +  RHI(0x52),RLOW(0x53),GHI(0x54),GLOW(0x55),BHI(0x56),BLOW(0x57), + +*/ +static void sanei_gl841_setup_sensor(Genesys_Device * dev, const Genesys_Sensor& sensor, +                                     Genesys_Register_Set * regs, +                                     bool extended, unsigned ccd_size_divisor) +{ +    DBG(DBG_proc, "%s\n", __func__); + +    // that one is tricky at least +    for (uint16_t addr = 0x08; addr <= 0x0b; ++addr) { +        regs->set8(0x70 + addr - 0x08, sensor.custom_regs.get_value(addr)); +    } + +    // ignore registers in range [0x10..0x16) +    for (uint16_t addr = 0x16; addr < 0x1e; ++addr) { +        regs->set8(addr, sensor.custom_regs.get_value(addr)); +    } + +    // ignore registers in range [0x5b..0x5e] +    for (uint16_t addr = 0x52; addr < 0x52 + 9; ++addr) { +        regs->set8(addr, sensor.custom_regs.get_value(addr)); +    } + +  /* don't go any further if no extended setup */ +  if (!extended) +    return; + +  /* todo : add more CCD types if needed */ +  /* we might want to expand the Sensor struct to have these +     2 kind of settings */ +    if (dev->model->sensor_id == SensorId::CCD_5345) { +        if (ccd_size_divisor > 1) { +          GenesysRegister* r; +	  /* settings for CCD used at half is max resolution */ +	  r = sanei_genesys_get_address (regs, 0x70); +	  r->value = 0x00; +	  r = sanei_genesys_get_address (regs, 0x71); +	  r->value = 0x05; +	  r = sanei_genesys_get_address (regs, 0x72); +	  r->value = 0x06; +	  r = sanei_genesys_get_address (regs, 0x73); +	  r->value = 0x08; +	  r = sanei_genesys_get_address (regs, 0x18); +	  r->value = 0x28; +	  r = sanei_genesys_get_address (regs, 0x58); +	  r->value = 0x80 | (r->value & 0x03);	/* VSMP=16 */ +	} +      else +	{ +          GenesysRegister* r; +	  /* swap latch times */ +	  r = sanei_genesys_get_address (regs, 0x18); +	  r->value = 0x30; +          regs->set8(0x52, sensor.custom_regs.get_value(0x55)); +          regs->set8(0x53, sensor.custom_regs.get_value(0x56)); +          regs->set8(0x54, sensor.custom_regs.get_value(0x57)); +          regs->set8(0x55, sensor.custom_regs.get_value(0x52)); +          regs->set8(0x56, sensor.custom_regs.get_value(0x53)); +          regs->set8(0x57, sensor.custom_regs.get_value(0x54)); +	  r = sanei_genesys_get_address (regs, 0x58); +	  r->value = 0x20 | (r->value & 0x03);	/* VSMP=4 */ +	} +      return; +    } + +    if (dev->model->sensor_id == SensorId::CCD_HP2300) { +      /* settings for CCD used at half is max resolution */ +      GenesysRegister* r; +        if (ccd_size_divisor > 1) { +	  r = sanei_genesys_get_address (regs, 0x70); +	  r->value = 0x16; +	  r = sanei_genesys_get_address (regs, 0x71); +	  r->value = 0x00; +	  r = sanei_genesys_get_address (regs, 0x72); +	  r->value = 0x01; +	  r = sanei_genesys_get_address (regs, 0x73); +	  r->value = 0x03; +	  /* manual clock programming */ +	  r = sanei_genesys_get_address (regs, 0x1d); +	  r->value |= 0x80; +	} +      else +	{ +	  r = sanei_genesys_get_address (regs, 0x70); +	  r->value = 1; +	  r = sanei_genesys_get_address (regs, 0x71); +	  r->value = 3; +	  r = sanei_genesys_get_address (regs, 0x72); +	  r->value = 4; +	  r = sanei_genesys_get_address (regs, 0x73); +	  r->value = 6; +	} +      r = sanei_genesys_get_address (regs, 0x58); +      r->value = 0x80 | (r->value & 0x03);	/* VSMP=16 */ +      return; +    } +} + +/* + * Set all registers LiDE 80 to default values + * (function called only once at the beginning) + * we are doing a special case to ease development + */ +static void +gl841_init_lide80 (Genesys_Device * dev) +{ +    dev->reg.init_reg(0x01, 0x82); // 0x02 = SHDAREA  and no CISSET ! +    dev->reg.init_reg(0x02, 0x10); +    dev->reg.init_reg(0x03, 0x50); +    dev->reg.init_reg(0x04, 0x02); +    dev->reg.init_reg(0x05, 0x4c);  // 1200 DPI +    dev->reg.init_reg(0x06, 0x38);  // 0x38 scanmod=1, pwrbit, GAIN4 +    dev->reg.init_reg(0x07, 0x00); +    dev->reg.init_reg(0x08, 0x00); +    dev->reg.init_reg(0x09, 0x11); +    dev->reg.init_reg(0x0a, 0x00); + +    dev->reg.init_reg(0x10, 0x40); +    dev->reg.init_reg(0x11, 0x00); +    dev->reg.init_reg(0x12, 0x40); +    dev->reg.init_reg(0x13, 0x00); +    dev->reg.init_reg(0x14, 0x40); +    dev->reg.init_reg(0x15, 0x00); +    dev->reg.init_reg(0x16, 0x00); +    dev->reg.init_reg(0x17, 0x01); +    dev->reg.init_reg(0x18, 0x00); +    dev->reg.init_reg(0x19, 0x06); +    dev->reg.init_reg(0x1a, 0x00); +    dev->reg.init_reg(0x1b, 0x00); +    dev->reg.init_reg(0x1c, 0x00); +    dev->reg.init_reg(0x1d, 0x04); +    dev->reg.init_reg(0x1e, 0x10); +    dev->reg.init_reg(0x1f, 0x04); +    dev->reg.init_reg(0x20, 0x02); +    dev->reg.init_reg(0x21, 0x10); +    dev->reg.init_reg(0x22, 0x20); +    dev->reg.init_reg(0x23, 0x20); +    dev->reg.init_reg(0x24, 0x10); +    dev->reg.init_reg(0x25, 0x00); +    dev->reg.init_reg(0x26, 0x00); +    dev->reg.init_reg(0x27, 0x00); + +    dev->reg.init_reg(0x29, 0xff); + +    const auto& sensor = sanei_genesys_find_sensor_any(dev); +    dev->reg.init_reg(0x2c, sensor.optical_res>>8); +    dev->reg.init_reg(0x2d, sensor.optical_res & 0xff); +    dev->reg.init_reg(0x2e, 0x80); +    dev->reg.init_reg(0x2f, 0x80); +    dev->reg.init_reg(0x30, 0x00); +    dev->reg.init_reg(0x31, 0x10); +    dev->reg.init_reg(0x32, 0x15); +    dev->reg.init_reg(0x33, 0x0e); +    dev->reg.init_reg(0x34, 0x40); +    dev->reg.init_reg(0x35, 0x00); +    dev->reg.init_reg(0x36, 0x2a); +    dev->reg.init_reg(0x37, 0x30); +    dev->reg.init_reg(0x38, 0x2a); +    dev->reg.init_reg(0x39, 0xf8); + +    dev->reg.init_reg(0x3d, 0x00); +    dev->reg.init_reg(0x3e, 0x00); +    dev->reg.init_reg(0x3f, 0x00); + +    dev->reg.init_reg(0x52, 0x03); +    dev->reg.init_reg(0x53, 0x07); +    dev->reg.init_reg(0x54, 0x00); +    dev->reg.init_reg(0x55, 0x00); +    dev->reg.init_reg(0x56, 0x00); +    dev->reg.init_reg(0x57, 0x00); +    dev->reg.init_reg(0x58, 0x29); +    dev->reg.init_reg(0x59, 0x69); +    dev->reg.init_reg(0x5a, 0x55); + +    dev->reg.init_reg(0x5d, 0x20); +    dev->reg.init_reg(0x5e, 0x41); +    dev->reg.init_reg(0x5f, 0x40); +    dev->reg.init_reg(0x60, 0x00); +    dev->reg.init_reg(0x61, 0x00); +    dev->reg.init_reg(0x62, 0x00); +    dev->reg.init_reg(0x63, 0x00); +    dev->reg.init_reg(0x64, 0x00); +    dev->reg.init_reg(0x65, 0x00); +    dev->reg.init_reg(0x66, 0x00); +    dev->reg.init_reg(0x67, 0x40); +    dev->reg.init_reg(0x68, 0x40); +    dev->reg.init_reg(0x69, 0x20); +    dev->reg.init_reg(0x6a, 0x20); +    dev->reg.init_reg(0x6c, 0x00); +    dev->reg.init_reg(0x6d, 0x00); +    dev->reg.init_reg(0x6e, 0x00); +    dev->reg.init_reg(0x6f, 0x00); +    dev->reg.init_reg(0x70, 0x00); +    dev->reg.init_reg(0x71, 0x05); +    dev->reg.init_reg(0x72, 0x07); +    dev->reg.init_reg(0x73, 0x09); +    dev->reg.init_reg(0x74, 0x00); +    dev->reg.init_reg(0x75, 0x01); +    dev->reg.init_reg(0x76, 0xff); +    dev->reg.init_reg(0x77, 0x00); +    dev->reg.init_reg(0x78, 0x0f); +    dev->reg.init_reg(0x79, 0xf0); +    dev->reg.init_reg(0x7a, 0xf0); +    dev->reg.init_reg(0x7b, 0x00); +    dev->reg.init_reg(0x7c, 0x1e); +    dev->reg.init_reg(0x7d, 0x11); +    dev->reg.init_reg(0x7e, 0x00); +    dev->reg.init_reg(0x7f, 0x50); +    dev->reg.init_reg(0x80, 0x00); +    dev->reg.init_reg(0x81, 0x00); +    dev->reg.init_reg(0x82, 0x0f); +    dev->reg.init_reg(0x83, 0x00); +    dev->reg.init_reg(0x84, 0x0e); +    dev->reg.init_reg(0x85, 0x00); +    dev->reg.init_reg(0x86, 0x0d); +    dev->reg.init_reg(0x87, 0x02); +    dev->reg.init_reg(0x88, 0x00); +    dev->reg.init_reg(0x89, 0x00); + +    for (const auto& reg : dev->gpo.regs) { +        dev->reg.set8(reg.address, reg.value); +    } + +    // specific scanner settings, clock and gpio first +    // FIXME: remove the dummy reads as we don't use the values +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6B); +    } +    dev->interface->write_register(REG_0x6B, 0x0c); +    dev->interface->write_register(0x06, 0x10); +    dev->interface->write_register(REG_0x6E, 0x6d); +    dev->interface->write_register(REG_0x6F, 0x80); +    dev->interface->write_register(REG_0x6B, 0x0e); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6C); +    } +    dev->interface->write_register(REG_0x6C, 0x00); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6D); +    } +    dev->interface->write_register(REG_0x6D, 0x8f); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6B); +    } +    dev->interface->write_register(REG_0x6B, 0x0e); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6B); +    } +    dev->interface->write_register(REG_0x6B, 0x0e); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6B); +    } +    dev->interface->write_register(REG_0x6B, 0x0a); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6B); +    } +    dev->interface->write_register(REG_0x6B, 0x02); +    if (!is_testing_mode()) { +        dev->interface->read_register(REG_0x6B); +    } +    dev->interface->write_register(REG_0x6B, 0x06); + +    dev->interface->write_0x8c(0x10, 0x94); +    dev->interface->write_register(0x09, 0x10); + +  // FIXME: the following code originally changed 0x6b, but due to bug the 0x6c register was +  // effectively changed. The current behavior matches the old code, but should probably be fixed. +    dev->reg.find_reg(0x6c).value |= REG_0x6B_GPO18; +    dev->reg.find_reg(0x6c).value &= ~REG_0x6B_GPO17; + +    sanei_gl841_setup_sensor(dev, sensor, &dev->reg, 0, 1); +} + +/* + * Set all registers to default values + * (function called only once at the beginning) + */ +static void +gl841_init_registers (Genesys_Device * dev) +{ +  int addr; + +  DBG(DBG_proc, "%s\n", __func__); + +  dev->reg.clear(); +    if (dev->model->model_id == ModelId::CANON_LIDE_80) { +      gl841_init_lide80(dev); +      return ; +    } + +    for (addr = 1; addr <= 0x0a; addr++) { +        dev->reg.init_reg(addr, 0); +    } +    for (addr = 0x10; addr <= 0x27; addr++) { +        dev->reg.init_reg(addr, 0); +    } +    dev->reg.init_reg(0x29, 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 <= 0x5a; addr++) +        dev->reg.init_reg(addr, 0); +    for (addr = 0x5d; addr <= 0x87; addr++) +        dev->reg.init_reg(addr, 0); + + +    dev->reg.find_reg(0x01).value = 0x20;	/* (enable shading), CCD, color, 1M */ +    if (dev->model->is_cis) { +        dev->reg.find_reg(0x01).value |= REG_0x01_CISSET; +    } else { +        dev->reg.find_reg(0x01).value &= ~REG_0x01_CISSET; +    } + +    dev->reg.find_reg(0x02).value = 0x30 /*0x38 */ ;	/* auto home, one-table-move, full step */ +    dev->reg.find_reg(0x02).value |= REG_0x02_AGOHOME; +    sanei_genesys_set_motor_power(dev->reg, true); +    dev->reg.find_reg(0x02).value |= REG_0x02_FASTFED; + +    dev->reg.find_reg(0x03).value = 0x1f /*0x17 */ ;	/* lamp on */ +    dev->reg.find_reg(0x03).value |= REG_0x03_AVEENB; + +    if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) { +        // AD front end +        dev->reg.find_reg(0x04).value  = (2 << REG_0x04S_AFEMOD) | 0x02; +    } +  else /* Wolfson front end */ +    { +        dev->reg.find_reg(0x04).value |= 1 << REG_0x04S_AFEMOD; +    } + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +  dev->reg.find_reg(0x05).value = 0x00;	/* disable gamma, 24 clocks/pixel */ + +    unsigned dpihw = 0; +    if (sensor.sensor_pixels < 0x1500) { +        dpihw = 600; +    } else if (sensor.sensor_pixels < 0x2a80) { +        dpihw = 1200; +    } else if (sensor.sensor_pixels < 0x5400) { +        dpihw = 2400; +    } else { +        throw SaneException("Cannot handle sensor pixel count %d", sensor.sensor_pixels); +    } +    sanei_genesys_set_dpihw(dev->reg, sensor, dpihw); + +    dev->reg.find_reg(0x06).value |= REG_0x06_PWRBIT; +    dev->reg.find_reg(0x06).value |= REG_0x06_GAIN4; + +  /* XP300 CCD needs different clock and clock/pixels values */ +    if (dev->model->sensor_id != SensorId::CCD_XP300 && +        dev->model->sensor_id != SensorId::CCD_DP685 && +        dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICPRO_3600) +    { +        dev->reg.find_reg(0x06).value |= 0 << REG_0x06S_SCANMOD; +        dev->reg.find_reg(0x09).value |= 1 << REG_0x09S_CLKSET; +    } +  else +    { +        dev->reg.find_reg(0x06).value |= 0x05 << REG_0x06S_SCANMOD; /* 15 clocks/pixel */ +      dev->reg.find_reg(0x09).value = 0; /* 24 MHz CLKSET */ +    } + +  dev->reg.find_reg(0x1e).value = 0xf0;	/* watch-dog time */ + +    dev->reg.find_reg(0x17).value |= 1 << REG_0x17S_TGW; + +  dev->reg.find_reg(0x19).value = 0x50; + +    dev->reg.find_reg(0x1d).value |= 1 << REG_0x1DS_TGSHLD; + +    dev->reg.find_reg(0x1e).value |= 1 << REG_0x1ES_WDTIME; + +/*SCANFED*/ +  dev->reg.find_reg(0x1f).value = 0x01; + +/*BUFSEL*/ +  dev->reg.find_reg(0x20).value = 0x20; + +/*LAMPPWM*/ +  dev->reg.find_reg(0x29).value = 0xff; + +/*BWHI*/ +  dev->reg.find_reg(0x2e).value = 0x80; + +/*BWLOW*/ +  dev->reg.find_reg(0x2f).value = 0x80; + +/*LPERIOD*/ +  dev->reg.find_reg(0x38).value = 0x4f; +  dev->reg.find_reg(0x39).value = 0xc1; + +/*VSMPW*/ +    dev->reg.find_reg(0x58).value |= 3 << REG_0x58S_VSMPW; + +/*BSMPW*/ +    dev->reg.find_reg(0x59).value |= 3 << REG_0x59S_BSMPW; + +/*RLCSEL*/ +    dev->reg.find_reg(0x5a).value |= REG_0x5A_RLCSEL; + +/*STOPTIM*/ +    dev->reg.find_reg(0x5e).value |= 0x2 << REG_0x5ES_STOPTIM; + +    sanei_gl841_setup_sensor(dev, sensor, &dev->reg, 0, 1); + +    // set up GPIO +    for (const auto& reg : dev->gpo.regs) { +        dev->reg.set8(reg.address, reg.value); +    } + +  /* TODO there is a switch calling to be written here */ +    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) { +        dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO18; +        dev->reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17; +    } + +    if (dev->model->gpio_id == GpioId::XP300) { +        dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17; +    } + +    if (dev->model->gpio_id == GpioId::DP685) { +      /* REG_0x6B_GPO18 lights on green led */ +        dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17|REG_0x6B_GPO18; +    } + +  DBG(DBG_proc, "%s complete\n", __func__); +} + +// Send slope table for motor movement slope_table in machine byte order +static void gl841_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps) +{ +    DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps); +  int dpihw; +  int start_address; +  char msg[4000]; +/*#ifdef WORDS_BIGENDIAN*/ +  int i; +/*#endif*/ + +  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 = 0x20000; +    else { +        throw SaneException("Unexpected dpihw"); +    } + +  std::vector<uint8_t> table(steps * 2); +  for(i = 0; i < steps; i++) { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +  } + +  if (DBG_LEVEL >= DBG_io) +    { +        std::sprintf(msg, "write slope %d (%d)=", table_nr, steps); +        for (i = 0; i < steps; i++) { +            std::sprintf (msg+strlen(msg), ",%d", slope_table[i]); +	} +      DBG(DBG_io, "%s: %s\n", __func__, msg); +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } +    dev->interface->write_buffer(0x3c, start_address + table_nr * 0x200, table.data(), steps * 2); +} + +static void gl841_set_lide80_fe(Genesys_Device* dev, uint8_t set) +{ +    DBG_HELPER(dbg); + +  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(0x03, dev->frontend.regs.get_value(0x01)); +        dev->interface->write_fe_register(0x06, dev->frontend.regs.get_value(0x02)); +    } + +  if (set == AFE_SET) +    { +        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00)); +        dev->interface->write_fe_register(0x06, dev->frontend.regs.get_value(0x20)); +        dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x28)); +    } +} + +// Set values of Analog Device type frontend +static void gl841_set_ad_fe(Genesys_Device* dev, uint8_t set) +{ +    DBG_HELPER(dbg); +  int i; + +    if (dev->model->adc_id==AdcId::CANON_LIDE_80) { +        gl841_set_lide80_fe(dev, set); +        return; +    } + +  if (set == AFE_INIT) +    { +        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)); + +        for (i = 0; i < 6; i++) { +            dev->interface->write_fe_register(0x02 + i, 0x00); +        } +    } +  if (set == AFE_SET) +    { +        // write them to analog frontend +        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00)); + +        dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01)); + +        // Write fe 0x02 (red gain) +        dev->interface->write_fe_register(0x02, dev->frontend.get_gain(0)); + +        // Write fe 0x03 (green gain) +        dev->interface->write_fe_register(0x03, dev->frontend.get_gain(1)); + +        // Write fe 0x04 (blue gain) +        dev->interface->write_fe_register(0x04, dev->frontend.get_gain(2)); + +        // Write fe 0x05 (red offset) +        dev->interface->write_fe_register(0x05, dev->frontend.get_offset(0)); + +        // Write fe 0x06 (green offset) +        dev->interface->write_fe_register(0x06, dev->frontend.get_offset(1)); + +        // Write fe 0x07 (blue offset) +        dev->interface->write_fe_register(0x07, dev->frontend.get_offset(2)); +          } +} + +// Set values of analog frontend +void CommandSetGl841::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" : +                               set == AFE_SET ? "set" : +                               set == AFE_POWER_SAVE ? "powersave" : "huh?"); +    (void) sensor; + +  /* Analog Device type frontend */ +    uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET; + +    if (frontend_type == 0x02) { +        gl841_set_ad_fe(dev, set); +        return; +    } + +    if (frontend_type != 0x00) { +        throw SaneException("unsupported frontend type %d", frontend_type); +    } + +  if (set == AFE_INIT) +    { +        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); +      DBG(DBG_proc, "%s(): frontend reset complete\n", __func__); +    } + + +  if (set == AFE_POWER_SAVE) +    { +        dev->interface->write_fe_register(0x01, 0x02); +        return; +    } + +  /* todo :  base this test on cfg reg3 or a CCD family flag to be created */ +  /*if (dev->model->ccd_type!=SensorId::CCD_HP2300 && dev->model->ccd_type!=SensorId::CCD_HP2400) */ +  { +        dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00)); +        dev->interface->write_fe_register(0x02, dev->frontend.regs.get_value(0x02)); +  } + +    dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01)); +    dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x03)); +    dev->interface->write_fe_register(0x06, dev->frontend.reg2[0]); +    dev->interface->write_fe_register(0x08, dev->frontend.reg2[1]); +    dev->interface->write_fe_register(0x09, dev->frontend.reg2[2]); + +    for (unsigned i = 0; i < 3; i++) { +        dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i)); +        dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i)); +        dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i)); +    } +} + +enum MotorAction { +    MOTOR_ACTION_FEED = 1, +    MOTOR_ACTION_GO_HOME = 2, +    MOTOR_ACTION_HOME_FREE = 3 +}; + +// @brief turn off motor +static void gl841_init_motor_regs_off(Genesys_Register_Set* reg, unsigned int scan_lines) +{ +    DBG_HELPER_ARGS(dbg, "scan_lines=%d", scan_lines); +    unsigned int feedl; +    GenesysRegister* r; + +    feedl = 2; + +    r = sanei_genesys_get_address (reg, 0x3d); +    r->value = (feedl >> 16) & 0xf; +    r = sanei_genesys_get_address (reg, 0x3e); +    r->value = (feedl >> 8) & 0xff; +    r = sanei_genesys_get_address (reg, 0x3f); +    r->value = feedl & 0xff; +    r = sanei_genesys_get_address (reg, 0x5e); +    r->value &= ~0xe0; + +    r = sanei_genesys_get_address (reg, 0x25); +    r->value = (scan_lines >> 16) & 0xf; +    r = sanei_genesys_get_address (reg, 0x26); +    r->value = (scan_lines >> 8) & 0xff; +    r = sanei_genesys_get_address (reg, 0x27); +    r->value = scan_lines & 0xff; + +    r = sanei_genesys_get_address (reg, 0x02); +    r->value &= ~0x01; /*LONGCURV OFF*/ +    r->value &= ~0x80; /*NOT_HOME OFF*/ + +    r->value &= ~0x10; + +    r->value &= ~0x06; + +    r->value &= ~0x08; + +    r->value &= ~0x20; + +    r->value &= ~0x40; + +    r = sanei_genesys_get_address (reg, 0x67); +    r->value = 0x3f; + +    r = sanei_genesys_get_address (reg, 0x68); +    r->value = 0x3f; + +    r = sanei_genesys_get_address(reg, REG_STEPNO); +    r->value = 0; + +    r = sanei_genesys_get_address(reg, REG_FASTNO); +    r->value = 0; + +    r = sanei_genesys_get_address (reg, 0x69); +    r->value = 0; + +    r = sanei_genesys_get_address (reg, 0x6a); +    r->value = 0; + +    r = sanei_genesys_get_address (reg, 0x5f); +    r->value = 0; +} + +/** @brief write motor table frequency + * Write motor frequency data table. + * @param dev device to set up motor + * @param ydpi motor target resolution + */ +static void gl841_write_freq(Genesys_Device* dev, unsigned int ydpi) +{ +    DBG_HELPER(dbg); +/**< fast table */ +uint8_t tdefault[] = {0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76}; +uint8_t t1200[]    = {0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20}; +uint8_t t300[]     = {0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60}; +uint8_t t150[]     = {0x0c,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0x40,0x14,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x0c,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0x11,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x0c,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0x40,0xd4,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x0c,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0x11,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60}; + +uint8_t *table; + +    if(dev->model->motor_id == MotorId::CANON_LIDE_80) { +      switch(ydpi) +        { +          case 3600: +          case 1200: +            table=t1200; +            break; +          case 900: +          case 300: +            table=t300; +            break; +          case 450: +          case 150: +            table=t150; +            break; +          default: +            table=tdefault; +        } +        dev->interface->write_register(0x66, 0x00); +        dev->interface->write_gamma(0x28, 0xc000, table, 128, +                                    ScannerInterface::FLAG_SWAP_REGISTERS); +        dev->interface->write_register(0x5b, 0x00); +        dev->interface->write_register(0x5c, 0x00); +    } +} + + +static void gl841_init_motor_regs(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                  Genesys_Register_Set* reg, unsigned int feed_steps,/*1/base_ydpi*/ +                                  /*maybe float for half/quarter step resolution?*/ +                                  unsigned int action, MotorFlag flags) +{ +    DBG_HELPER_ARGS(dbg, "feed_steps=%d, action=%d, flags=%x", feed_steps, action, +                    static_cast<unsigned>(flags)); +    unsigned int fast_exposure = 0; +    int use_fast_fed = 0; +    unsigned int feedl; +    GenesysRegister* r; +/*number of scan lines to add in a scan_lines line*/ + +    { +        std::vector<uint16_t> table; +        table.resize(256, 0xffff); + +        gl841_send_slope_table(dev, 0, table, 256); +        gl841_send_slope_table(dev, 1, table, 256); +        gl841_send_slope_table(dev, 2, table, 256); +        gl841_send_slope_table(dev, 3, table, 256); +        gl841_send_slope_table(dev, 4, table, 256); +    } + +    gl841_write_freq(dev, dev->motor.base_ydpi / 4); + +    if (action == MOTOR_ACTION_FEED || action == MOTOR_ACTION_GO_HOME) { +        /* FEED and GO_HOME can use fastest slopes available */ +        fast_exposure = gl841_exposure_time(dev, sensor, +                                            dev->motor.base_ydpi / 4, +                                            StepType::FULL, +                                            0, +                                            0); +        DBG(DBG_info, "%s : fast_exposure=%d pixels\n", __func__, fast_exposure); +      } + +    if (action == MOTOR_ACTION_HOME_FREE) { +/* HOME_FREE must be able to stop in one step, so do not try to get faster */ +        fast_exposure = dev->motor.get_slope(StepType::FULL).max_speed_w; +    } + +    auto fast_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor, +                                                        StepType::FULL, fast_exposure, +                                                        dev->motor.base_ydpi / 4); + +    feedl = feed_steps - fast_table.steps_count * 2; +    use_fast_fed = 1; + +/* all needed slopes available. we did even decide which mode to use. +   what next? +   - transfer slopes +SCAN: +flags \ use_fast_fed    ! 0         1 +------------------------\-------------------- +                      0 ! 0,1,2     0,1,2,3 +MotorFlag::AUTO_GO_HOME ! 0,1,2,4   0,1,2,3,4 +OFF:       none +FEED:      3 +GO_HOME:   3 +HOME_FREE: 3 +   - setup registers +     * slope specific registers (already done) +     * DECSEL for HOME_FREE/GO_HOME/SCAN +     * FEEDL +     * MTRREV +     * MTRPWR +     * FASTFED +     * STEPSEL +     * MTRPWM +     * FSTPSEL +     * FASTPWM +     * HOMENEG +     * BWDSTEP +     * FWDSTEP +     * Z1 +     * Z2 + */ + +    r = sanei_genesys_get_address(reg, 0x3d); +    r->value = (feedl >> 16) & 0xf; +    r = sanei_genesys_get_address(reg, 0x3e); +    r->value = (feedl >> 8) & 0xff; +    r = sanei_genesys_get_address(reg, 0x3f); +    r->value = feedl & 0xff; +    r = sanei_genesys_get_address(reg, 0x5e); +    r->value &= ~0xe0; + +    r = sanei_genesys_get_address(reg, 0x25); +    r->value = 0; +    r = sanei_genesys_get_address(reg, 0x26); +    r->value = 0; +    r = sanei_genesys_get_address(reg, 0x27); +    r->value = 0; + +    r = sanei_genesys_get_address(reg, 0x02); +    r->value &= ~0x01; /*LONGCURV OFF*/ +    r->value &= ~0x80; /*NOT_HOME OFF*/ + +    r->value |= 0x10; + +    if (action == MOTOR_ACTION_GO_HOME) +	r->value |= 0x06; +    else +	r->value &= ~0x06; + +    if (use_fast_fed) +	r->value |= 0x08; +    else +	r->value &= ~0x08; + +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        r->value |= 0x20; +    } else { +        r->value &= ~0x20; +    } + +    r->value &= ~0x40; + +    if (has_flag(flags, MotorFlag::REVERSE)) { +        r->value |= REG_0x02_MTRREV; +    } + +    gl841_send_slope_table(dev, 3, fast_table.table, 256); + +    r = sanei_genesys_get_address(reg, 0x67); +    r->value = 0x3f; + +    r = sanei_genesys_get_address(reg, 0x68); +    r->value = 0x3f; + +    r = sanei_genesys_get_address(reg, REG_STEPNO); +    r->value = 0; + +    r = sanei_genesys_get_address(reg, REG_FASTNO); +    r->value = 0; + +    r = sanei_genesys_get_address(reg, 0x69); +    r->value = 0; + +    r = sanei_genesys_get_address(reg, 0x6a); +    r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1); + +    r = sanei_genesys_get_address(reg, 0x5f); +    r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1); +} + +static void gl841_init_motor_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                       Genesys_Register_Set* reg, +                                       unsigned int scan_exposure_time,/*pixel*/ +                                       unsigned scan_yres, // dpi, motor resolution +                                       StepType scan_step_type, +                                       unsigned int scan_lines,/*lines, scan resolution*/ +                                       unsigned int scan_dummy, +                                       // number of scan lines to add in a scan_lines line +                                       unsigned int feed_steps,/*1/base_ydpi*/ +                                       // maybe float for half/quarter step resolution? +                                       MotorFlag flags) +{ +    DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, scan_yres=%d, scan_step_type=%d, scan_lines=%d," +                         " scan_dummy=%d, feed_steps=%d, flags=%x", +                    scan_exposure_time, scan_yres, static_cast<unsigned>(scan_step_type), +                    scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags)); +    unsigned int fast_exposure; +    int use_fast_fed = 0; +    unsigned int fast_time; +    unsigned int slow_time; +    unsigned int feedl; +    GenesysRegister* r; +    unsigned int min_restep = 0x20; +    uint32_t z1, z2; + +    fast_exposure = gl841_exposure_time(dev, sensor, +                                        dev->motor.base_ydpi / 4, +                                        StepType::FULL, +                                        0, +                                        0); + +    DBG(DBG_info, "%s : fast_exposure=%d pixels\n", __func__, fast_exposure); + +    { +        std::vector<uint16_t> table; +        table.resize(256, 0xffff); + +        gl841_send_slope_table(dev, 0, table, 256); +        gl841_send_slope_table(dev, 1, table, 256); +        gl841_send_slope_table(dev, 2, table, 256); +        gl841_send_slope_table(dev, 3, table, 256); +        gl841_send_slope_table(dev, 4, table, 256); +    } + + +    /* motor frequency table */ +    gl841_write_freq(dev, scan_yres); + +/* +  we calculate both tables for SCAN. the fast slope step count depends on +  how many steps we need for slow acceleration and how much steps we are +  allowed to use. + */ + +    auto slow_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor, +                                                        scan_step_type, scan_exposure_time, +                                                        scan_yres); + +    auto back_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor, +                                                        scan_step_type, 0, scan_yres); + +    if (feed_steps < (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) { +	/*TODO: what should we do here?? go back to exposure calculation?*/ +        feed_steps = slow_table.steps_count >> static_cast<unsigned>(scan_step_type); +    } + +    auto fast_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor, +                                                        StepType::FULL, fast_exposure, +                                                        dev->motor.base_ydpi / 4); + +    unsigned max_fast_slope_steps_count = 1; +    if (feed_steps > (slow_table.steps_count >> static_cast<unsigned>(scan_step_type)) + 2) { +        max_fast_slope_steps_count = (feed_steps - +            (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) / 2; +    } + +    if (fast_table.steps_count > max_fast_slope_steps_count) { +        fast_table.slice_steps(max_fast_slope_steps_count); +    } + +    /* fast fed special cases handling */ +    if (dev->model->gpio_id == GpioId::XP300 +     || dev->model->gpio_id == GpioId::DP685) +      { +	/* quirk: looks like at least this scanner is unable to use +	   2-feed mode */ +	use_fast_fed = 0; +      } +    else if (feed_steps < fast_table.steps_count * 2 + +             (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) +    { +        use_fast_fed = 0; +        DBG(DBG_info, "%s: feed too short, slow move forced.\n", __func__); +    } else { +/* for deciding whether we should use fast mode we need to check how long we +   need for (fast)accelerating, moving, decelerating, (TODO: stopping?) +   (slow)accelerating again versus (slow)accelerating and moving. we need +   fast and slow tables here. +*/ +/*NOTE: scan_exposure_time is per scan_yres*/ +/*NOTE: fast_exposure is per base_ydpi/4*/ +/*we use full steps as base unit here*/ +	fast_time = +	    fast_exposure / 4 * +        (feed_steps - fast_table.steps_count*2 - +         (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) +        + fast_table.pixeltime_sum*2 + slow_table.pixeltime_sum; +	slow_time = +	    (scan_exposure_time * scan_yres) / dev->motor.base_ydpi * +        (feed_steps - (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) +        + slow_table.pixeltime_sum; + +	DBG(DBG_info, "%s: Time for slow move: %d\n", __func__, slow_time); +	DBG(DBG_info, "%s: Time for fast move: %d\n", __func__, fast_time); + +	use_fast_fed = fast_time < slow_time; +    } + +    if (use_fast_fed) { +        feedl = feed_steps - fast_table.steps_count * 2 - +                (slow_table.steps_count >> static_cast<unsigned>(scan_step_type)); +    } else if ((feed_steps << static_cast<unsigned>(scan_step_type)) < slow_table.steps_count) { +        feedl = 0; +    } else { +        feedl = (feed_steps << static_cast<unsigned>(scan_step_type)) - slow_table.steps_count; +    } +    DBG(DBG_info, "%s: Decided to use %s mode\n", __func__, use_fast_fed?"fast feed":"slow feed"); + +/* all needed slopes available. we did even decide which mode to use. +   what next? +   - transfer slopes +SCAN: +flags \ use_fast_fed    ! 0         1 +------------------------\-------------------- +                      0 ! 0,1,2     0,1,2,3 +MotorFlag::AUTO_GO_HOME ! 0,1,2,4   0,1,2,3,4 +OFF:       none +FEED:      3 +GO_HOME:   3 +HOME_FREE: 3 +   - setup registers +     * slope specific registers (already done) +     * DECSEL for HOME_FREE/GO_HOME/SCAN +     * FEEDL +     * MTRREV +     * MTRPWR +     * FASTFED +     * STEPSEL +     * MTRPWM +     * FSTPSEL +     * FASTPWM +     * HOMENEG +     * BWDSTEP +     * FWDSTEP +     * Z1 +     * Z2 + */ + +    r = sanei_genesys_get_address (reg, 0x3d); +    r->value = (feedl >> 16) & 0xf; +    r = sanei_genesys_get_address (reg, 0x3e); +    r->value = (feedl >> 8) & 0xff; +    r = sanei_genesys_get_address (reg, 0x3f); +    r->value = feedl & 0xff; +    r = sanei_genesys_get_address (reg, 0x5e); +    r->value &= ~0xe0; + +    r = sanei_genesys_get_address (reg, 0x25); +    r->value = (scan_lines >> 16) & 0xf; +    r = sanei_genesys_get_address (reg, 0x26); +    r->value = (scan_lines >> 8) & 0xff; +    r = sanei_genesys_get_address (reg, 0x27); +    r->value = scan_lines & 0xff; + +    r = sanei_genesys_get_address (reg, 0x02); +    r->value &= ~0x01; /*LONGCURV OFF*/ +    r->value &= ~0x80; /*NOT_HOME OFF*/ +    r->value |= 0x10; + +    r->value &= ~0x06; + +    if (use_fast_fed) +	r->value |= 0x08; +    else +	r->value &= ~0x08; + +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) +	r->value |= 0x20; +    else +	r->value &= ~0x20; + +    if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE)) { +        r->value |= 0x40; +    } else { +        r->value &= ~0x40; +    } + +    gl841_send_slope_table(dev, 0, slow_table.table, 256); + +    gl841_send_slope_table(dev, 1, back_table.table, 256); + +    gl841_send_slope_table(dev, 2, slow_table.table, 256); + +    if (use_fast_fed) { +        gl841_send_slope_table(dev, 3, fast_table.table, 256); +    } + +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        gl841_send_slope_table(dev, 4, fast_table.table, 256); +    } + +/* now reg 0x21 and 0x24 are available, we can calculate reg 0x22 and 0x23, +   reg 0x60-0x62 and reg 0x63-0x65 +   rule: +   2*STEPNO+FWDSTEP=2*FASTNO+BWDSTEP +*/ +/* steps of table 0*/ +    if (min_restep < slow_table.steps_count * 2 + 2) { +        min_restep = slow_table.steps_count * 2 + 2; +    } +/* steps of table 1*/ +    if (min_restep < back_table.steps_count * 2 + 2) { +        min_restep = back_table.steps_count * 2 + 2; +    } +/* steps of table 0*/ +    r = sanei_genesys_get_address(reg, REG_FWDSTEP); +    r->value = min_restep - slow_table.steps_count*2; +/* steps of table 1*/ +    r = sanei_genesys_get_address(reg, REG_BWDSTEP); +    r->value = min_restep - back_table.steps_count*2; + +/* +  for z1/z2: +  in dokumentation mentioned variables a-d: +  a = time needed for acceleration, table 1 +  b = time needed for reg 0x1f... wouldn't that be reg0x1f*exposure_time? +  c = time needed for acceleration, table 1 +  d = time needed for reg 0x22... wouldn't that be reg0x22*exposure_time? +  z1 = (c+d-1) % exposure_time +  z2 = (a+b-1) % exposure_time +*/ +/* i don't see any effect of this. i can only guess that this will enhance +   sub-pixel accuracy +   z1 = (slope_0_time-1) % exposure_time; +   z2 = (slope_0_time-1) % exposure_time; +*/ +    z1 = z2 = 0; + +    DBG(DBG_info, "%s: z1 = %d\n", __func__, z1); +    DBG(DBG_info, "%s: z2 = %d\n", __func__, z2); +    r = sanei_genesys_get_address (reg, 0x60); +    r->value = ((z1 >> 16) & 0xff); +    r = sanei_genesys_get_address (reg, 0x61); +    r->value = ((z1 >> 8) & 0xff); +    r = sanei_genesys_get_address (reg, 0x62); +    r->value = (z1 & 0xff); +    r = sanei_genesys_get_address (reg, 0x63); +    r->value = ((z2 >> 16) & 0xff); +    r = sanei_genesys_get_address (reg, 0x64); +    r->value = ((z2 >> 8) & 0xff); +    r = sanei_genesys_get_address (reg, 0x65); +    r->value = (z2 & 0xff); + +    r = sanei_genesys_get_address(reg, REG_0x1E); +    r->value &= REG_0x1E_WDTIME; +    r->value |= scan_dummy; + +    r = sanei_genesys_get_address (reg, 0x67); +    r->value = 0x3f | (static_cast<unsigned>(scan_step_type) << 6); + +    r = sanei_genesys_get_address (reg, 0x68); +    r->value = 0x3f; + +    r = sanei_genesys_get_address(reg, REG_STEPNO); +    r->value = (slow_table.steps_count >> 1) + (slow_table.steps_count & 1); + +    r = sanei_genesys_get_address(reg, REG_FASTNO); +    r->value = (back_table.steps_count >> 1) + (back_table.steps_count & 1); + +    r = sanei_genesys_get_address (reg, 0x69); +    r->value = (slow_table.steps_count >> 1) + (slow_table.steps_count & 1); + +    r = sanei_genesys_get_address (reg, 0x6a); +    r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1); + +    r = sanei_genesys_get_address (reg, 0x5f); +    r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1); +} + +static int +gl841_get_dpihw(Genesys_Device * dev) +{ +  GenesysRegister* r; +  r = sanei_genesys_get_address(&dev->reg, 0x05); +    if ((r->value & REG_0x05_DPIHW) == REG_0x05_DPIHW_600) { +        return 600; +    } +    if ((r->value & REG_0x05_DPIHW) == REG_0x05_DPIHW_1200) { +        return 1200; +    } +    if ((r->value & REG_0x05_DPIHW) == REG_0x05_DPIHW_2400) { +        return 2400; +    } +  return 0; +} + +static void gl841_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set* reg, unsigned int exposure_time, +                                         const ScanSession& session) +{ +    DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time); +    GenesysRegister* r; +    uint16_t expavg, expr, expb, expg; + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); + +    /* gpio part.*/ +    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) { +        r = sanei_genesys_get_address(reg, REG_0x6C); +        if (session.ccd_size_divisor > 1) { +            r->value &= ~0x80; +        } else { +            r->value |= 0x80; +        } +      } +    if (dev->model->gpio_id == GpioId::CANON_LIDE_80) { +        r = sanei_genesys_get_address(reg, REG_0x6C); +        if (session.ccd_size_divisor > 1) { +	    r->value &= ~0x40; +	    r->value |= 0x20; +        } else { +	    r->value &= ~0x20; +	    r->value |= 0x40; +        } +      } + +    /* enable shading */ +    r = sanei_genesys_get_address (reg, 0x01); +    r->value |= REG_0x01_SCAN; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) || +        (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) { +        r->value &= ~REG_0x01_DVDSET; +    } else { +        r->value |= REG_0x01_DVDSET; +    } + +    /* average looks better than deletion, and we are already set up to +       use  one of the average enabled resolutions +    */ +    r = sanei_genesys_get_address (reg, 0x03); +    r->value |= REG_0x03_AVEENB; +    sanei_genesys_set_lamp_power(dev, sensor, *reg, +                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP)); + +    /* BW threshold */ +    r = sanei_genesys_get_address (reg, 0x2e); +    r->value = dev->settings.threshold; +    r = sanei_genesys_get_address (reg, 0x2f); +    r->value = dev->settings.threshold; + + +    /* monochrome / color scan */ +    r = sanei_genesys_get_address (reg, 0x04); +    switch (session.params.depth) { +	case 8: +            r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +	    break; +	case 16: +            r->value &= ~REG_0x04_LINEART; +            r->value |= REG_0x04_BITSET; +	    break; +    } + +    /* AFEMOD should depend on FESET, and we should set these +     * bits separately */ +    r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD); +    if (has_flag(session.params.flags, ScanFlag::ENABLE_LEDADD)) { +        r->value |= 0x10;	/* no filter */ +    } +    else if (session.params.channels == 1) +      { +    switch (session.params.color_filter) +	  { +            case ColorFilter::RED: +                r->value |= 0x14; +                break; +            case ColorFilter::GREEN: +                r->value |= 0x18; +                break; +            case ColorFilter::BLUE: +                r->value |= 0x1c; +                break; +            default: +                r->value |= 0x10; +                break; +	  } +      } +    else +      { +        if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) { +            r->value |= 0x22;	/* slow color pixel by pixel */ +          } +	else +          { +	    r->value |= 0x10;	/* color pixel by pixel */ +          } +      } + +    /* CIS scanners can do true gray by setting LEDADD */ +    r = sanei_genesys_get_address (reg, 0x87); +    r->value &= ~REG_0x87_LEDADD; +    if (has_flag(session.params.flags, ScanFlag::ENABLE_LEDADD)) { +        r->value |= REG_0x87_LEDADD; +        expr = reg->get16(REG_EXPR); +        expg = reg->get16(REG_EXPG); +        expb = reg->get16(REG_EXPB); + +	/* use minimal exposure for best image quality */ +	expavg = expg; +	if (expr < expg) +	  expavg = expr; +	if (expb < expavg) +	  expavg = expb; + +        dev->reg.set16(REG_EXPR, expavg); +        dev->reg.set16(REG_EXPG, expavg); +        dev->reg.set16(REG_EXPB, expavg); +      } + +    // enable gamma tables +    if (should_enable_gamma(session, sensor)) { +        reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB; +    } else { +        reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB; +    } + +    /* sensor parameters */ +    sanei_gl841_setup_sensor(dev, sensor, &dev->reg, 1, session.ccd_size_divisor); + +    r = sanei_genesys_get_address (reg, 0x29); +    r->value = 255; /*<<<"magic" number, only suitable for cis*/ + +    reg->set16(REG_DPISET, gl841_get_dpihw(dev) * session.output_resolution / session.optical_resolution); +    reg->set16(REG_STRPIXEL, session.pixel_startx); +    reg->set16(REG_ENDPIXEL, session.pixel_endx); + +    reg->set24(REG_MAXWD, session.output_line_bytes); + +    reg->set16(REG_LPERIOD, exposure_time); + +    r = sanei_genesys_get_address (reg, 0x34); +    r->value = sensor.dummy_pixel; +} + +static int +gl841_get_led_exposure(Genesys_Device * dev, const Genesys_Sensor& sensor) +{ +    int d,r,g,b,m; +    if (!dev->model->is_cis) +	return 0; +    d = dev->reg.find_reg(0x19).value; + +    r = sensor.exposure.red; +    g = sensor.exposure.green; +    b = sensor.exposure.blue; + +    m = r; +    if (m < g) +	m = g; +    if (m < b) +	m = b; + +    return m + d; +} + +/** @brief compute exposure time + * Compute exposure time for the device and the given scan resolution + */ +static int +gl841_exposure_time(Genesys_Device *dev, const Genesys_Sensor& sensor, +                    float slope_dpi, +                    StepType scan_step_type, +                    int start, +                    int used_pixels) +{ +int exposure_time = 0; +int led_exposure; + +  led_exposure=gl841_get_led_exposure(dev, sensor); +  exposure_time = sanei_genesys_exposure_time2( +      dev, +      slope_dpi, +      scan_step_type, +      start+used_pixels,/*+tgtime? currently done in sanei_genesys_exposure_time2 with tgtime = 32 pixel*/ +      led_exposure); + +  return exposure_time; +} + +/**@brief compute scan_step_type + * Try to do at least 4 steps per line. if that is impossible we will have to + * live with that. + * @param dev device + * @param yres motor resolution + */ +static StepType gl841_scan_step_type(Genesys_Device *dev, int yres) +{ +    StepType type = StepType::FULL; + +  /* TODO : check if there is a bug around the use of max_step_type   */ +  /* should be <=1, need to chek all devices entry in genesys_devices */ +    if (yres * 4 < dev->motor.base_ydpi || dev->motor.max_step_type() == StepType::FULL) { +        type = StepType::FULL; +    } else if (yres * 4 < dev->motor.base_ydpi * 2 || +               dev->motor.max_step_type() <= StepType::HALF) +    { +        type = StepType::HALF; +    } else { +        type = StepType::QUARTER; +    } + +  /* this motor behaves differently */ +    if (dev->model->motor_id==MotorId::CANON_LIDE_80) { +        // driven by 'frequency' tables ? +        type = StepType::FULL; +    } + +    return type; +} + +void CommandSetGl841::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* reg, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +  int move; +  int exposure_time; + +  int slope_dpi = 0; +  int dummy = 0; + +/* +results: + +for scanner: +start +end +dpiset +exposure_time +dummy +z1 +z2 + +for ordered_read: +  dev->words_per_line +  dev->read_factor +  dev->requested_buffer_size +  dev->read_buffer_size +  dev->read_pos +  dev->read_bytes_in_buffer +  dev->read_bytes_left +  dev->max_shift +  dev->stagger + +independent of our calculated values: +  dev->total_bytes_read +  dev->bytes_to_read + */ + +/* dummy */ +  /* dummy lines: may not be usefull, for instance 250 dpi works with 0 or 1 +     dummy line. Maybe the dummy line adds correctness since the motor runs +     slower (higher dpi) +  */ +/* for cis this creates better aligned color lines: +dummy \ scanned lines +   0: R           G           B           R ... +   1: R        G        B        -        R ... +   2: R      G      B       -      -      R ... +   3: R     G     B     -     -     -     R ... +   4: R    G    B     -   -     -    -    R ... +   5: R    G   B    -   -   -    -   -    R ... +   6: R   G   B   -   -   -   -   -   -   R ... +   7: R   G  B   -  -   -   -  -   -  -   R ... +   8: R  G  B   -  -  -   -  -  -   -  -  R ... +   9: R  G  B  -  -  -  -  -  -  -  -  -  R ... +  10: R  G B  -  -  -  - -  -  -  -  - -  R ... +  11: R  G B  - -  - -  -  - -  - -  - -  R ... +  12: R G  B - -  - -  - -  - -  - - -  - R ... +  13: R G B  - - - -  - - -  - - - -  - - R ... +  14: R G B - - -  - - - - - -  - - - - - R ... +  15: R G B - - - - - - - - - - - - - - - R ... + -- pierre + */ +  dummy = 0; + +/* slope_dpi */ +/* cis color scan is effectively a gray scan with 3 gray lines per color +   line and a FILTER of 0 */ +    if (dev->model->is_cis) { +        slope_dpi = session.params.yres* session.params.channels; +    } else { +        slope_dpi = session.params.yres; +    } + +  slope_dpi = slope_dpi * (1 + dummy); + +    StepType scan_step_type = gl841_scan_step_type(dev, session.params.yres); +    exposure_time = gl841_exposure_time(dev, sensor, +                    slope_dpi, +                    scan_step_type, +                                        session.pixel_startx, +                                        session.optical_pixels); +  DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time); + +    gl841_init_optical_regs_scan(dev, sensor, reg, exposure_time, session); + +    move = session.params.starty; +  DBG(DBG_info, "%s: move=%d steps\n", __func__, move); + +  /* subtract current head position */ +    move -= (dev->head_pos(ScanHeadId::PRIMARY) * session.params.yres) / dev->motor.base_ydpi; +  DBG(DBG_info, "%s: move=%d steps\n", __func__, move); + +  if (move < 0) +      move = 0; + +  /* round it */ +/* the move is not affected by dummy -- pierre */ +/*  move = ((move + dummy) / (dummy + 1)) * (dummy + 1); +    DBG(DBG_info, "%s: move=%d steps\n", __func__, move);*/ + +    if (has_flag(session.params.flags, ScanFlag::SINGLE_LINE)) { +        gl841_init_motor_regs_off(reg, dev->model->is_cis ? session.output_line_count * session.params.channels +                                                          : session.output_line_count); +    } else { +        auto motor_flag = has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE) ? +                              MotorFlag::DISABLE_BUFFER_FULL_MOVE : MotorFlag::NONE; + +        gl841_init_motor_regs_scan(dev, sensor, reg, exposure_time, slope_dpi, scan_step_type, +                                   dev->model->is_cis ? session.output_line_count * session.params.channels +                                                      : session.output_line_count, +                                   dummy, move, motor_flag); +  } + +    dev->read_buffer.clear(); +    dev->read_buffer.alloc(session.buffer_size_read); + +    build_image_pipeline(dev, session); + +    dev->read_active = true; + +    dev->session = session; + +    dev->total_bytes_read = 0; +    dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines; + +    DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read); +} + +ScanSession CommandSetGl841::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +  int start; + +    DBG(DBG_info, "%s ", __func__); +    debug_dump(DBG_info, settings); + +/* start */ +    start = static_cast<int>(dev->model->x_offset); +    start += static_cast<int>(settings.tl_x); + +    start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = start; +    session.params.starty = 0; // not used +    session.params.pixels = settings.pixels; +    session.params.requested_pixels = settings.requested_pixels; +    session.params.lines = settings.lines; +    session.params.depth = settings.depth; +    session.params.channels = settings.get_channels(); +    session.params.scan_method = settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; + +    compute_session(dev, session, sensor); + +    return session; +} + +// for fast power saving methods only, like disabling certain amplifiers +void CommandSetGl841::save_power(Genesys_Device* dev, bool enable) const +{ +    DBG_HELPER_ARGS(dbg, "enable = %d", enable); + +    const auto& sensor = sanei_genesys_find_sensor_any(dev); + +    if (enable) +    { +    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) +	{ +/* expect GPIO17 to be enabled, and GPIO9 to be disabled, +   while GPIO8 is disabled*/ +/* final state: GPIO8 disabled, GPIO9 enabled, GPIO17 disabled, +   GPIO18 disabled*/ + +            uint8_t val = dev->interface->read_register(REG_0x6D); +            dev->interface->write_register(REG_0x6D, val | 0x80); + +            dev->interface->sleep_ms(1); + +	    /*enable GPIO9*/ +            val = dev->interface->read_register(REG_0x6C); +            dev->interface->write_register(REG_0x6C, val | 0x01); + +	    /*disable GPO17*/ +            val = dev->interface->read_register(REG_0x6B); +            dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO17); + +	    /*disable GPO18*/ +            val = dev->interface->read_register(REG_0x6B); +            dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO18); + +            dev->interface->sleep_ms(1); + +            val = dev->interface->read_register(REG_0x6D); +            dev->interface->write_register(REG_0x6D, val & ~0x80); + +	} +    if (dev->model->gpio_id == GpioId::DP685) +	  { +            uint8_t val = dev->interface->read_register(REG_0x6B); +            dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO17); +            dev->reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17; +            dev->calib_reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17; +	  } + +        set_fe(dev, sensor, AFE_POWER_SAVE); + +    } +    else +    { +    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) +	{ +/* expect GPIO17 to be enabled, and GPIO9 to be disabled, +   while GPIO8 is disabled*/ +/* final state: GPIO8 enabled, GPIO9 disabled, GPIO17 enabled, +   GPIO18 enabled*/ + +            uint8_t val = dev->interface->read_register(REG_0x6D); +            dev->interface->write_register(REG_0x6D, val | 0x80); + +            dev->interface->sleep_ms(10); + +	    /*disable GPIO9*/ +            val = dev->interface->read_register(REG_0x6C); +            dev->interface->write_register(REG_0x6C, val & ~0x01); + +	    /*enable GPIO10*/ +            val = dev->interface->read_register(REG_0x6C); +            dev->interface->write_register(REG_0x6C, val | 0x02); + +	    /*enable GPO17*/ +            val = dev->interface->read_register(REG_0x6B); +            dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO17); +            dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17; +            dev->calib_reg.find_reg(0x6b).value |= REG_0x6B_GPO17; + +	    /*enable GPO18*/ +            val = dev->interface->read_register(REG_0x6B); +            dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO18); +            dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO18; +            dev->calib_reg.find_reg(0x6b).value |= REG_0x6B_GPO18; + +	} +    if (dev->model->gpio_id == GpioId::DP665 +            || dev->model->gpio_id == GpioId::DP685) +	  { +            uint8_t val = dev->interface->read_register(REG_0x6B); +            dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO17); +            dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17; +            dev->calib_reg.find_reg(0x6b).value |= REG_0x6B_GPO17; +	  } + +    } +} + +void CommandSetGl841::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    DBG_HELPER_ARGS(dbg, "delay = %d", delay); +  // FIXME: SEQUENTIAL not really needed in this case +  Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL); +  int rate, exposure_time, tgtime, time; + +    local_reg.init_reg(0x01, dev->reg.get8(0x01));	/* disable fastmode */ +    local_reg.init_reg(0x03, dev->reg.get8(0x03));	/* Lamp power control */ +    local_reg.init_reg(0x05, dev->reg.get8(0x05)); /*& ~REG_0x05_BASESEL*/;	/* 24 clocks/pixel */ +    local_reg.init_reg(0x18, 0x00); // Set CCD type +    local_reg.init_reg(0x38, 0x00); +    local_reg.init_reg(0x39, 0x00); + +    // period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE +    local_reg.init_reg(0x1c, dev->reg.get8(0x05) & ~REG_0x1C_TGTIME); + +    if (!delay) { +        local_reg.find_reg(0x03).value = local_reg.find_reg(0x03).value & 0xf0;	/* disable lampdog and set lamptime = 0 */ +    } else if (delay < 20) { +        local_reg.find_reg(0x03).value = (local_reg.find_reg(0x03).value & 0xf0) | 0x09;	/* enable lampdog and set lamptime = 1 */ +    } else { +        local_reg.find_reg(0x03).value = (local_reg.find_reg(0x03).value & 0xf0) | 0x0f;	/* enable lampdog and set lamptime = 7 */ +    } + +  time = delay * 1000 * 60;	/* -> msec */ +  exposure_time = static_cast<std::uint32_t>(time * 32000.0 / +                 (24.0 * 64.0 * (local_reg.find_reg(0x03).value & REG_0x03_LAMPTIM) * +		  1024.0) + 0.5); +  /* 32000 = system clock, 24 = clocks per pixel */ +  rate = (exposure_time + 65536) / 65536; +  if (rate > 4) +    { +      rate = 8; +      tgtime = 3; +    } +  else if (rate > 2) +    { +      rate = 4; +      tgtime = 2; +    } +  else if (rate > 1) +    { +      rate = 2; +      tgtime = 1; +    } +  else +    { +      rate = 1; +      tgtime = 0; +    } + +  local_reg.find_reg(0x1c).value |= tgtime; +  exposure_time /= rate; + +  if (exposure_time > 65535) +    exposure_time = 65535; + +  local_reg.set8(0x38, exposure_time >> 8); +  local_reg.set8(0x39, exposure_time & 255);	/* lowbyte */ + +    dev->interface->write_registers(local_reg); +} + +static void gl841_stop_action(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  Genesys_Register_Set local_reg; +  unsigned int loop; + +    scanner_read_print_status(*dev); + +    if (scanner_is_motor_stopped(*dev)) { +        DBG(DBG_info, "%s: already stopped\n", __func__); +        return; +    } + +  local_reg = dev->reg; + +    regs_set_optical_off(dev->model->asic_type, local_reg); + +    gl841_init_motor_regs_off(&local_reg,0); +    dev->interface->write_registers(local_reg); + +    if (is_testing_mode()) { +        return; +    } + +  /* looks like writing the right registers to zero is enough to get the chip +     out of scan mode into command mode, actually triggering(writing to +     register 0x0f) seems to be unnecessary */ + +  loop = 10; +    while (loop > 0) { +        if (scanner_is_motor_stopped(*dev)) { +            return; +        } + +        dev->interface->sleep_ms(100); +        loop--; +    } + +    throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor"); +} + +static bool gl841_get_paper_sensor(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +    uint8_t val = dev->interface->read_register(REG_0x6D); + +    return (val & 0x1) == 0; +} + +void CommandSetGl841::eject_document(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  Genesys_Register_Set local_reg; +  unsigned int init_steps; +  float feed_mm; +  int loop; + +    if (!dev->model->is_sheetfed) { +      DBG(DBG_proc, "%s: there is no \"eject sheet\"-concept for non sheet fed\n", __func__); +      DBG(DBG_proc, "%s: finished\n", __func__); +      return; +    } + + +  local_reg.clear(); + +    // FIXME: unused result +    scanner_read_status(*dev); + +    gl841_stop_action(dev); + +  local_reg = dev->reg; + +    regs_set_optical_off(dev->model->asic_type, local_reg); + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); +    gl841_init_motor_regs(dev, sensor, &local_reg, 65536, MOTOR_ACTION_FEED, MotorFlag::NONE); + +    dev->interface->write_registers(local_reg); + +    try { +        scanner_start_action(*dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { gl841_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() +        { +            dev->interface->write_registers(dev->reg); +        }); +        throw; +    } + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("eject_document"); +        gl841_stop_action(dev); +        return; +    } + +    if (gl841_get_paper_sensor(dev)) { +      DBG(DBG_info, "%s: paper still loaded\n", __func__); +      /* force document TRUE, because it is definitely present */ +        dev->document = true; +        dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +      loop = 300; +      while (loop > 0)		/* do not wait longer then 30 seconds */ +	{ + +            if (!gl841_get_paper_sensor(dev)) { +	      DBG(DBG_info, "%s: reached home position\n", __func__); +	      DBG(DBG_proc, "%s: finished\n", __func__); +	      break; +	    } +          dev->interface->sleep_ms(100); +	  --loop; +	} + +      if (loop == 0) +	{ +          // when we come here then the scanner needed too much time for this, so we better stop +          // the motor +          catch_all_exceptions(__func__, [&](){ gl841_stop_action(dev); }); +          throw SaneException(SANE_STATUS_IO_ERROR, +                              "timeout while waiting for scanhead to go home"); +	} +    } + +    feed_mm = static_cast<float>(dev->model->eject_feed); +  if (dev->document) +    { +        feed_mm += static_cast<float>(dev->model->post_scan); +    } + +        sanei_genesys_read_feed_steps(dev, &init_steps); + +  /* now feed for extra <number> steps */ +  loop = 0; +  while (loop < 300)		/* do not wait longer then 30 seconds */ +    { +      unsigned int steps; + +        sanei_genesys_read_feed_steps(dev, &steps); + +      DBG(DBG_info, "%s: init_steps: %d, steps: %d\n", __func__, init_steps, steps); + +      if (steps > init_steps + (feed_mm * dev->motor.base_ydpi) / MM_PER_INCH) +	{ +	  break; +	} + +        dev->interface->sleep_ms(100); +      ++loop; +    } + +    gl841_stop_action(dev); + +    dev->document = false; +} + + +void CommandSetGl841::load_document(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  int loop = 300; +  while (loop > 0)		/* do not wait longer then 30 seconds */ +    { +        if (gl841_get_paper_sensor(dev)) { +	  DBG(DBG_info, "%s: document inserted\n", __func__); + +	  /* when loading OK, document is here */ +        dev->document = true; + +          // give user some time to place document correctly +          dev->interface->sleep_ms(1000); +	  break; +	} +        dev->interface->sleep_ms(100); +      --loop; +    } + +  if (loop == 0) +    { +        // when we come here then the user needed to much time for this +        throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for document"); +    } +} + +/** + * detects end of document and adjust current scan + * to take it into account + * used by sheetfed scanners + */ +void CommandSetGl841::detect_document_end(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +    bool paper_loaded = gl841_get_paper_sensor(dev); + +  /* sheetfed scanner uses home sensor as paper present */ +    if (dev->document && !paper_loaded) { +      DBG(DBG_info, "%s: no more document\n", __func__); +        dev->document = false; + +      /* we can't rely on total_bytes_to_read since the frontend +       * might have been slow to read data, so we re-evaluate the +       * amount of data to scan form the hardware settings +       */ +        unsigned scanned_lines = 0; +        try { +            sanei_genesys_read_scancnt(dev, &scanned_lines); +        } catch (...) { +            dev->total_bytes_to_read = dev->total_bytes_read; +            throw; +        } + +        if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS && dev->model->is_cis) { +            scanned_lines /= 3; +        } + +        std::size_t output_lines = dev->session.output_line_count; + +        std::size_t offset_lines = static_cast<std::size_t>( +                (dev->model->post_scan / MM_PER_INCH) * dev->settings.yres); + +        std::size_t scan_end_lines = scanned_lines + offset_lines; + +        std::size_t remaining_lines = dev->get_pipeline_source().remaining_bytes() / +                dev->session.output_line_bytes_raw; + +        DBG(DBG_io, "%s: scanned_lines=%u\n", __func__, scanned_lines); +        DBG(DBG_io, "%s: scan_end_lines=%zu\n", __func__, scan_end_lines); +        DBG(DBG_io, "%s: output_lines=%zu\n", __func__, output_lines); +        DBG(DBG_io, "%s: remaining_lines=%zu\n", __func__, remaining_lines); + +        if (scan_end_lines > output_lines) { +            auto skip_lines = scan_end_lines - output_lines; + +            if (remaining_lines > skip_lines) { +                DBG(DBG_io, "%s: skip_lines=%zu\n", __func__, skip_lines); + +                remaining_lines -= skip_lines; +                dev->get_pipeline_source().set_remaining_bytes(remaining_lines * +                                                               dev->session.output_line_bytes_raw); +                dev->total_bytes_to_read -= skip_lines * dev->session.output_line_bytes_requested; +            } +        } +    } +} + +// Send the low-level scan command +// todo : is this that useful ? +void CommandSetGl841::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set* reg, bool start_motor) const +{ +    DBG_HELPER(dbg); +    (void) sensor; +  // FIXME: SEQUENTIAL not really needed in this case +  Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL); +  uint8_t val; + +    if (dev->model->gpio_id == GpioId::CANON_LIDE_80) { +        val = dev->interface->read_register(REG_0x6B); +        val = REG_0x6B_GPO18; +        dev->interface->write_register(REG_0x6B, val); +    } + +    if (dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICPRO_3600) { +        local_reg.init_reg(0x03, reg->get8(0x03) | REG_0x03_LAMPPWR); +    } else { +        // TODO PLUSTEK_3600: why ?? +        local_reg.init_reg(0x03, reg->get8(0x03)); +    } + +    local_reg.init_reg(0x01, reg->get8(0x01) | REG_0x01_SCAN); +    local_reg.init_reg(0x0d, 0x01); + +    // scanner_start_action(dev, start_motor) +    if (start_motor) { +        local_reg.init_reg(0x0f, 0x01); +    } else { +        // do not start motor yet +        local_reg.init_reg(0x0f, 0x00); +    } + +    dev->interface->write_registers(local_reg); + +    dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +} + + +// Send the stop scan command +void CommandSetGl841::end_scan(Genesys_Device* dev, Genesys_Register_Set __sane_unused__* reg, +                               bool check_stop) const +{ +    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop); + +    if (!dev->model->is_sheetfed) { +        gl841_stop_action(dev); +    } +} + +// Moves the slider to steps +static void gl841_feed(Genesys_Device* dev, int steps) +{ +    DBG_HELPER_ARGS(dbg, "steps = %d", steps); +  Genesys_Register_Set local_reg; +  int loop; + +    gl841_stop_action(dev); + +  // FIXME: we should pick sensor according to the resolution scanner is currently operating on +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +  local_reg = dev->reg; + +    regs_set_optical_off(dev->model->asic_type, local_reg); + +    gl841_init_motor_regs(dev, sensor, &local_reg, steps, MOTOR_ACTION_FEED, MotorFlag::NONE); + +    dev->interface->write_registers(local_reg); + +    try { +        scanner_start_action(*dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { gl841_stop_action (dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() +        { +            dev->interface->write_registers(dev->reg); +        }); +        throw; +    } + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("feed"); +        dev->advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::FORWARD, steps); +        gl841_stop_action(dev); +        return; +    } + +  loop = 0; +  while (loop < 300)		/* do not wait longer then 30 seconds */ +  { +        auto status = scanner_read_status(*dev); + +        if (!status.is_motor_enabled) { +            DBG(DBG_proc, "%s: finished\n", __func__); +            dev->advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::FORWARD, steps); +            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 */ +  gl841_stop_action (dev); + +    dev->set_head_pos_unknown(); + +    throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home"); +} + +// Moves the slider to the home (top) position slowly +void CommandSetGl841::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    DBG_HELPER_ARGS(dbg, "wait_until_home = %d", wait_until_home); +  Genesys_Register_Set local_reg; +  int loop = 0; + +    if (dev->model->is_sheetfed) { +      DBG(DBG_proc, "%s: there is no \"home\"-concept for sheet fed\n", __func__); +      DBG(DBG_proc, "%s: finished\n", __func__); +      return; +    } + +    // reset gpio pin +    uint8_t val; +    if (dev->model->gpio_id == GpioId::CANON_LIDE_35) { +        val = dev->interface->read_register(REG_0x6C); +        val = dev->gpo.regs.get_value(0x6c); +        dev->interface->write_register(REG_0x6C, val); +    } +    if (dev->model->gpio_id == GpioId::CANON_LIDE_80) { +        val = dev->interface->read_register(REG_0x6B); +        val = REG_0x6B_GPO18 | REG_0x6B_GPO17; +        dev->interface->write_register(REG_0x6B, val); +    } +    dev->cmd_set->save_power(dev, false); + +    // first read gives HOME_SENSOR true +    auto status = scanner_read_reliable_status(*dev); + + +    if (status.is_at_home) { +      DBG(DBG_info, "%s: already at home, completed\n", __func__); +        dev->set_head_pos_zero(ScanHeadId::PRIMARY); +      return; +    } + +    scanner_stop_action_no_move(*dev, dev->reg); + +  /* if motor is on, stop current action */ +    if (status.is_motor_enabled) { +        gl841_stop_action(dev); +    } + +  local_reg = dev->reg; + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +    gl841_init_motor_regs(dev, sensor, &local_reg, 65536, MOTOR_ACTION_GO_HOME, MotorFlag::REVERSE); + +    // set up for no scan +    regs_set_optical_off(dev->model->asic_type, local_reg); + +    dev->interface->write_registers(local_reg); + +    try { +        scanner_start_action(*dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { gl841_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() +        { +            dev->interface->write_registers(dev->reg); +        }); +        throw; +    } + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("move_back_home"); +        dev->set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +  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: finished\n", __func__); +                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__, [&](){ gl841_stop_action(dev); }); +        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 a 200x200 pixels area at 600 dpi +// from very top of scanner +void CommandSetGl841::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  int size; +  Genesys_Register_Set local_reg; + +  int pixels = 600; +  int dpi = 300; + +  local_reg = dev->reg; + +  /* sets for a 200 lines * 600 pixels */ +  /* normal scan with no shading */ + +    // FIXME: the current approach of doing search only for one resolution does not work on scanners +    // whith employ different sensors with potentially different settings. +    const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method); + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; /*we should give a small offset here~60 steps*/ +    session.params.pixels = 600; +    session.params.lines = dev->model->search_lines; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::GREEN; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::IGNORE_LINE_DISTANCE | +                           ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    // send to scanner +    dev->interface->write_registers(local_reg); + +  size = pixels * dev->model->search_lines; + +  std::vector<uint8_t> data(size); + +    dev->cmd_set->begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_start_position"); +        dev->cmd_set->end_scan(dev, &local_reg, true); +        dev->reg = local_reg; +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl841_search_position.pnm", data.data(), 8, 1, pixels, +                                     dev->model->search_lines); +    } + +    dev->cmd_set->end_scan(dev, &local_reg, true); + +  /* update regs to copy ASIC internal state */ +  dev->reg = local_reg; + +    for (auto& sensor_update : +            sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method)) +    { +        sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, dpi, pixels, +                                             dev->model->search_lines); +    } +} + +// sets up register for coarse gain calibration +// todo: check it for scanners using it +void CommandSetGl841::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel(); +    session.params.lines = 20; +    session.params.depth = 16; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__, +      sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres); + +    dev->interface->write_registers(regs); + +/*  if (DBG_LEVEL >= DBG_info) +    sanei_gl841_print_registers (regs);*/ +} + + +// init registers for shading calibration +void CommandSetGl841::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER_ARGS(dbg, "lines = %zu", dev->calib_lines); +  SANE_Int ydpi; +    unsigned starty = 0; + +  /* initial calibration reg values */ +  regs = dev->reg; + +  ydpi = dev->motor.base_ydpi; +  if (dev->model->motor_id == MotorId::PLUSTEK_OPTICPRO_3600)  /* TODO PLUSTEK_3600: 1200dpi not yet working, produces dark bar */ +    { +      ydpi = 600; +    } +    if (dev->model->motor_id == MotorId::CANON_LIDE_80) { +      ydpi = gl841_get_dpihw(dev); +      /* get over extra dark area for this model. +	 It looks like different devices have dark areas of different width +	 due to manufacturing variability. The initial value of starty was 140, +	 but it moves the sensor almost past the dark area completely in places +	 on certain devices. + +	 On a particular device the black area starts at roughly position +	 160 to 230 depending on location (the dark area is not completely +	 parallel to the frame). +      */ +      starty = 70; +    } + +  dev->calib_channels = 3; +  dev->calib_lines = dev->model->shading_lines; + +    unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres); +    unsigned factor = sensor.optical_res / resolution; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, dev->calib_channels, +                                                         dev->settings.scan_method); + +    dev->calib_pixels = calib_sensor.sensor_pixels / factor; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = ydpi; +    session.params.startx = 0; +    session.params.starty = starty; +    session.params.pixels = dev->calib_pixels; +    session.params.lines = dev->calib_lines; +    session.params.depth = 16; +    session.params.channels = dev->calib_channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           /*ScanFlag::DISABLE_BUFFER_FULL_MOVE |*/ +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); +} + +// set up registers for the actual scan +void CommandSetGl841::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  float move; +  int move_dpi; +  float start; + +    debug_dump(DBG_info, dev->settings); + +  /* steps to move to reach scanning area: +     - first we move to physical start of scanning +     either by a fixed steps amount from the black strip +     or by a fixed amount from parking position, +     minus the steps done during shading calibration +     - then we move by the needed offset whitin physical +     scanning area + +     assumption: steps are expressed at maximum motor resolution + +     we need: +     float y_offset; +     float y_size; +     float y_offset_calib; +     mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH */ + +  /* if scanner uses GENESYS_FLAG_SEARCH_START y_offset is +     relative from origin, else, it is from parking position */ + +  move_dpi = dev->motor.base_ydpi; + +  move = 0; +    if (dev->model->flags & GENESYS_FLAG_SEARCH_START) { +        move += static_cast<float>(dev->model->y_offset_calib_white); +    } + +  DBG(DBG_info, "%s move=%f steps\n", __func__, move); + +    move += static_cast<float>(dev->model->y_offset); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +    move += static_cast<float>(dev->settings.tl_y); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +    move = static_cast<float>((move * move_dpi) / MM_PER_INCH); + +/* start */ +    start = static_cast<float>(dev->model->x_offset); + +    start += static_cast<float>(dev->settings.tl_x); + +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +  /* we enable true gray for cis scanners only, and just when doing +   * scan since color calibration is OK for this mode +   */ +    ScanFlag flags = ScanFlag::NONE; + +  /* true gray (led add for cis scanners) */ +  if(dev->model->is_cis && dev->settings.true_gray +    && dev->settings.scan_mode != ScanColorMode::COLOR_SINGLE_PASS +    && dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80) +    { +      // on Lide 80 the LEDADD bit results in only red LED array being lit +      DBG(DBG_io, "%s: activating LEDADD\n", __func__); +        flags |= ScanFlag::ENABLE_LEDADD; +    } + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->settings.pixels; +    session.params.requested_pixels = dev->settings.requested_pixels; +    session.params.lines = dev->settings.lines; +    session.params.depth = dev->settings.depth; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); +} + + +// this function sends generic gamma table (ie linear ones) or the Sensor specific one if provided +void CommandSetGl841::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  int size; + +  size = 256; + +  /* allocate temporary gamma tables: 16 bits words, 3 channels */ +  std::vector<uint8_t> gamma(size * 2 * 3); + +    sanei_genesys_generate_gamma_buffer(dev, sensor, 16, 65535, size, gamma.data()); + +    dev->interface->write_gamma(0x28, 0x0000, gamma.data(), size * 2 * 3); +} + + +/* this function does the led calibration by scanning one line of the calibration +   area below scanner's top on white strip. + +-needs working coarse/gain +*/ +SensorExposure CommandSetGl841::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int total_size; +  int i, j; +  int val; +  int channels; +  int avg[3], avga, avge; +  int turn; +  uint16_t exp[3], target; +  int move; + +  /* these 2 boundaries should be per sensor */ +  uint16_t min_exposure=500; +  uint16_t max_exposure; + +  /* feed to white strip if needed */ +    if (dev->model->y_offset_calib_white > 0) { +        move = static_cast<int>(dev->model->y_offset_calib_white); +        move = static_cast<int>((move * (dev->motor.base_ydpi)) / MM_PER_INCH); +      DBG(DBG_io, "%s: move=%d lines\n", __func__, move); +        gl841_feed(dev, move); +    } + +  /* offset calibration is always done in color mode */ +  channels = 3; + +    unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres); +    unsigned factor = sensor.optical_res / resolution; + +    const auto& calib_sensor_base = sanei_genesys_find_sensor(dev, resolution, channels, +                                                              dev->settings.scan_method); + +    num_pixels = calib_sensor_base.sensor_pixels / factor; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor_base); + +    init_regs_for_scan_session(dev, calib_sensor_base, ®s, session); + +    dev->interface->write_registers(regs); + + +  total_size = num_pixels * channels * 2 * 1;	/* colors * bytes_per_color * scan lines */ + +  std::vector<uint8_t> line(total_size); + +/* +   we try to get equal bright leds here: + +   loop: +     average per color +     adjust exposure times + */ + +  exp[0] = sensor.exposure.red; +  exp[1] = sensor.exposure.green; +  exp[2] = sensor.exposure.blue; + +  turn = 0; +  /* max exposure is set to ~2 time initial average +   * exposure, or 2 time last calibration exposure */ +  max_exposure=((exp[0]+exp[1]+exp[2])/3)*2; +  target=sensor.gain_white_ref*256; + +    auto calib_sensor = calib_sensor_base; + +    bool acceptable = false; +    do { +        calib_sensor.exposure.red = exp[0]; +        calib_sensor.exposure.green = exp[1]; +        calib_sensor.exposure.blue = exp[2]; + +        regs_set_exposure(dev->model->asic_type, regs, calib_sensor.exposure); +        dev->interface->write_register(0x10, (calib_sensor.exposure.red >> 8) & 0xff); +        dev->interface->write_register(0x11, calib_sensor.exposure.red & 0xff); +        dev->interface->write_register(0x12, (calib_sensor.exposure.green >> 8) & 0xff); +        dev->interface->write_register(0x13, calib_sensor.exposure.green & 0xff); +        dev->interface->write_register(0x14, (calib_sensor.exposure.blue >> 8) & 0xff); +        dev->interface->write_register(0x15, calib_sensor.exposure.blue & 0xff); + +        dev->interface->write_registers(regs); + +      DBG(DBG_info, "%s: starting line reading\n", __func__); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("led_calibration"); +            move_back_home(dev, true); +            return calib_sensor.exposure; +        } + +        sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +      if (DBG_LEVEL >= DBG_data) { +          char fn[30]; +          std::snprintf(fn, 30, "gl841_led_%d.pnm", turn); +          sanei_genesys_write_pnm_file(fn, line.data(), 16, channels, num_pixels, 1); +      } + +     /* compute average */ +      for (j = 0; j < channels; j++) +      { +	  avg[j] = 0; +	  for (i = 0; i < num_pixels; i++) +	  { +	      if (dev->model->is_cis) +		  val = +		      line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		      line[i * 2 + j * 2 * num_pixels]; +	      else +		  val = +		      line[i * 2 * channels + 2 * j + 1] * 256 + +		      line[i * 2 * channels + 2 * j]; +	      avg[j] += val; +	  } + +	  avg[j] /= num_pixels; +      } + +      DBG(DBG_info,"%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +        acceptable = true; + +     /* exposure is acceptable if each color is in the %5 range +      * of other color channels */ +      if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 || +	  avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 || +	  avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95) +        { +            acceptable = false; +        } + +      /* led exposure is not acceptable if white level is too low +       * ~80 hardcoded value for white level */ +      if(avg[0]<20000 || avg[1]<20000 || avg[2]<20000) +        { +            acceptable = false; +        } + +      /* for scanners using target value */ +      if(target>0) +        { +            acceptable = true; +          for(i=0;i<3;i++) +            { +              /* we accept +- 2% delta from target */ +              if(abs(avg[i]-target)>target/50) +                { +                  exp[i]=(exp[i]*target)/avg[i]; +                    acceptable = false; +                } +            } +        } +      else +        { +          if (!acceptable) +            { +              avga = (avg[0]+avg[1]+avg[2])/3; +              exp[0] = (exp[0] * avga) / avg[0]; +              exp[1] = (exp[1] * avga) / avg[1]; +              exp[2] = (exp[2] * avga) / avg[2]; +              /* +                keep the resulting exposures below this value. +                too long exposure drives the ccd into saturation. +                we may fix this by relying on the fact that +                we get a striped scan without shading, by means of +                statistical calculation +              */ +              avge = (exp[0] + exp[1] + exp[2]) / 3; + +              if (avge > max_exposure) { +                  exp[0] = (exp[0] * max_exposure) / avge; +                  exp[1] = (exp[1] * max_exposure) / avge; +                  exp[2] = (exp[2] * max_exposure) / avge; +              } +              if (avge < min_exposure) { +                  exp[0] = (exp[0] * min_exposure) / avge; +                  exp[1] = (exp[1] * min_exposure) / avge; +                  exp[2] = (exp[2] * min_exposure) / avge; +              } + +            } +        } + +        gl841_stop_action(dev); + +      turn++; + +  } while (!acceptable && turn < 100); + +  DBG(DBG_info,"%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]); + +    dev->cmd_set->move_back_home(dev, true); + +    return calib_sensor.exposure; +} + +/** @brief calibration for AD frontend devices + * offset calibration assumes that the scanning head is on a black area + * For LiDE80 analog frontend + * 0x0003 : is gain and belongs to [0..63] + * 0x0006 : is offset + * We scan a line with no gain until average offset reaches the target + */ +static void ad_fe_offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                     Genesys_Register_Set& regs) +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int total_size; +  int i; +  int average; +  int turn; +  int top; +  int bottom; +  int target; + +  /* don't impact 3600 behavior since we can't test it */ +    if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) { +      return; +    } + +    unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres); +    unsigned factor = sensor.optical_res / resolution; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, +                                                              dev->settings.scan_method); + +    num_pixels = calib_sensor.sensor_pixels / factor; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 8; +    session.params.channels = 3; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    dev->cmd_set->init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +  total_size = num_pixels * 3 * 2 * 1; + +  std::vector<uint8_t> line(total_size); + +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); + +  /* loop on scan until target offset is reached */ +  turn=0; +  target=24; +  bottom=0; +  top=255; +  do { +      /* set up offset mid range */ +      dev->frontend.set_offset(0, (top + bottom) / 2); +      dev->frontend.set_offset(1, (top + bottom) / 2); +      dev->frontend.set_offset(2, (top + bottom) / 2); + +      /* scan line */ +      DBG(DBG_info, "%s: starting line reading\n", __func__); +        dev->interface->write_registers(regs); +      dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("ad_fe_offset_calibration"); +            gl841_stop_action(dev); +            return; +        } + +      sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); +      gl841_stop_action (dev); +      if (DBG_LEVEL >= DBG_data) { +          char fn[30]; +          std::snprintf(fn, 30, "gl841_offset_%02d.pnm", turn); +          sanei_genesys_write_pnm_file(fn, line.data(), 8, 3, num_pixels, 1); +      } + +      /* search for minimal value */ +      average=0; +      for(i=0;i<total_size;i++) +        { +          average+=line[i]; +        } +      average/=total_size; +      DBG(DBG_data, "%s: average=%d\n", __func__, average); + +      /* if min value is above target, the current value becomes the new top +       * else it is the new bottom */ +      if(average>target) +        { +          top=(top+bottom)/2; +        } +      else +        { +          bottom=(top+bottom)/2; +        } +      turn++; +  } while ((top-bottom)>1 && turn < 100); + +  // FIXME: don't overwrite the calibrated values +  dev->frontend.set_offset(0, 0); +  dev->frontend.set_offset(1, 0); +  dev->frontend.set_offset(2, 0); +  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. +   sanei_genesys_search_start() must have been called so that the offsets and margins +   are allready known. + +this function expects the slider to be where? +*/ +void CommandSetGl841::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int total_size; +  int i, j; +  int val; +  int channels; +  int off[3],offh[3],offl[3],off1[3],off2[3]; +  int min1[3],min2[3]; +  int cmin[3],cmax[3]; +  int turn; +  int mintgt = 0x400; + +  /* Analog Device fronted have a different calibration */ +    if ((dev->reg.find_reg(0x04).value & REG_0x04_FESET) == 0x02) { +      return ad_fe_offset_calibration(dev, sensor, regs); +    } + +  /* offset calibration is always done in color mode */ +  channels = 3; + +    unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres); +    unsigned factor = sensor.optical_res / resolution; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         dev->settings.scan_method); + +    num_pixels = calib_sensor.sensor_pixels / factor; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE | +                           ScanFlag::DISABLE_LAMP; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +  total_size = num_pixels * channels * 2 * 1;	/* colors * bytes_per_color * scan lines */ + +  std::vector<uint8_t> first_line(total_size); +  std::vector<uint8_t> second_line(total_size); + +  /* scan first line of data with no offset nor gain */ +/*WM8199: gain=0.73; offset=-260mV*/ +/*okay. the sensor black level is now at -260mV. we only get 0 from AFE...*/ +/* we should probably do real calibration here: + * -detect acceptable offset with binary search + * -calculate offset from this last version + * + * acceptable offset means + *   - few completely black pixels(<10%?) + *   - few completely white pixels(<10%?) + * + * final offset should map the minimum not completely black + * pixel to 0(16 bits) + * + * this does account for dummy pixels at the end of ccd + * this assumes slider is at black strip(which is not quite as black as "no + * signal"). + * + */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); +  offh[0] = 0xff; +  offh[1] = 0xff; +  offh[2] = 0xff; +  offl[0] = 0x00; +  offl[1] = 0x00; +  offl[2] = 0x00; +  turn = 0; + +    bool acceptable = false; +  do { + +        dev->interface->write_registers(regs); + +      for (j=0; j < channels; j++) { +	  off[j] = (offh[j]+offl[j])/2; +          dev->frontend.set_offset(j, off[j]); +      } + +        dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +      DBG(DBG_info, "%s: starting first line reading\n", __func__); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("offset_calibration"); +            return; +        } + +        sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); + +      if (DBG_LEVEL >= DBG_data) { +          char fn[30]; +          std::snprintf(fn, 30, "gl841_offset1_%02d.pnm", turn); +          sanei_genesys_write_pnm_file(fn, first_line.data(), 16, channels, num_pixels, 1); +      } + +        acceptable = true; + +      for (j = 0; j < channels; j++) +      { +	  cmin[j] = 0; +	  cmax[j] = 0; + +	  for (i = 0; i < num_pixels; i++) +	  { +	      if (dev->model->is_cis) +		  val = +		      first_line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		      first_line[i * 2 + j * 2 * num_pixels]; +	      else +		  val = +		      first_line[i * 2 * channels + 2 * j + 1] * 256 + +		      first_line[i * 2 * channels + 2 * j]; +	      if (val < 10) +		  cmin[j]++; +	      if (val > 65525) +		  cmax[j]++; +	  } + +          /* TODO the DP685 has a black strip in the middle of the sensor +           * should be handled in a more elegant way , could be a bug */ +          if (dev->model->sensor_id == SensorId::CCD_DP685) +              cmin[j] -= 20; + +	  if (cmin[j] > num_pixels/100) { +          acceptable = false; +	      if (dev->model->is_cis) +		  offl[0] = off[0]; +	      else +		  offl[j] = off[j]; +	  } +	  if (cmax[j] > num_pixels/100) { +          acceptable = false; +	      if (dev->model->is_cis) +		  offh[0] = off[0]; +	      else +		  offh[j] = off[j]; +	  } +      } + +      DBG(DBG_info,"%s: black/white pixels: %d/%d,%d/%d,%d/%d\n", __func__, cmin[0], cmax[0], +          cmin[1], cmax[1], cmin[2], cmax[2]); + +      if (dev->model->is_cis) { +	  offh[2] = offh[1] = offh[0]; +	  offl[2] = offl[1] = offl[0]; +      } + +        gl841_stop_action(dev); + +      turn++; +  } while (!acceptable && turn < 100); + +  DBG(DBG_info,"%s: acceptable offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]); + + +  for (j = 0; j < channels; j++) +  { +      off1[j] = off[j]; + +      min1[j] = 65536; + +      for (i = 0; i < num_pixels; i++) +      { +	  if (dev->model->is_cis) +	      val = +		  first_line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		  first_line[i * 2 + j * 2 * num_pixels]; +	  else +	      val = +		  first_line[i * 2 * channels + 2 * j + 1] * 256 + +		  first_line[i * 2 * channels + 2 * j]; +	  if (min1[j] > val && val >= 10) +	      min1[j] = val; +      } +  } + + +  offl[0] = off[0]; +  offl[1] = off[0]; +  offl[2] = off[0]; +  turn = 0; + +  do { + +      for (j=0; j < channels; j++) { +	  off[j] = (offh[j]+offl[j])/2; +          dev->frontend.set_offset(j, off[j]); +      } + +        dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +      DBG(DBG_info, "%s: starting second line reading\n", __func__); +        dev->interface->write_registers(regs); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); +        sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +      if (DBG_LEVEL >= DBG_data) { +          char fn[30]; +          std::snprintf(fn, 30, "gl841_offset2_%02d.pnm", turn); +          sanei_genesys_write_pnm_file(fn, second_line.data(), 16, channels, num_pixels, 1); +      } + +        acceptable = true; + +      for (j = 0; j < channels; j++) +      { +	  cmin[j] = 0; +	  cmax[j] = 0; + +	  for (i = 0; i < num_pixels; i++) +	  { +	      if (dev->model->is_cis) +		  val = +		      second_line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		      second_line[i * 2 + j * 2 * num_pixels]; +	      else +		  val = +		      second_line[i * 2 * channels + 2 * j + 1] * 256 + +		      second_line[i * 2 * channels + 2 * j]; +	      if (val < 10) +		  cmin[j]++; +	      if (val > 65525) +		  cmax[j]++; +	  } + +	  if (cmin[j] > num_pixels/100) { +            acceptable = false; +	      if (dev->model->is_cis) +		  offl[0] = off[0]; +	      else +		  offl[j] = off[j]; +	  } +	  if (cmax[j] > num_pixels/100) { +            acceptable = false; +	      if (dev->model->is_cis) +		  offh[0] = off[0]; +	      else +		  offh[j] = off[j]; +	  } +      } + +      DBG(DBG_info, "%s: black/white pixels: %d/%d,%d/%d,%d/%d\n", __func__, cmin[0], cmax[0], +          cmin[1], cmax[1], cmin[2], cmax[2]); + +      if (dev->model->is_cis) { +	  offh[2] = offh[1] = offh[0]; +	  offl[2] = offl[1] = offl[0]; +      } + +        gl841_stop_action(dev); + +      turn++; + +  } while (!acceptable && turn < 100); + +  DBG(DBG_info, "%s: acceptable offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]); + + +  for (j = 0; j < channels; j++) +  { +      off2[j] = off[j]; + +      min2[j] = 65536; + +      for (i = 0; i < num_pixels; i++) +      { +	  if (dev->model->is_cis) +	      val = +		  second_line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		  second_line[i * 2 + j * 2 * num_pixels]; +	  else +	      val = +		  second_line[i * 2 * channels + 2 * j + 1] * 256 + +		  second_line[i * 2 * channels + 2 * j]; +	  if (min2[j] > val && val != 0) +	      min2[j] = val; +      } +  } + +  DBG(DBG_info, "%s: first set: %d/%d,%d/%d,%d/%d\n", __func__, off1[0], min1[0], off1[1], min1[1], +      off1[2], min1[2]); + +  DBG(DBG_info, "%s: second set: %d/%d,%d/%d,%d/%d\n", __func__, off2[0], min2[0], off2[1], min2[1], +      off2[2], min2[2]); + +/* +  calculate offset for each channel +  based on minimal pixel value min1 at offset off1 and minimal pixel value min2 +  at offset off2 + +  to get min at off, values are linearly interpolated: +  min=real+off*fact +  min1=real+off1*fact +  min2=real+off2*fact + +  fact=(min1-min2)/(off1-off2) +  real=min1-off1*(min1-min2)/(off1-off2) + +  off=(min-min1+off1*(min1-min2)/(off1-off2))/((min1-min2)/(off1-off2)) + +  off=(min*(off1-off2)+min1*off2-off1*min2)/(min1-min2) + + */ +  for (j = 0; j < channels; j++) +  { +      if (min2[j]-min1[j] == 0) { +/*TODO: try to avoid this*/ +	  DBG(DBG_warn, "%s: difference too small\n", __func__); +	  if (mintgt * (off1[j] - off2[j]) + min1[j] * off2[j] - min2[j] * off1[j] >= 0) +	      off[j] = 0x0000; +	  else +	      off[j] = 0xffff; +      } else +	  off[j] = (mintgt * (off1[j] - off2[j]) + min1[j] * off2[j] - min2[j] * off1[j])/(min1[j]-min2[j]); +      if (off[j] > 255) +	  off[j] = 255; +      if (off[j] < 0) +	  off[j] = 0; +      dev->frontend.set_offset(j, off[j]); +  } + +  DBG(DBG_info, "%s: final offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]); + +  if (dev->model->is_cis) { +      if (off[0] < off[1]) +	  off[0] = off[1]; +      if (off[0] < off[2]) +	  off[0] = off[2]; +      dev->frontend.set_offset(0, off[0]); +      dev->frontend.set_offset(1, off[0]); +      dev->frontend.set_offset(2, off[0]); +  } + +  if (channels == 1) +    { +      dev->frontend.set_offset(1, dev->frontend.get_offset(0)); +      dev->frontend.set_offset(2, dev->frontend.get_offset(0)); +    } +} + + +/* alternative coarse gain calibration +   this on uses the settings from offset_calibration and +   uses only one scanline + */ +/* +  with offset and coarse calibration we only want to get our input range into +  a reasonable shape. the fine calibration of the upper and lower bounds will +  be done with shading. + */ +void CommandSetGl841::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER_ARGS(dbg, "dpi=%d", dpi); +  int num_pixels; +  int total_size; +  int i, j, channels; +  int max[3]; +  float gain[3]; +  int val; +  int lines=1; +  int move; + +    // feed to white strip if needed +    if (dev->model->y_offset_calib_white > 0) { +        move = static_cast<int>(dev->model->y_offset_calib_white); +        move = static_cast<int>((move * (dev->motor.base_ydpi)) / MM_PER_INCH); +      DBG(DBG_io, "%s: move=%d lines\n", __func__, move); +        gl841_feed(dev, move); +    } + +  /* coarse gain calibration is allways done in color mode */ +  channels = 3; + +    unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres); +    unsigned factor = sensor.optical_res / resolution; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         dev->settings.scan_method); + +    num_pixels = calib_sensor.sensor_pixels / factor; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = lines; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); + +  total_size = num_pixels * channels * 2 * lines;	/* colors * bytes_per_color * scan lines */ + +  std::vector<uint8_t> line(total_size); + +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("coarse_gain_calibration"); +        gl841_stop_action(dev); +        move_back_home(dev, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl841_gain.pnm", line.data(), 16, channels, num_pixels, lines); + +  /* 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 (j = 0; j < channels; j++) +    { +      max[j] = 0; +      for (i = 0; i < num_pixels; i++) +	{ +	  if (dev->model->is_cis) +	      val = +		  line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		  line[i * 2 + j * 2 * num_pixels]; +	  else +	      val = +		  line[i * 2 * channels + 2 * j + 1] * 256 + +		  line[i * 2 * channels + 2 * j]; + +	  if (val > max[j]) +	    max[j] = val; +	} + +        gain[j] = 65535.0f / max[j]; + +      uint8_t out_gain = 0; + +        if (dev->model->adc_id == AdcId::CANON_LIDE_35 || +            dev->model->adc_id == AdcId::WOLFSON_XP300 || +            dev->model->adc_id == AdcId::WOLFSON_DSM600) +        { +        gain[j] *= 0.69f; // seems we don't get the real maximum. empirically derived +	  if (283 - 208/gain[j] > 255) +              out_gain = 255; +	  else if (283 - 208/gain[j] < 0) +              out_gain = 0; +	  else +              out_gain = static_cast<std::uint8_t>(283 - 208 / gain[j]); +        } else if (dev->model->adc_id == AdcId::CANON_LIDE_80) { +              out_gain = static_cast<std::uint8_t>(gain[j] * 12); +        } +      dev->frontend.set_gain(j, out_gain); + +      DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], gain[j], +          out_gain); +    } + +  for (j = 0; j < channels; j++) +    { +      if(gain[j] > 10) +        { +	  DBG (DBG_error0, "**********************************************\n"); +	  DBG (DBG_error0, "**********************************************\n"); +	  DBG (DBG_error0, "****                                      ****\n"); +	  DBG (DBG_error0, "****  Extremely low Brightness detected.  ****\n"); +	  DBG (DBG_error0, "****  Check the scanning head is          ****\n"); +	  DBG (DBG_error0, "****  unlocked and moving.                ****\n"); +	  DBG (DBG_error0, "****                                      ****\n"); +	  DBG (DBG_error0, "**********************************************\n"); +	  DBG (DBG_error0, "**********************************************\n"); +            throw SaneException(SANE_STATUS_JAMMED, "scanning head is locked"); +        } + +    } + +    if (dev->model->is_cis) { +        uint8_t gain0 = dev->frontend.get_gain(0); +        if (gain0 > dev->frontend.get_gain(1)) { +            gain0 = dev->frontend.get_gain(1); +        } +        if (gain0 > dev->frontend.get_gain(2)) { +            gain0 = dev->frontend.get_gain(2); +        } +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); +    } + +    if (channels == 1) { +        dev->frontend.set_gain(0, dev->frontend.get_gain(1)); +        dev->frontend.set_gain(2, dev->frontend.get_gain(1)); +    } + +  DBG(DBG_info, "%s: gain=(%d,%d,%d)\n", __func__, +      dev->frontend.get_gain(0), +      dev->frontend.get_gain(1), +      dev->frontend.get_gain(2)); + +    gl841_stop_action(dev); + +    dev->cmd_set->move_back_home(dev, true); +} + +// wait for lamp warmup by scanning the same line until difference +// between 2 scans is below a threshold +void CommandSetGl841::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* local_reg, int* channels, +                                           int* total_size) const +{ +    DBG_HELPER(dbg); +    int num_pixels = 4 * 300; +  *local_reg = dev->reg; + +/* okay.. these should be defaults stored somewhere */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); +  dev->frontend.set_offset(0, 0x80); +  dev->frontend.set_offset(1, 0x80); +  dev->frontend.set_offset(2, 0x80); + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = dev->settings.yres; +    session.params.startx = sensor.dummy_pixel; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = *channels; +    session.params.scan_method = dev->settings.scan_method; +    if (*channels == 3) { +        session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    } else { +        session.params.scan_mode = ScanColorMode::GRAY; +    } +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, local_reg, session); + +    num_pixels = session.output_pixels; + +  *total_size = num_pixels * 3 * 2 * 1;	/* colors * bytes_per_color * scan lines */ + +    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 sanei_gl841_repark_head(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +    gl841_feed(dev,232); + +    // toggle motor flag, put an huge step number and redo move backward +    dev->cmd_set->move_back_home(dev, true); +} + +/* + * initialize ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + */ +void CommandSetGl841::init(Genesys_Device* dev) const +{ +  size_t size; + +  DBG_INIT (); +    DBG_HELPER(dbg); + +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +  /* Check if the device has already been initialized and powered up */ +  if (dev->already_initialized) +    { +        auto status = scanner_read_status(*dev); +        if (!status.is_replugged) { +            DBG(DBG_info, "%s: already initialized\n", __func__); +            return; +        } +    } + +  dev->dark_average_data.clear(); +  dev->white_average_data.clear(); + +  dev->settings.color_filter = ColorFilter::RED; + +    // ASIC reset +    dev->interface->write_register(0x0e, 0x01); +    dev->interface->write_register(0x0e, 0x00); + +  /* Set default values for registers */ +  gl841_init_registers (dev); + +    // Write initial registers +    dev->interface->write_registers(dev->reg); + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +    // Set analog frontend +    dev->cmd_set->set_fe(dev, sensor, AFE_INIT); + +    // FIXME: move_back_home modifies dev->calib_reg and requires it to be filled +    dev->calib_reg = dev->reg; + +    // Move home +    dev->cmd_set->move_back_home(dev, true); + +    // Init shading data +    sanei_genesys_init_shading_data(dev, sensor, sensor.sensor_pixels); + +  /* ensure head is correctly parked, and check lock */ +  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 +        sanei_gl841_repark_head(dev); +    } + +    // send gamma tables +    dev->cmd_set->send_gamma_table(dev, sensor); + +  /* initial calibration reg values */ +  Genesys_Register_Set& regs = dev->calib_reg; +  regs = dev->reg; + +    unsigned resolution = sensor.get_logical_hwdpi(300); +    unsigned factor = sensor.optical_res / resolution; + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, +                                                         dev->settings.scan_method); + +    unsigned num_pixels = 16 / factor; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = 300; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = 3; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); + +    size = num_pixels * 3 * 2 * 1; // colors * bytes_per_color * scan lines + +  std::vector<uint8_t> line(size); + +  DBG(DBG_info, "%s: starting dummy data reading\n", __func__); +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +  sanei_usb_set_timeout(1000);/* 1 second*/ + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("init"); +    } else { +        // ignore errors. next read will succeed +        catch_all_exceptions(__func__, +                             [&](){ sanei_genesys_read_data_from_scanner(dev, line.data(), size); }); +    } + +  sanei_usb_set_timeout(30 * 1000);/* 30 seconds*/ + +    end_scan(dev, ®s, true); + +  regs = dev->reg; + +    // Set powersaving(default = 15 minutes) +    set_powersaving(dev, 15); +    dev->already_initialized = true; +} + +void CommandSetGl841::update_hardware_sensors(Genesys_Scanner* s) const +{ +    DBG_HELPER(dbg); +  /* do what is needed to get a new set of events, but try to not lose +     any of them. +   */ +  uint8_t val; + +    if (s->dev->model->gpio_id == GpioId::CANON_LIDE_35 +        || s->dev->model->gpio_id == GpioId::CANON_LIDE_80) +    { +        val = s->dev->interface->read_register(REG_0x6D); +        s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0); +        s->buttons[BUTTON_FILE_SW].write((val & 0x02) == 0); +        s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0); +        s->buttons[BUTTON_COPY_SW].write((val & 0x08) == 0); +    } + +    if (s->dev->model->gpio_id == GpioId::XP300 || +        s->dev->model->gpio_id == GpioId::DP665 || +        s->dev->model->gpio_id == GpioId::DP685) +    { +        val = s->dev->interface->read_register(REG_0x6D); + +        s->buttons[BUTTON_PAGE_LOADED_SW].write((val & 0x01) == 0); +        s->buttons[BUTTON_SCAN_SW].write((val & 0x02) == 0); +    } +} + +/** @brief search for a full width black or white strip. + * This function searches for a black or white stripe across the scanning area. + * When searching backward, the searched area must completely be of the desired + * color since this area will be used for calibration which scans forward. + * @param dev scanner device + * @param forward true if searching forward, false if searching backward + * @param black true if searching for a black strip, false for a white strip + */ +void CommandSetGl841::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward, +                                   bool black) const +{ +    DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse"); +  unsigned int pixels, lines, channels; +  Genesys_Register_Set local_reg; +  size_t size; +  unsigned int pass, count, found, x, y, length; +  char title[80]; +  GenesysRegister *r; +  uint8_t white_level=90;  /**< default white level to detect white dots */ +  uint8_t black_level=60;  /**< default black level to detect black dots */ + +  /* use maximum gain when doing forward white strip detection +   * since we don't have calibrated the sensor yet */ +  if(!black && forward) +    { +      dev->frontend.set_gain(0, 0xff); +      dev->frontend.set_gain(1, 0xff); +      dev->frontend.set_gain(2, 0xff); +    } + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); +    gl841_stop_action(dev); + +    // set up for a gray scan at lowest dpi +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method); +    unsigned dpi = resolution_settings.get_min_resolution_x(); +  channels = 1; + +  /* shading calibation is done with dev->motor.base_ydpi */ +  /* lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi; */ +    lines = static_cast<unsigned>((10 * dpi) / MM_PER_INCH); + +  pixels = (sensor.sensor_pixels * dpi) / sensor.optical_res; + +  /* 20 cm max length for calibration sheet */ +    length = static_cast<unsigned>(((200 * dpi) / MM_PER_INCH) / lines); + +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +  local_reg = dev->reg; + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_GAMMA; +    compute_session(dev, session, sensor); + +    size = pixels * channels * lines * (session.params.depth / 8); +    std::vector<uint8_t> data(size); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +  /* set up for reverse or forward */ +  r = sanei_genesys_get_address(&local_reg, 0x02); +    if (forward) { +        r->value &= ~4; +    } else { +        r->value |= 4; +    } + +    dev->interface->write_registers(local_reg); + +    dev->cmd_set->begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_strip"); +        gl841_stop_action(dev); +        return; +    } + +    // waits for valid data +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    gl841_stop_action(dev); + +  pass = 0; +  if (DBG_LEVEL >= DBG_data) +    { +        std::sprintf(title, "gl841_search_strip_%s_%s%02u.pnm", black ? "black" : "white", +                     forward ? "fwd" : "bwd", pass); +      sanei_genesys_write_pnm_file(title, data.data(), session.params.depth, +                                   channels, pixels, lines); +    } + +  /* loop until strip is found or maximum pass number done */ +  found = 0; +  while (pass < length && !found) +    { +        dev->interface->write_registers(local_reg); + +        //now start scan +        dev->cmd_set->begin_scan(dev, sensor, &local_reg, true); + +        // waits for valid data +        wait_until_buffer_non_empty(dev); + +        // now we're on target, we can read data +        sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +        gl841_stop_action (dev); + +      if (DBG_LEVEL >= DBG_data) +	{ +            std::sprintf(title, "gl841_search_strip_%s_%s%02u.pnm", +                         black ? "black" : "white", forward ? "fwd" : "bwd", pass); +          sanei_genesys_write_pnm_file(title, data.data(), session.params.depth, +                                       channels, pixels, 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 < lines && !found; y++) +	    { +	      count = 0; +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +		  if (black && data[y * pixels + x] > white_level) +		    { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +		  if (!black && data[y * pixels + x] < black_level) +		    { +		      count++; +		    } +		} + +	      /* at end of line, if count >= 3%, line is not fully of the desired color +	       * so we must go to next line of the buffer */ +	      /* count*100/pixels < 3 */ +	      if ((count * 100) / pixels < 3) +		{ +		  found = 1; +		  DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__, +		      pass, y); +		} +	      else +		{ +		  DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		      (100 * count) / pixels); +		} +	    } +	} +      else			/* since calibration scans are done forward, we need the whole area +				   to be of the required color when searching backward */ +	{ +	  count = 0; +	  for (y = 0; y < lines; y++) +	    { +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +		  if (black && data[y * pixels + x] > white_level) +		    { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +		  if (!black && data[y * pixels + x] < black_level) +		    { +		      count++; +		    } +		} +	    } + +	  /* at end of area, if count >= 3%, area is not fully of the desired color +	   * so we must go to next buffer */ +	  if ((count * 100) / (pixels * lines) < 3) +	    { +	      found = 1; +	      DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); +	    } +	  else +	    { +	      DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		  (100 * count) / pixels); +	    } +	} +      pass++; +    } + +  if (found) +    { +      DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white"); +    } +  else +    { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white"); +    } +} + +/** + * Send shading calibration data. The buffer is considered to always hold values + * for all the channels. + */ +void CommandSetGl841::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        uint8_t* data, int size) const +{ +    DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size); +  uint32_t length, x, factor, pixels, i; +    uint16_t dpiset, dpihw, beginpixel; +  uint8_t *ptr,*src; + +  /* old method if no SHDAREA */ +    if ((dev->reg.find_reg(0x01).value & REG_0x01_SHDAREA) == 0) { +        dev->interface->write_buffer(0x3c, 0x0000, data, size); +        return; +    } + +  /* data is whole line, we extract only the part for the scanned area */ +    length = static_cast<std::uint32_t>(size / 3); +    unsigned strpixel = dev->session.pixel_startx; +    unsigned endpixel = dev->session.pixel_endx; + +  /* compute deletion/average factor */ +    dpiset = dev->reg.get16(REG_DPISET); +  dpihw = gl841_get_dpihw(dev); +    unsigned ccd_size_divisor = dev->session.ccd_size_divisor; +  factor=dpihw/dpiset; +  DBG(DBG_io2, "%s: dpihw=%d, dpiset=%d, ccd_size_divisor=%d, factor=%d\n", __func__, dpihw, dpiset, +      ccd_size_divisor, factor); + +  /* turn pixel value into bytes 2x16 bits words */ +  strpixel*=2*2; /* 2 words of 2 bytes */ +  endpixel*=2*2; +  pixels=endpixel-strpixel; + +  /* shading pixel begin is start pixel minus start pixel during shading +   * calibration. Currently only cases handled are full and half ccd resolution. +   */ +    beginpixel = sensor.ccd_start_xoffset / ccd_size_divisor; +  beginpixel += sensor.dummy_pixel + 1; +  DBG(DBG_io2, "%s: ORIGIN PIXEL=%d\n", __func__, beginpixel); +  beginpixel = (strpixel-beginpixel*2*2)/factor; +  DBG(DBG_io2, "%s: BEGIN PIXEL=%d\n", __func__, beginpixel/4); + +    dev->interface->record_key_value("shading_offset", std::to_string(beginpixel)); +    dev->interface->record_key_value("shading_pixels", std::to_string(pixels)); +    dev->interface->record_key_value("shading_length", std::to_string(length)); + +  DBG(DBG_io2, "%s: using chunks of %d bytes (%d shading data pixels)\n", __func__, length, +      length/4); +  std::vector<uint8_t> buffer(pixels, 0); + +  /* write actual shading data contigously +   * channel by channel, starting at addr 0x0000 +   * */ +  for(i=0;i<3;i++) +    { +      /* copy data to work buffer and process it */ +          /* coefficent destination */ +      ptr=buffer.data(); + +      /* iterate on both sensor segment, data has been averaged, +       * so is in the right order and we only have to copy it */ +      for(x=0;x<pixels;x+=4) +        { +          /* coefficient source */ +          src=data+x+beginpixel+i*length; +          ptr[0]=src[0]; +          ptr[1]=src[1]; +          ptr[2]=src[2]; +          ptr[3]=src[3]; + +          /* next shading coefficient */ +          ptr+=4; +        } + +        // 0x5400 alignment for LIDE80 internal memory +        dev->interface->write_buffer(0x3c, 0x5400 * i, buffer.data(), pixels); +    } +} + +bool CommandSetGl841::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    (void) dev; +    return true; +} + +void CommandSetGl841::wait_for_motor_stop(Genesys_Device* dev) const +{ +    (void) dev; +} + +void CommandSetGl841::move_to_ta(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl841::asic_boot(Genesys_Device *dev, bool cold) const +{ +    (void) dev; +    (void) cold; +    throw SaneException("not implemented"); +} + +std::unique_ptr<CommandSet> create_gl841_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl841{}); +} + +} // namespace gl841 +} // namespace genesys diff --git a/backend/genesys/gl841.h b/backend/genesys/gl841.h new file mode 100644 index 0000000..5e24249 --- /dev/null +++ b/backend/genesys/gl841.h @@ -0,0 +1,130 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2011-2013 Stéphane Voltz <stef.dev@free.fr> + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#include "genesys.h" +#include "command_set.h" + +#ifndef BACKEND_GENESYS_GL841_H +#define BACKEND_GENESYS_GL841_H + +namespace genesys { +namespace gl841 { + +class CommandSetGl841 : public CommandSet +{ +public: +    ~CommandSetGl841() override = default; + +    bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override; + +    void init(Genesys_Device* dev) const override; + +    void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              Genesys_Register_Set* regs, int* channels, +                              int* total_size) const override; + +    void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs) const override; + +    void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set& regs) const override; + +    void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set* reg, +                                    const ScanSession& session) const override; + +    void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override; +    void set_powersaving(Genesys_Device* dev, int delay) const override; +    void save_power(Genesys_Device* dev, bool enable) const override; + +    void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                    Genesys_Register_Set* regs, bool start_motor) const override; + +    void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override; + +    void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void search_start_position(Genesys_Device* dev) const override; + +    void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set& regs) const override; + +    void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set& regs, int dpi) const override; + +    SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   Genesys_Register_Set& regs) const override; + +    void wait_for_motor_stop(Genesys_Device* dev) const override; + +    void move_back_home(Genesys_Device* dev, bool wait_until_home) const override; + +    void update_hardware_sensors(struct Genesys_Scanner* s) const override; + +    void load_document(Genesys_Device* dev) const override; + +    void detect_document_end(Genesys_Device* dev) const override; + +    void eject_document(Genesys_Device* dev) const override; + +    void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                      bool forward, bool black) const override; + +    void move_to_ta(Genesys_Device* dev) const override; + +    void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data, +                           int size) const override; + +    ScanSession calculate_scan_session(const Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       const Genesys_Settings& settings) const override; + +    void asic_boot(Genesys_Device* dev, bool cold) const override; +}; + +} // namespace gl841 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL841_H diff --git a/backend/genesys/gl841_registers.h b/backend/genesys/gl841_registers.h new file mode 100644 index 0000000..8e0c204 --- /dev/null +++ b/backend/genesys/gl841_registers.h @@ -0,0 +1,269 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL841_REGISTERS_H +#define BACKEND_GENESYS_GL841_REGISTERS_H + +#include <cstdint> + +namespace genesys { +namespace gl841 { + +using RegAddr = std::uint16_t; +using RegMask = std::uint8_t; +using RegShift = unsigned; + +static constexpr RegAddr REG_0x01 = 0x01; +static constexpr RegMask REG_0x01_CISSET = 0x80; +static constexpr RegMask REG_0x01_DOGENB = 0x40; +static constexpr RegMask REG_0x01_DVDSET = 0x20; +static constexpr RegMask REG_0x01_M16DRAM = 0x08; +static constexpr RegMask REG_0x01_DRAMSEL = 0x04; +static constexpr RegMask REG_0x01_SHDAREA = 0x02; +static constexpr RegMask REG_0x01_SCAN = 0x01; + +static constexpr RegAddr REG_0x02 = 0x02; +static constexpr RegMask REG_0x02_NOTHOME = 0x80; +static constexpr RegMask REG_0x02_ACDCDIS = 0x40; +static constexpr RegMask REG_0x02_AGOHOME = 0x20; +static constexpr RegMask REG_0x02_MTRPWR = 0x10; +static constexpr RegMask REG_0x02_FASTFED = 0x08; +static constexpr RegMask REG_0x02_MTRREV = 0x04; +static constexpr RegMask REG_0x02_HOMENEG = 0x02; +static constexpr RegMask REG_0x02_LONGCURV = 0x01; + +static constexpr RegMask REG_0x03_LAMPDOG = 0x80; +static constexpr RegMask REG_0x03_AVEENB = 0x40; +static constexpr RegMask REG_0x03_XPASEL = 0x20; +static constexpr RegMask REG_0x03_LAMPPWR = 0x10; +static constexpr RegMask REG_0x03_LAMPTIM = 0x0f; + +static constexpr RegMask REG_0x04_LINEART = 0x80; +static constexpr RegMask REG_0x04_BITSET = 0x40; +static constexpr RegMask REG_0x04_AFEMOD = 0x30; +static constexpr RegMask REG_0x04_FILTER = 0x0c; +static constexpr RegMask REG_0x04_FESET = 0x03; + +static constexpr RegShift REG_0x04S_AFEMOD = 4; + +static constexpr RegAddr REG_0x05 = 0x05; +static constexpr RegMask REG_0x05_DPIHW = 0xc0; +static constexpr RegMask REG_0x05_DPIHW_600 = 0x00; +static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40; +static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80; +static constexpr RegMask REG_0x05_MTLLAMP = 0x30; +static constexpr RegMask REG_0x05_GMMENB = 0x08; +static constexpr RegMask REG_0x05_MTLBASE = 0x03; + +static constexpr RegAddr REG_0x06 = 0x06; +static constexpr RegMask REG_0x06_SCANMOD = 0xe0; +static constexpr RegShift REG_0x06S_SCANMOD = 5; +static constexpr RegMask REG_0x06_PWRBIT = 0x10; +static constexpr RegMask REG_0x06_GAIN4 = 0x08; +static constexpr RegMask REG_0x06_OPTEST = 0x07; + +static constexpr RegMask REG_0x07_SRAMSEL = 0x08; +static constexpr RegMask REG_0x07_FASTDMA = 0x04; +static constexpr RegMask REG_0x07_DMASEL = 0x02; +static constexpr RegMask REG_0x07_DMARDWR = 0x01; + +static constexpr RegMask REG_0x08_DECFLAG = 0x40; +static constexpr RegMask REG_0x08_GMMFFR = 0x20; +static constexpr RegMask REG_0x08_GMMFFG = 0x10; +static constexpr RegMask REG_0x08_GMMFFB = 0x08; +static constexpr RegMask REG_0x08_GMMZR = 0x04; +static constexpr RegMask REG_0x08_GMMZG = 0x02; +static constexpr RegMask REG_0x08_GMMZB = 0x01; + +static constexpr RegMask REG_0x09_MCNTSET = 0xc0; +static constexpr RegMask REG_0x09_CLKSET = 0x30; +static constexpr RegMask REG_0x09_BACKSCAN = 0x08; +static constexpr RegMask REG_0x09_ENHANCE = 0x04; +static constexpr RegMask REG_0x09_SHORTTG = 0x02; +static constexpr RegMask REG_0x09_NWAIT = 0x01; + +static constexpr RegShift REG_0x09S_MCNTSET = 6; +static constexpr RegShift REG_0x09S_CLKSET = 4; + + +static constexpr RegMask REG_0x0A_SRAMBUF = 0x01; + +static constexpr RegAddr REG_0x0D = 0x0d; +static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01; + +static constexpr RegMask REG_0x16_CTRLHI = 0x80; +static constexpr RegMask REG_0x16_TOSHIBA = 0x40; +static constexpr RegMask REG_0x16_TGINV = 0x20; +static constexpr RegMask REG_0x16_CK1INV = 0x10; +static constexpr RegMask REG_0x16_CK2INV = 0x08; +static constexpr RegMask REG_0x16_CTRLINV = 0x04; +static constexpr RegMask REG_0x16_CKDIS = 0x02; +static constexpr RegMask REG_0x16_CTRLDIS = 0x01; + +static constexpr RegMask REG_0x17_TGMODE = 0xc0; +static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00; +static constexpr RegMask REG_0x17_TGMODE_REF = 0x40; +static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80; +static constexpr RegMask REG_0x17_TGW = 0x3f; +static constexpr RegShift REG_0x17S_TGW = 0; + +static constexpr RegMask REG_0x18_CNSET = 0x80; +static constexpr RegMask REG_0x18_DCKSEL = 0x60; +static constexpr RegMask REG_0x18_CKTOGGLE = 0x10; +static constexpr RegMask REG_0x18_CKDELAY = 0x0c; +static constexpr RegMask REG_0x18_CKSEL = 0x03; + +static constexpr RegMask REG_0x1A_MANUAL3 = 0x02; +static constexpr RegMask REG_0x1A_MANUAL1 = 0x01; +static constexpr RegMask REG_0x1A_CK4INV = 0x08; +static constexpr RegMask REG_0x1A_CK3INV = 0x04; +static constexpr RegMask REG_0x1A_LINECLP = 0x02; + +static constexpr RegMask REG_0x1C_TGTIME = 0x07; + +static constexpr RegMask REG_0x1D_CK4LOW = 0x80; +static constexpr RegMask REG_0x1D_CK3LOW = 0x40; +static constexpr RegMask REG_0x1D_CK1LOW = 0x20; +static constexpr RegMask REG_0x1D_TGSHLD = 0x1f; +static constexpr RegShift REG_0x1DS_TGSHLD = 0; + + +static constexpr RegAddr REG_0x1E = 0x1e; +static constexpr RegMask REG_0x1E_WDTIME = 0xf0; +static constexpr RegShift REG_0x1ES_WDTIME = 4; +static constexpr RegMask REG_0x1E_LINESEL = 0x0f; +static constexpr RegShift REG_0x1ES_LINESEL = 0; + +static constexpr RegAddr REG_EXPR = 0x10; +static constexpr RegAddr REG_EXPG = 0x12; +static constexpr RegAddr REG_EXPB = 0x14; +static constexpr RegAddr REG_STEPNO = 0x21; +static constexpr RegAddr REG_FWDSTEP = 0x22; +static constexpr RegAddr REG_BWDSTEP = 0x23; +static constexpr RegAddr REG_FASTNO = 0x24; +static constexpr RegAddr REG_LINCNT = 0x25; +static constexpr RegAddr REG_DPISET = 0x2c; +static constexpr RegAddr REG_STRPIXEL = 0x30; +static constexpr RegAddr REG_ENDPIXEL = 0x32; +static constexpr RegAddr REG_MAXWD = 0x35; +static constexpr RegAddr REG_LPERIOD = 0x38; + +static constexpr RegAddr REG_0x40 = 0x40; +static constexpr RegMask REG_0x40_HISPDFLG = 0x04; +static constexpr RegMask REG_0x40_MOTMFLG = 0x02; +static constexpr RegMask REG_0x40_DATAENB = 0x01; + +static constexpr RegMask REG_0x41_PWRBIT = 0x80; +static constexpr RegMask REG_0x41_BUFEMPTY = 0x40; +static constexpr RegMask REG_0x41_FEEDFSH = 0x20; +static constexpr RegMask REG_0x41_SCANFSH = 0x10; +static constexpr RegMask REG_0x41_HOMESNR = 0x08; +static constexpr RegMask REG_0x41_LAMPSTS = 0x04; +static constexpr RegMask REG_0x41_FEBUSY = 0x02; +static constexpr RegMask REG_0x41_MOTORENB = 0x01; + +static constexpr RegMask REG_0x58_VSMP = 0xf8; +static constexpr RegShift REG_0x58S_VSMP = 3; +static constexpr RegMask REG_0x58_VSMPW = 0x07; +static constexpr RegShift REG_0x58S_VSMPW = 0; + +static constexpr RegMask REG_0x59_BSMP = 0xf8; +static constexpr RegShift REG_0x59S_BSMP = 3; +static constexpr RegMask REG_0x59_BSMPW = 0x07; +static constexpr RegShift REG_0x59S_BSMPW = 0; + +static constexpr RegMask REG_0x5A_ADCLKINV = 0x80; +static constexpr RegMask REG_0x5A_RLCSEL = 0x40; +static constexpr RegMask REG_0x5A_CDSREF = 0x30; +static constexpr RegShift REG_0x5AS_CDSREF = 4; +static constexpr RegMask REG_0x5A_RLC = 0x0f; +static constexpr RegShift REG_0x5AS_RLC = 0; + +static constexpr RegMask REG_0x5E_DECSEL = 0xe0; +static constexpr RegShift REG_0x5ES_DECSEL = 5; +static constexpr RegMask REG_0x5E_STOPTIM = 0x1f; +static constexpr RegShift REG_0x5ES_STOPTIM = 0; + +static constexpr RegMask REG_0x60_ZIMOD = 0x1f; +static constexpr RegMask REG_0x61_Z1MOD = 0xff; +static constexpr RegMask REG_0x62_Z1MOD = 0xff; + +static constexpr RegMask REG_0x63_Z2MOD = 0x1f; +static constexpr RegMask REG_0x64_Z2MOD = 0xff; +static constexpr RegMask REG_0x65_Z2MOD = 0xff; + +static constexpr RegMask REG_0x67_STEPSEL = 0xc0; +static constexpr RegMask REG_0x67_FULLSTEP = 0x00; +static constexpr RegMask REG_0x67_HALFSTEP = 0x40; +static constexpr RegMask REG_0x67_QUATERSTEP = 0x80; +static constexpr RegMask REG_0x67_MTRPWM = 0x3f; + +static constexpr RegMask REG_0x68_FSTPSEL = 0xc0; +static constexpr RegMask REG_0x68_FULLSTEP = 0x00; +static constexpr RegMask REG_0x68_HALFSTEP = 0x40; +static constexpr RegMask REG_0x68_QUATERSTEP = 0x80; +static constexpr RegMask REG_0x68_FASTPWM = 0x3f; + +static constexpr RegMask REG_0x6B_MULTFILM = 0x80; +static constexpr RegMask REG_0x6B_GPOM13 = 0x40; +static constexpr RegMask REG_0x6B_GPOM12 = 0x20; +static constexpr RegMask REG_0x6B_GPOM11 = 0x10; +static constexpr RegMask REG_0x6B_GPO18 = 0x02; +static constexpr RegMask REG_0x6B_GPO17 = 0x01; + +static constexpr RegAddr REG_0x6B = 0x6b; + +static constexpr RegAddr REG_0x6C = 0x6c; +static constexpr RegMask REG_0x6C_GPIOH = 0xff; +static constexpr RegMask REG_0x6C_GPIOL = 0xff; + +static constexpr RegAddr REG_0x6D = 0x6d; +static constexpr RegAddr REG_0x6E = 0x6e; +static constexpr RegAddr REG_0x6F = 0x6f; + +static constexpr RegMask REG_0x87_LEDADD = 0x04; + +} // namespace gl841 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL841_REGISTERS_H diff --git a/backend/genesys/gl843.cpp b/backend/genesys/gl843.cpp new file mode 100644 index 0000000..f83ac8d --- /dev/null +++ b/backend/genesys/gl843.cpp @@ -0,0 +1,3060 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "gl843_registers.h" +#include "gl843.h" +#include "test_settings.h" + +#include <string> +#include <vector> + +namespace genesys { +namespace gl843 { + +// Set address for writing data +static void gl843_set_buffer_address(Genesys_Device* dev, uint32_t addr) +{ +    DBG_HELPER_ARGS(dbg, "setting address to 0x%05x", addr & 0xffff); + +    dev->interface->write_register(0x5b, ((addr >> 8) & 0xff)); +    dev->interface->write_register(0x5c, (addr & 0xff)); +} + +/** + * compute the step multiplier used + */ +static int gl843_get_step_multiplier(Genesys_Register_Set* regs) +{ +    GenesysRegister *r = sanei_genesys_get_address(regs, REG_0x9D); +    int value = 1; +  if (r != nullptr) +    { +      switch (r->value & 0x0c) +	{ +	case 0x04: +	  value = 2; +	  break; +	case 0x08: +	  value = 4; +	  break; +	default: +	  value = 1; +	} +    } +  DBG(DBG_io, "%s: step multiplier is %d\n", __func__, value); +  return value; +} + +/** copy sensor specific settings */ +static void gl843_setup_sensor(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set* regs) +{ +    DBG_HELPER(dbg); +    for (const auto& custom_reg : sensor.custom_regs) { +        regs->set8(custom_reg.address, custom_reg.value); +    } +    if (!(dev->model->flags & GENESYS_FLAG_FULL_HWDPI_MODE) && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300 && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        regs->set8(0x7d, 0x90); +    } + +    dev->segment_order = sensor.segment_order; +} + + +/** @brief set all registers to default values . + * This function is called only once at the beginning and + * fills register startup values for registers reused across scans. + * Those that are rarely modified or not modified are written + * individually. + * @param dev device structure holding register set to initialize + */ +static void +gl843_init_registers (Genesys_Device * dev) +{ +  // Within this function SENSOR_DEF marker documents that a register is part +  // of the sensors definition and the actual value is set in +  // gl843_setup_sensor(). + +    // 0x6c, 0x6d, 0x6e, 0x6f, 0xa6, 0xa7, 0xa8, 0xa9 are defined in the Gpo sensor struct + +    DBG_HELPER(dbg); + +    dev->reg.clear(); + +    dev->reg.init_reg(0x01, 0x00); +    dev->reg.init_reg(0x02, 0x78); +    dev->reg.init_reg(0x03, 0x1f); +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x03, 0x1d); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x03, 0x1c); +    } + +    dev->reg.init_reg(0x04, 0x10); +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x04, 0x22); +    } + +    // fine tune upon device description +    dev->reg.init_reg(0x05, 0x80); +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +      dev->reg.init_reg(0x05, 0x08); +    } + +    const auto& sensor = sanei_genesys_find_sensor_any(dev); +    sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res); + +    // TODO: on 8600F the windows driver turns off GAIN4 which is recommended +    dev->reg.init_reg(0x06, 0xd8); /* SCANMOD=110, PWRBIT and GAIN4 */ +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x06, 0xd8); /* SCANMOD=110, PWRBIT and GAIN4 */ +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) { +        dev->reg.init_reg(0x06, 0xd0); +    } +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x06, 0xf0); /* SCANMOD=111, PWRBIT and no GAIN4 */ +    } + +  dev->reg.init_reg(0x08, 0x00); +  dev->reg.init_reg(0x09, 0x00); +  dev->reg.init_reg(0x0a, 0x00); +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x0a, 0x18); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x0a, 0x10); +    } + +    // This register controls clock and RAM settings and is further modified in +    // gl843_boot +    dev->reg.init_reg(0x0b, 0x6a); + +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x0b, 0x69); // 16M only +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x0b, 0x89); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) { +        dev->reg.init_reg(0x0b, 0x2a); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) { +        dev->reg.init_reg(0x0b, 0x4a); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x0b, 0x69); +    } + +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +        dev->reg.init_reg(0x0c, 0x00); +    } + +    // EXPR[0:15], EXPG[0:15], EXPB[0:15]: Exposure time settings. +    dev->reg.init_reg(0x10, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x11, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x12, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x13, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x14, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x15, 0x00); // SENSOR_DEF +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.set16(REG_EXPR, 0x9c40); +        dev->reg.set16(REG_EXPG, 0x9c40); +        dev->reg.set16(REG_EXPB, 0x9c40); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.set16(REG_EXPR, 0x2c09); +        dev->reg.set16(REG_EXPG, 0x22b8); +        dev->reg.set16(REG_EXPB, 0x10f0); +    } + +    // CCD signal settings. +    dev->reg.init_reg(0x16, 0x33); // SENSOR_DEF +    dev->reg.init_reg(0x17, 0x1c); // SENSOR_DEF +    dev->reg.init_reg(0x18, 0x10); // SENSOR_DEF + +    // EXPDMY[0:7]: Exposure time of dummy lines. +    dev->reg.init_reg(0x19, 0x2a); // SENSOR_DEF + +    // Various CCD clock settings. +    dev->reg.init_reg(0x1a, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x1c, 0x20); // SENSOR_DEF +    dev->reg.init_reg(0x1d, 0x04); // SENSOR_DEF + +    dev->reg.init_reg(0x1e, 0x10); +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0x1e, 0x20); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x1e, 0xa0); +    } + +    dev->reg.init_reg(0x1f, 0x01); +    if (dev->model->model_id == ModelId::CANON_8600F) { +      dev->reg.init_reg(0x1f, 0xff); +    } + +    dev->reg.init_reg(0x20, 0x10); +    dev->reg.init_reg(0x21, 0x04); + +    dev->reg.init_reg(0x22, 0x10); +    dev->reg.init_reg(0x23, 0x10); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x22, 0xc8); +        dev->reg.init_reg(0x23, 0xc8); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x22, 0x50); +        dev->reg.init_reg(0x23, 0x50); +    } + +    dev->reg.init_reg(0x24, 0x04); +    dev->reg.init_reg(0x25, 0x00); +    dev->reg.init_reg(0x26, 0x00); +    dev->reg.init_reg(0x27, 0x00); +    dev->reg.init_reg(0x2c, 0x02); +    dev->reg.init_reg(0x2d, 0x58); +    // BWHI[0:7]: high level of black and white threshold +    dev->reg.init_reg(0x2e, 0x80); +    // BWLOW[0:7]: low level of black and white threshold +    dev->reg.init_reg(0x2f, 0x80); +    dev->reg.init_reg(0x30, 0x00); +    dev->reg.init_reg(0x31, 0x14); +    dev->reg.init_reg(0x32, 0x27); +    dev->reg.init_reg(0x33, 0xec); + +    // DUMMY: CCD dummy and optically black pixel count +    dev->reg.init_reg(0x34, 0x24); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x34, 0x14); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x34, 0x3c); +    } + +    // MAXWD: If available buffer size is less than 2*MAXWD words, then +    // "buffer full" state will be set. +    dev->reg.init_reg(0x35, 0x00); +    dev->reg.init_reg(0x36, 0xff); +    dev->reg.init_reg(0x37, 0xff); + +    // LPERIOD: Line period or exposure time for CCD or CIS. +    dev->reg.init_reg(0x38, 0x55); // SENSOR_DEF +    dev->reg.init_reg(0x39, 0xf0); // SENSOR_DEF + +    // FEEDL[0:24]: The number of steps of motor movement. +    dev->reg.init_reg(0x3d, 0x00); +    dev->reg.init_reg(0x3e, 0x00); +    dev->reg.init_reg(0x3f, 0x01); + +    // Latch points for high and low bytes of R, G and B channels of AFE. If +    // multiple clocks per pixel are consumed, then the setting defines during +    // which clock the corresponding value will be read. +    // RHI[0:4]: The latch point for high byte of R channel. +    // RLOW[0:4]: The latch point for low byte of R channel. +    // GHI[0:4]: The latch point for high byte of G channel. +    // GLOW[0:4]: The latch point for low byte of G channel. +    // BHI[0:4]: The latch point for high byte of B channel. +    // BLOW[0:4]: The latch point for low byte of B channel. +    dev->reg.init_reg(0x52, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x53, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x54, 0x07); // SENSOR_DEF +    dev->reg.init_reg(0x55, 0x0a); // SENSOR_DEF +    dev->reg.init_reg(0x56, 0x0d); // SENSOR_DEF +    dev->reg.init_reg(0x57, 0x10); // SENSOR_DEF + +    // VSMP[0:4]: The position of the image sampling pulse for AFE in cycles. +    // VSMPW[0:2]: The length of the image sampling pulse for AFE in cycles. +    dev->reg.init_reg(0x58, 0x1b); // SENSOR_DEF + +    dev->reg.init_reg(0x59, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x5a, 0x40); // SENSOR_DEF + +    // 0x5b-0x5c: GMMADDR[0:15] address for gamma or motor tables download +    // SENSOR_DEF + +    // DECSEL[0:2]: The number of deceleration steps after touching home sensor +    // STOPTIM[0:4]: The stop duration between change of directions in +    // backtracking +    dev->reg.init_reg(0x5e, 0x23); +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x5e, 0x3f); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x5e, 0x85); +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x5e, 0x1f); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x5e, 0x01); +    } + +    //FMOVDEC: The number of deceleration steps in table 5 for auto-go-home +    dev->reg.init_reg(0x5f, 0x01); +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x5f, 0xf0); +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x5f, 0xf0); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x5f, 0x01); +    } + +    // Z1MOD[0:20] +    dev->reg.init_reg(0x60, 0x00); +    dev->reg.init_reg(0x61, 0x00); +    dev->reg.init_reg(0x62, 0x00); + +    // Z2MOD[0:20] +    dev->reg.init_reg(0x63, 0x00); +    dev->reg.init_reg(0x64, 0x00); +    dev->reg.init_reg(0x65, 0x00); + +    // STEPSEL[0:1]. Motor movement step mode selection for tables 1-3 in +    // scanning mode. +    // MTRPWM[0:5]. Motor phase PWM duty cycle setting for tables 1-3 +    dev->reg.init_reg(0x67, 0x7f); +    // FSTPSEL[0:1]: Motor movement step mode selection for tables 4-5 in +    // command mode. +    // FASTPWM[5:0]: Motor phase PWM duty cycle setting for tables 4-5 +    dev->reg.init_reg(0x68, 0x7f); + +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300) { +        dev->reg.init_reg(0x67, 0x80); +        dev->reg.init_reg(0x68, 0x80); +    } + +    // FSHDEC[0:7]: The number of deceleration steps after scanning is finished +    // (table 3) +    dev->reg.init_reg(0x69, 0x01); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x69, 64); +    } + +    // FMOVNO[0:7] The number of acceleration or deceleration steps for fast +    // moving (table 4) +    dev->reg.init_reg(0x6a, 0x04); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x69, 64); +    } + +    // GPIO-related register bits +    dev->reg.init_reg(0x6b, 0x30); +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0x6b, 0x72); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x6b, 0xb1); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x6b, 0xf4); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x6b, 0x31); +    } + +    // 0x6c, 0x6d, 0x6e, 0x6f are set according to gpio tables. See +    // gl843_init_gpio. + +    // RSH[0:4]: The position of rising edge of CCD RS signal in cycles +    // RSL[0:4]: The position of falling edge of CCD RS signal in cycles +    // CPH[0:4]: The position of rising edge of CCD CP signal in cycles. +    // CPL[0:4]: The position of falling edge of CCD CP signal in cycles +    dev->reg.init_reg(0x70, 0x01); // SENSOR_DEF +    dev->reg.init_reg(0x71, 0x03); // SENSOR_DEF +    dev->reg.init_reg(0x72, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x73, 0x05); // SENSOR_DEF + +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x70, 0x01); +        dev->reg.init_reg(0x71, 0x03); +        dev->reg.init_reg(0x72, 0x01); +        dev->reg.init_reg(0x73, 0x03); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x70, 0x01); +        dev->reg.init_reg(0x71, 0x03); +        dev->reg.init_reg(0x72, 0x03); +        dev->reg.init_reg(0x73, 0x04); +    } +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x70, 0x00); +        dev->reg.init_reg(0x71, 0x02); +        dev->reg.init_reg(0x72, 0x02); +        dev->reg.init_reg(0x73, 0x04); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x70, 0x00); +        dev->reg.init_reg(0x71, 0x02); +        dev->reg.init_reg(0x72, 0x00); +        dev->reg.init_reg(0x73, 0x00); +    } + +    // CK1MAP[0:17], CK3MAP[0:17], CK4MAP[0:17]: CCD clock bit mapping setting. +    dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF +    dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF +    dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF + +    // various AFE settings +    dev->reg.init_reg(0x7d, 0x00); +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x7d, 0x20); +    } + +    // GPOLED[x]: LED vs GPIO settings +    dev->reg.init_reg(0x7e, 0x00); + +    // BSMPDLY, VSMPDLY +    // LEDCNT[0:1]: Controls led blinking and its period +    dev->reg.init_reg(0x7f, 0x00); + +    // VRHOME, VRMOVE, VRBACK, VRSCAN: Vref settings of the motor driver IC for +    // moving in various situations. +    dev->reg.init_reg(0x80, 0x00); +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0x80, 0x0c); +    } +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->reg.init_reg(0x80, 0x28); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x80, 0x50); +    } +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x80, 0x0f); +    } + +    if (dev->model->model_id != ModelId::CANON_4400F) { +        dev->reg.init_reg(0x81, 0x00); +        dev->reg.init_reg(0x82, 0x00); +        dev->reg.init_reg(0x83, 0x00); +        dev->reg.init_reg(0x84, 0x00); +        dev->reg.init_reg(0x85, 0x00); +        dev->reg.init_reg(0x86, 0x00); +    } + +    dev->reg.init_reg(0x87, 0x00); +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0x87, 0x02); +    } + +    // MTRPLS[0:7]: The width of the ADF motor trigger signal pulse. +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +        dev->reg.init_reg(0x94, 0xff); +    } + +    // 0x95-0x97: SCANLEN[0:19]: Controls when paper jam bit is set in sheetfed +    // scanners. + +    // ONDUR[0:15]: The duration of PWM ON phase for LAMP control +    // OFFDUR[0:15]: The duration of PWM OFF phase for LAMP control +    // both of the above are in system clocks +    if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->reg.init_reg(0x98, 0x00); +        dev->reg.init_reg(0x99, 0x00); +        dev->reg.init_reg(0x9a, 0x00); +        dev->reg.init_reg(0x9b, 0x00); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        // TODO: move to set for scan +        dev->reg.init_reg(0x98, 0x03); +        dev->reg.init_reg(0x99, 0x30); +        dev->reg.init_reg(0x9a, 0x01); +        dev->reg.init_reg(0x9b, 0x80); +    } + +    // RMADLY[0:1], MOTLAG, CMODE, STEPTIM, MULDMYLN, IFRS +    dev->reg.init_reg(0x9d, 0x04); +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->reg.init_reg(0x9d, 0x00); +    } +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8400F || +        dev->model->model_id == ModelId::CANON_8600F || +        dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +        dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0x9d, 0x08); // sets the multiplier for slope tables +    } + + +    // SEL3INV, TGSTIME[0:2], TGWTIME[0:2] +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +      dev->reg.init_reg(0x9e, 0x00); // SENSOR_DEF +    } + +    if (dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) { +        dev->reg.init_reg(0xa2, 0x0f); +    } + +    // RFHSET[0:4]: Refresh time of SDRAM in units of 2us +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        dev->reg.init_reg(0xa2, 0x1f); +    } + +    // 0xa6-0xa9: controls gpio, see gl843_gpio_init + +    // not documented +    if (dev->model->model_id != ModelId::CANON_4400F && +        dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) +    { +        dev->reg.init_reg(0xaa, 0x00); +    } + +    // GPOM9, MULSTOP[0-2], NODECEL, TB3TB1, TB5TB2, FIX16CLK. Not documented +    if (dev->model->model_id != ModelId::CANON_8400F && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I && +        dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) { +        dev->reg.init_reg(0xab, 0x50); +    } +    if (dev->model->model_id == ModelId::CANON_4400F) { +        dev->reg.init_reg(0xab, 0x00); +    } +    if (dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        // BUG: this should apply to ModelId::CANON_CANOSCAN_8600F too, but due to previous bug +        // the 8400F case overwrote it +        dev->reg.init_reg(0xab, 0x40); +    } + +    // VRHOME[3:2], VRMOVE[3:2], VRBACK[3:2]: Vref setting of the motor driver IC +    // for various situations. +    if (dev->model->model_id == ModelId::CANON_8600F || +        dev->model->model_id == ModelId::HP_SCANJET_G4010 || +        dev->model->model_id == ModelId::HP_SCANJET_G4050 || +        dev->model->model_id == ModelId::HP_SCANJET_4850C) +    { +        dev->reg.init_reg(0xac, 0x00); +    } + +    dev->calib_reg = dev->reg; + +    if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) { +        uint8_t data[32] = { +            0x8c, 0x8f, 0xc9, 0x00, 0x01, 0x00, 0x00, 0x00, +            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +            0x6a, 0x73, 0x63, 0x68, 0x69, 0x65, 0x6e, 0x00, +        }; + +        dev->interface->write_buffer(0x3c, 0x3ff000, data, 32, +                                     ScannerInterface::FLAG_SWAP_REGISTERS); +    } +} + +// Send slope table for motor movement slope_table in machine byte order +static void gl843_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps) +{ +    DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps); + +  int i; +  char msg[10000]; + +  std::vector<uint8_t> table(steps * 2); +  for (i = 0; i < steps; i++) +    { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +    } + +  if (DBG_LEVEL >= DBG_io) +    { +        std::sprintf(msg, "write slope %d (%d)=", table_nr, steps); +        for (i = 0; i < steps; i++) { +            std::sprintf (msg+strlen(msg), "%d", slope_table[i]); +	} +      DBG(DBG_io, "%s: %s\n", __func__, msg); +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } + +    // slope table addresses are fixed : 0x40000,  0x48000,  0x50000,  0x58000,  0x60000 +    // XXX STEF XXX USB 1.1 ? sanei_genesys_write_0x8c (dev, 0x0f, 0x14); +    dev->interface->write_gamma(0x28,  0x40000 + 0x8000 * table_nr, table.data(), steps * 2, +                                ScannerInterface::FLAG_SWAP_REGISTERS); + +    // FIXME: remove this when updating tests +    gl843_set_buffer_address(dev, 0); +} + +static void gl843_set_ad_fe(Genesys_Device* dev) +{ +    for (const auto& reg : dev->frontend.regs) { +        dev->interface->write_fe_register(reg.address, reg.value); +    } +} + +// Set values of analog frontend +void CommandSetGl843::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" : +                               set == AFE_SET ? "set" : +                               set == AFE_POWER_SAVE ? "powersave" : "huh?"); +    (void) sensor; +  int i; + +  if (set == AFE_INIT) +    { +        DBG(DBG_proc, "%s(): setting DAC %u\n", __func__, +            static_cast<unsigned>(dev->model->adc_id)); +      dev->frontend = dev->frontend_initial; +        dev->frontend_is_init = true; +    } + +    // check analog frontend type +    // FIXME: looks like we write to that register with initial data +    uint8_t fe_type = dev->interface->read_register(REG_0x04) & REG_0x04_FESET; +    if (fe_type == 2) { +        gl843_set_ad_fe(dev); +        return; +    } +    if (fe_type != 0) { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "unsupported frontend type %d", fe_type); +    } + +  DBG(DBG_proc, "%s(): frontend reset complete\n", __func__); + +  for (i = 1; i <= 3; i++) +    { +        // FIXME: the check below is just historical artifact, we can remove it when convenient +        if (!dev->frontend_is_init) { +            dev->interface->write_fe_register(i, 0x00); +        } else { +            dev->interface->write_fe_register(i, dev->frontend.regs.get_value(0x00 + i)); +        } +    } +    for (const auto& reg : sensor.custom_fe_regs) { +        dev->interface->write_fe_register(reg.address, reg.value); +    } + +  for (i = 0; i < 3; i++) +    { +         // FIXME: the check below is just historical artifact, we can remove it when convenient +        if (!dev->frontend_is_init) { +            dev->interface->write_fe_register(0x20 + i, 0x00); +        } else { +            dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i)); +        } +    } + +    if (dev->model->sensor_id == SensorId::CCD_KVSS080) { +      for (i = 0; i < 3; i++) +	{ +            // FIXME: the check below is just historical artifact, we can remove it when convenient +            if (!dev->frontend_is_init) { +                dev->interface->write_fe_register(0x24 + i, 0x00); +            } else { +                dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i)); +            } +	} +    } + +  for (i = 0; i < 3; i++) +    { +        // FIXME: the check below is just historical artifact, we can remove it when convenient +        if (!dev->frontend_is_init) { +            dev->interface->write_fe_register(0x28 + i, 0x00); +        } else { +            dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i)); +        } +    } +} + + +static void gl843_init_motor_regs_scan(Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       Genesys_Register_Set* reg, +                                       const Motor_Profile& motor_profile, +                                       unsigned int exposure, +                                       unsigned scan_yres, +                                       unsigned int scan_lines, +                                       unsigned int scan_dummy, +                                       unsigned int feed_steps, +                                       MotorFlag flags) +{ +    DBG_HELPER_ARGS(dbg, "exposure=%d, scan_yres=%d, step_type=%d, scan_lines=%d, scan_dummy=%d, " +                         "feed_steps=%d, flags=%x", +                    exposure, scan_yres, static_cast<unsigned>(motor_profile.step_type), +                    scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags)); + +  int use_fast_fed, coeff; +  unsigned int lincnt; +    unsigned feedl, dist; +  GenesysRegister *r; +  uint32_t z1, z2; + +  /* get step multiplier */ +    unsigned step_multiplier = gl843_get_step_multiplier (reg); + +  use_fast_fed = 0; + +    if ((scan_yres >= 300 && feed_steps > 900) || (has_flag(flags, MotorFlag::FEED))) { +        use_fast_fed = 1; +    } + +  lincnt=scan_lines; +    reg->set24(REG_LINCNT, lincnt); +  DBG(DBG_io, "%s: lincnt=%d\n", __func__, lincnt); + +  /* compute register 02 value */ +    r = sanei_genesys_get_address(reg, REG_0x02); +  r->value = 0x00; +  sanei_genesys_set_motor_power(*reg, true); + +    if (use_fast_fed) { +        r->value |= REG_0x02_FASTFED; +    } else { +        r->value &= ~REG_0x02_FASTFED; +    } + +  /* in case of automatic go home, move until home sensor */ +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME; +    } + +  /* disable backtracking */ +    if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) +      ||(scan_yres>=2400 && dev->model->model_id != ModelId::CANON_4400F) +      ||(scan_yres>=sensor.optical_res)) +    { +        r->value |= REG_0x02_ACDCDIS; +    } + +    if (has_flag(flags, MotorFlag::REVERSE)) { +        r->value |= REG_0x02_MTRREV; +    } else { +        r->value &= ~REG_0x02_MTRREV; +    } + +  /* scan and backtracking slope table */ +    auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres, exposure, +                                                dev->motor.base_ydpi, step_multiplier, +                                                motor_profile); + +    gl843_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count); +    gl843_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count); + +    reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier); + +    // fast table +    unsigned fast_yres = sanei_genesys_get_lowest_ydpi(dev); +    auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_yres, exposure, +                                                dev->motor.base_ydpi, step_multiplier, +                                                motor_profile); +    gl843_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count); +    gl843_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count); +    gl843_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count); + +    reg->set8(REG_FSHDEC, fast_table.steps_count / step_multiplier); +    reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier); + +  /* substract acceleration distance from feedl */ +  feedl=feed_steps; +    feedl <<= static_cast<unsigned>(motor_profile.step_type); + +    dist = scan_table.steps_count / step_multiplier; +  if (use_fast_fed) +    { +        dist += (fast_table.steps_count / step_multiplier) * 2; +    } +  DBG(DBG_io2, "%s: acceleration distance=%d\n", __func__, dist); + +  /* get sure when don't insane value : XXX STEF XXX in this case we should +   * fall back to single table move */ +    if (dist < feedl) { +        feedl -= dist; +    } else { +        feedl = 1; +    } + +    reg->set24(REG_FEEDL, feedl); +  DBG(DBG_io, "%s: feedl=%d\n", __func__, feedl); + +  /* doesn't seem to matter that much */ +    sanei_genesys_calculate_zmod(use_fast_fed, +				  exposure, +                                 scan_table.table, +                                 scan_table.steps_count / step_multiplier, +				  feedl, +                                 scan_table.steps_count / step_multiplier, +                                  &z1, +                                  &z2); +  if(scan_yres>600) +    { +      z1=0; +      z2=0; +    } + +    reg->set24(REG_Z1MOD, z1); +  DBG(DBG_info, "%s: z1 = %d\n", __func__, z1); + +    reg->set24(REG_Z2MOD, z2); +  DBG(DBG_info, "%s: z2 = %d\n", __func__, z2); + +    r = sanei_genesys_get_address(reg, REG_0x1E); +  r->value &= 0xf0;		/* 0 dummy lines */ +  r->value |= scan_dummy;	/* dummy lines */ + +    reg->set8_mask(REG_0x67, static_cast<unsigned>(motor_profile.step_type) << REG_0x67S_STEPSEL, 0xc0); +    reg->set8_mask(REG_0x68, static_cast<unsigned>(motor_profile.step_type) << REG_0x68S_FSTPSEL, 0xc0); + +    // steps for STOP table +    reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier); + +  /* Vref XXX STEF XXX : optical divider or step type ? */ +  r = sanei_genesys_get_address (reg, 0x80); +  if (!(dev->model->flags & GENESYS_FLAG_FULL_HWDPI_MODE)) +    { +      r->value = 0x50; +        coeff = sensor.get_hwdpi_divisor_for_dpi(scan_yres); +        if (dev->model->motor_id == MotorId::KVSS080) { +          if(coeff>=1) +            { +              r->value |= 0x05; +            } +        } +      else { +        switch(coeff) +          { +          case 4: +              r->value |= 0x0a; +              break; +          case 2: +              r->value |= 0x0f; +              break; +          case 1: +              r->value |= 0x0f; +              break; +          } +        } +    } +} + + +/** @brief setup optical related registers + * start and pixels are expressed in optical sensor resolution coordinate + * space. + * @param dev device to use + * @param reg registers to set up + * @param exposure exposure time to use + * @param used_res scanning resolution used, may differ from + *        scan's one + * @param start logical start pixel coordinate + * @param pixels logical number of pixels to use + * @param channels number of color channles used (1 or 3) + * @param depth bit depth of the scan (1, 8 or 16 bits) + * @param ccd_size_divisor true specifies how much x coordinates must be shrunk + * @param color_filter to choose the color channel used in gray scans + * @param flags to drive specific settings such no calibration, XPA use ... + */ +static void gl843_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set* reg, unsigned int exposure, +                                         const ScanSession& session) +{ +    DBG_HELPER_ARGS(dbg, "exposure=%d", exposure); +    unsigned int dpihw; +  unsigned int tgtime;          /**> exposure time multiplier */ +  GenesysRegister *r; + +  /* tgtime */ +  tgtime = exposure / 65536 + 1; +  DBG(DBG_io2, "%s: tgtime=%d\n", __func__, tgtime); + +    // to manage high resolution device while keeping good low resolution scanning speed, we make +    // hardware dpi vary +    dpihw = sensor.get_register_hwdpi(session.output_resolution); +    DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw); + +  /* sensor parameters */ +    gl843_setup_sensor(dev, sensor, reg); + +    // resolution is divided according to CKSEL +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); +    DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel); + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); + +  /* enable shading */ +    regs_set_optical_off(dev->model->asic_type, *reg); +    r = sanei_genesys_get_address (reg, REG_0x01); +    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) || +        (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION || +        (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE))) +    { +        r->value &= ~REG_0x01_DVDSET; +    } else { +        r->value |= REG_0x01_DVDSET; +    } + +    bool use_shdarea = dpihw > 600; +    if (dev->model->model_id == ModelId::CANON_4400F) { +        use_shdarea = session.params.xres <= 600; +    } else if (dev->model->model_id == ModelId::CANON_8400F) { +        use_shdarea = session.params.xres <= 400; +    } +    if (use_shdarea) { +        r->value |= REG_0x01_SHDAREA; +    } else { +        r->value &= ~REG_0x01_SHDAREA; +    } + +    r = sanei_genesys_get_address (reg, REG_0x03); +    if (dev->model->model_id == ModelId::CANON_8600F) { +        r->value |= REG_0x03_AVEENB; +    } else { +        r->value &= ~REG_0x03_AVEENB; +  } + +    // FIXME: we probably don't need to set exposure to registers at this point. It was this way +    // before a refactor. +    sanei_genesys_set_lamp_power(dev, sensor, *reg, +                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP)); + +  /* select XPA */ +    r->value &= ~REG_0x03_XPASEL; +    if (has_flag(session.params.flags, ScanFlag::USE_XPA)) { +        r->value |= REG_0x03_XPASEL; +    } +    reg->state.is_xpa_on = has_flag(session.params.flags, ScanFlag::USE_XPA); + +  /* BW threshold */ +    r = sanei_genesys_get_address(reg, REG_0x2E); +  r->value = dev->settings.threshold; +    r = sanei_genesys_get_address(reg, REG_0x2F); +  r->value = dev->settings.threshold; + +  /* monochrome / color scan */ +    r = sanei_genesys_get_address(reg, REG_0x04); +    switch (session.params.depth) { +    case 8: +            r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +      break; +    case 16: +            r->value &= ~REG_0x04_LINEART; +            r->value |= REG_0x04_BITSET; +      break; +    } + +    r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD); +  if (session.params.channels == 1) +    { +      switch (session.params.color_filter) +	{ +            case ColorFilter::RED: +                r->value |= 0x14; +                break; +            case ColorFilter::BLUE: +                r->value |= 0x1c; +                break; +            case ColorFilter::GREEN: +                r->value |= 0x18; +                break; +            default: +                break; // should not happen +	} +    } else { +        switch (dev->frontend.layout.type) { +            case FrontendType::WOLFSON: +                r->value |= 0x10; // pixel by pixel +                break; +            case FrontendType::ANALOG_DEVICES: +                r->value |= 0x20; // slow color pixel by pixel +                break; +            default: +                throw SaneException("Invalid frontend type %d", +                                    static_cast<unsigned>(dev->frontend.layout.type)); +        } +    } + +    sanei_genesys_set_dpihw(*reg, sensor, dpihw); + +    if (should_enable_gamma(session, sensor)) { +        reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB; +    } else { +        reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB; +    } + +    unsigned dpiset = session.output_resolution * session.ccd_size_divisor * +            ccd_pixels_per_system_pixel; + +    if (sensor.dpiset_override != 0) { +        dpiset = sensor.dpiset_override; +    } +    reg->set16(REG_DPISET, dpiset); +    DBG(DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset); + +    reg->set16(REG_STRPIXEL, session.pixel_startx); +    reg->set16(REG_ENDPIXEL, session.pixel_endx); + +  /* MAXWD is expressed in 2 words unit */ +  /* nousedspace = (mem_bank_range * 1024 / 256 -1 ) * 4; */ +    // BUG: the division by ccd_size_divisor likely does not make sense +    reg->set24(REG_MAXWD, (session.output_line_bytes / session.ccd_size_divisor) >> 1); + +    reg->set16(REG_LPERIOD, exposure / tgtime); +  DBG(DBG_io2, "%s: exposure used=%d\n", __func__, exposure/tgtime); + +  r = sanei_genesys_get_address (reg, REG_DUMMY); +  r->value = sensor.dummy_pixel; +} + +void CommandSetGl843::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* reg, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +  int exposure; + +  int slope_dpi = 0; +  int dummy = 0; + +  /* we enable true gray for cis scanners only, and just when doing +   * scan since color calibration is OK for this mode +   */ + +  dummy = 0; +    if (dev->model->model_id == ModelId::CANON_4400F && session.params.yres == 1200) { +        dummy = 1; +    } + +  /* slope_dpi */ +  /* cis color scan is effectively a gray scan with 3 gray lines per color line and a FILTER of 0 */ +  if (dev->model->is_cis) +    slope_dpi = session.params.yres * session.params.channels; +  else +    slope_dpi = session.params.yres; +  slope_dpi = slope_dpi * (1 + dummy); + +  /* scan_step_type */ +  exposure = sensor.exposure_lperiod; +  if (exposure < 0) { +      throw std::runtime_error("Exposure not defined in sensor definition"); +  } +    const auto& motor_profile = sanei_genesys_get_motor_profile(*gl843_motor_profiles, +                                                                dev->model->motor_id, +                                                                exposure); + +  DBG(DBG_info, "%s : exposure=%d pixels\n", __func__, exposure); +    DBG(DBG_info, "%s : scan_step_type=%d\n", __func__, +        static_cast<unsigned>(motor_profile.step_type)); + +    // now _LOGICAL_ optical values used are known, setup registers +    gl843_init_optical_regs_scan(dev, sensor, reg, exposure, session); + +  /*** motor parameters ***/ +    MotorFlag mflags = MotorFlag::NONE; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) { +        mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE; +    } +    if (has_flag(session.params.flags, ScanFlag::FEEDING)) { +        mflags |= MotorFlag::FEED; +    } +    if (has_flag(session.params.flags, ScanFlag::USE_XPA)) { +        mflags |= MotorFlag::USE_XPA; +    } +    if (has_flag(session.params.flags, ScanFlag::REVERSE)) { +        mflags |= MotorFlag::REVERSE; +    } + +    unsigned scan_lines = dev->model->is_cis ? session.output_line_count * session.params.channels +                                             : session.output_line_count; + +    gl843_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure, slope_dpi, +                               scan_lines, dummy, session.params.starty, mflags); + +    dev->read_buffer.clear(); +    dev->read_buffer.alloc(session.buffer_size_read); + +    build_image_pipeline(dev, session); + +    dev->read_active = true; + +    dev->session = session; + +  dev->total_bytes_read = 0; +    dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines; + +    DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read); +} + +ScanSession CommandSetGl843::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +    DBG_HELPER(dbg); +    debug_dump(DBG_info, settings); + +  int start; + +  /* we have 2 domains for ccd: xres below or above half ccd max dpi */ +  unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(settings.xres); + +    if (settings.scan_method == ScanMethod::TRANSPARENCY || +        settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        start = static_cast<int>(dev->model->x_offset_ta); +    } else { +        start = static_cast<int>(dev->model->x_offset); +    } + +    if (dev->model->model_id == ModelId::CANON_8600F) +    { +        // FIXME: this is probably just an artifact of a bug elsewhere +        start /= ccd_size_divisor; +    } + +    start += static_cast<int>(settings.tl_x); +    start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = start; // not used +    session.params.starty = 0; // not used +    session.params.pixels = settings.pixels; +    session.params.requested_pixels = settings.requested_pixels; +    session.params.lines = settings.lines; +    session.params.depth = settings.depth; +    session.params.channels = settings.get_channels(); +    session.params.scan_method = settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; + +    compute_session(dev, session, sensor); + +    return session; +} + +/** + * for fast power saving methods only, like disabling certain amplifiers + * @param dev device to use + * @param enable true to set inot powersaving + * */ +void CommandSetGl843::save_power(Genesys_Device* dev, bool enable) const +{ +    DBG_HELPER_ARGS(dbg, "enable = %d", enable); + +    // switch KV-SS080 lamp off +    if (dev->model->gpio_id == GpioId::KVSS080) { +        uint8_t val = dev->interface->read_register(REG_0x6C); +        if (enable) { +            val &= 0xef; +        } else { +            val |= 0x10; +        } +        dev->interface->write_register(REG_0x6C, val); +    } +} + +void CommandSetGl843::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    (void) dev; +    DBG_HELPER_ARGS(dbg, "delay = %d", delay); +} + +static bool gl843_get_paper_sensor(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +    uint8_t val = dev->interface->read_register(REG_0x6D); + +    return (val & 0x1) == 0; +} + +void CommandSetGl843::eject_document(Genesys_Device* dev) const +{ +    (void) dev; +    DBG_HELPER(dbg); +} + + +void CommandSetGl843::load_document(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +    (void) dev; +} + +/** + * detects end of document and adjust current scan + * to take it into account + * used by sheetfed scanners + */ +void CommandSetGl843::detect_document_end(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +    bool paper_loaded = gl843_get_paper_sensor(dev); + +  /* sheetfed scanner uses home sensor as paper present */ +    if (dev->document && !paper_loaded) { +      DBG(DBG_info, "%s: no more document\n", __func__); +        dev->document = false; + +        unsigned scanned_lines = 0; +        catch_all_exceptions(__func__, [&](){ sanei_genesys_read_scancnt(dev, &scanned_lines); }); + +        std::size_t output_lines = dev->session.output_line_count; + +        std::size_t offset_lines = static_cast<std::size_t>( +                (dev->model->post_scan * dev->session.params.yres) / MM_PER_INCH); + +        std::size_t scan_end_lines = scanned_lines + offset_lines; + +        std::size_t remaining_lines = dev->get_pipeline_source().remaining_bytes() / +                dev->session.output_line_bytes_raw; + +        DBG(DBG_io, "%s: scanned_lines=%u\n", __func__, scanned_lines); +        DBG(DBG_io, "%s: scan_end_lines=%zu\n", __func__, scan_end_lines); +        DBG(DBG_io, "%s: output_lines=%zu\n", __func__, output_lines); +        DBG(DBG_io, "%s: remaining_lines=%zu\n", __func__, remaining_lines); + +        if (scan_end_lines > output_lines) { +            auto skip_lines = scan_end_lines - output_lines; + +            if (remaining_lines > skip_lines) { +                DBG(DBG_io, "%s: skip_lines=%zu\n", __func__, skip_lines); + +                remaining_lines -= skip_lines; +                dev->get_pipeline_source().set_remaining_bytes(remaining_lines * +                                                               dev->session.output_line_bytes_raw); +                dev->total_bytes_to_read -= skip_lines * dev->session.output_line_bytes_requested; +            } +        } +    } +} + +// enables or disables XPA slider motor +void gl843_set_xpa_motor_power(Genesys_Device* dev, Genesys_Register_Set& regs, bool set) +{ +    DBG_HELPER(dbg); +    uint8_t val; + +    if (dev->model->model_id == ModelId::CANON_8400F) { + +        if (set) { +            val = dev->interface->read_register(0x6c); +            val &= ~(REG_0x6C_GPIO16 | REG_0x6C_GPIO13); +            if (dev->session.output_resolution >= 2400) { +                val &= ~REG_0x6C_GPIO10; +            } +            dev->interface->write_register(0x6c, val); + +            val = dev->interface->read_register(0xa9); +            val |= REG_0xA9_GPO30; +            val &= ~REG_0xA9_GPO29; +            dev->interface->write_register(0xa9, val); +        } else { +            val = dev->interface->read_register(0x6c); +            val |= REG_0x6C_GPIO16 | REG_0x6C_GPIO13; +            dev->interface->write_register(0x6c, val); + +            val = dev->interface->read_register(0xa9); +            val &= ~REG_0xA9_GPO30; +            val |= REG_0xA9_GPO29; +            dev->interface->write_register(0xa9, val); +        } +    } else if (dev->model->model_id == ModelId::CANON_8600F) { +        if (set) { +            val = dev->interface->read_register(REG_0x6C); +            val &= ~REG_0x6C_GPIO14; +            if (dev->session.output_resolution >= 2400) { +                val |= REG_0x6C_GPIO10; +            } +            dev->interface->write_register(REG_0x6C, val); + +            val = dev->interface->read_register(REG_0xA6); +            val |= REG_0xA6_GPIO17; +            val &= ~REG_0xA6_GPIO23; +            dev->interface->write_register(REG_0xA6, val); +        } else { +            val = dev->interface->read_register(REG_0x6C); +            val |= REG_0x6C_GPIO14; +            val &= ~REG_0x6C_GPIO10; +            dev->interface->write_register(REG_0x6C, val); + +            val = dev->interface->read_register(REG_0xA6); +            val &= ~REG_0xA6_GPIO17; +            val &= ~REG_0xA6_GPIO23; +            dev->interface->write_register(REG_0xA6, val); +        } +    } else if (dev->model->model_id == ModelId::HP_SCANJET_G4050) { +        if (set) { +            // set MULTFILM et GPOADF +            val = dev->interface->read_register(REG_0x6B); +            val |=REG_0x6B_MULTFILM|REG_0x6B_GPOADF; +            dev->interface->write_register(REG_0x6B, val); + +            val = dev->interface->read_register(REG_0x6C); +            val &= ~REG_0x6C_GPIO15; +            dev->interface->write_register(REG_0x6C, val); + +            /* Motor power ? No move at all without this one */ +            val = dev->interface->read_register(REG_0xA6); +            val |= REG_0xA6_GPIO20; +            dev->interface->write_register(REG_0xA6, val); + +            val = dev->interface->read_register(REG_0xA8); +            val &= ~REG_0xA8_GPO27; +            dev->interface->write_register(REG_0xA8, val); + +            val = dev->interface->read_register(REG_0xA9); +            val |= REG_0xA9_GPO32|REG_0xA9_GPO31; +            dev->interface->write_register(REG_0xA9, val); +        } else { +            // unset GPOADF +            val = dev->interface->read_register(REG_0x6B); +            val &= ~REG_0x6B_GPOADF; +            dev->interface->write_register(REG_0x6B, val); + +            val = dev->interface->read_register(REG_0xA8); +            val |= REG_0xA8_GPO27; +            dev->interface->write_register(REG_0xA8, val); + +            val = dev->interface->read_register(REG_0xA9); +            val &= ~REG_0xA9_GPO31; +            dev->interface->write_register(REG_0xA9, val); +        } +    } +    regs.state.is_xpa_motor_on = set; +} + + +/** @brief light XPA lamp + * toggle gpios to switch off regular lamp and light on the + * XPA light + * @param dev device to set up + */ +static void gl843_set_xpa_lamp_power(Genesys_Device* dev, bool set) +{ +    DBG_HELPER(dbg); + +    struct LampSettings { +        ModelId model_id; +        ScanMethod scan_method; +        GenesysRegisterSettingSet regs_on; +        GenesysRegisterSettingSet regs_off; +    }; + +    // FIXME: BUG: we're not clearing the registers to the previous state when returning back when +    // turning off the lamp +    LampSettings settings[] = { +        {   ModelId::CANON_8400F, ScanMethod::TRANSPARENCY, { +                { 0xa6, 0x34, 0xf4 }, +            }, { +                { 0xa6, 0x40, 0x70 }, +            } +        }, +        {   ModelId::CANON_8400F, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0x6c, 0x40, 0x40 }, +                { 0xa6, 0x01, 0xff }, +            }, { +                { 0x6c, 0x00, 0x40 }, +                { 0xa6, 0x00, 0xff }, +            } +        }, +        {   ModelId::CANON_8600F, ScanMethod::TRANSPARENCY, { +                { 0xa6, 0x34, 0xf4 }, +                { 0xa7, 0xe0, 0xe0 }, +            }, { +                { 0xa6, 0x40, 0x70 }, +            } +        }, +        {   ModelId::CANON_8600F, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0xa6, 0x00, 0xc0 }, +                { 0xa7, 0xe0, 0xe0 }, +                { 0x6c, 0x80, 0x80 }, +            }, { +                { 0xa6, 0x00, 0xc0 }, +                { 0x6c, 0x00, 0x80 }, +            } +        }, +        {   ModelId::PLUSTEK_OPTICFILM_7200I, ScanMethod::TRANSPARENCY, { +            }, { +                { 0xa6, 0x40, 0x70 }, // BUG: remove this cleanup write, it was enabled by accident +            } +        }, +        {   ModelId::PLUSTEK_OPTICFILM_7200I, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0xa8, 0x07, 0x07 }, +            }, { +                { 0xa8, 0x00, 0x07 }, +            } +        }, +        {   ModelId::PLUSTEK_OPTICFILM_7300, ScanMethod::TRANSPARENCY, {}, {} }, +        {   ModelId::PLUSTEK_OPTICFILM_7500I, ScanMethod::TRANSPARENCY, {}, {} }, +        {   ModelId::PLUSTEK_OPTICFILM_7500I, ScanMethod::TRANSPARENCY_INFRARED, { +                { 0xa8, 0x07, 0x07 }, +            }, { +                { 0xa8, 0x00, 0x07 }, +            } +        }, +    }; + +    for (const auto& setting : settings) { +        if (setting.model_id == dev->model->model_id && +            setting.scan_method == dev->settings.scan_method) +        { +            apply_reg_settings_to_device(*dev, set ? setting.regs_on : setting.regs_off); +            return; +        } +    } + +    // BUG: we're currently calling the function in shut down path of regular lamp +    if (set) { +        throw SaneException("Unexpected code path entered"); +    } + +    GenesysRegisterSettingSet regs = { +        { 0xa6, 0x40, 0x70 }, +    }; +    apply_reg_settings_to_device(*dev, regs); +    // TODO: throw exception when we're only calling this function in error return path +    // throw SaneException("Could not find XPA lamp settings"); +} + +// Send the low-level scan command +void CommandSetGl843::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set* reg, bool start_motor) const +{ +    DBG_HELPER(dbg); +    (void) sensor; + +  /* set up GPIO for scan */ +    switch(dev->model->gpio_id) { +      /* KV case */ +        case GpioId::KVSS080: +            dev->interface->write_register(REG_0xA9, 0x00); +            dev->interface->write_register(REG_0xA6, 0xf6); +            // blinking led +            dev->interface->write_register(0x7e, 0x04); +            break; +        case GpioId::G4050: +            dev->interface->write_register(REG_0xA7, 0xfe); +            dev->interface->write_register(REG_0xA8, 0x3e); +            dev->interface->write_register(REG_0xA9, 0x06); +            if ((reg->get8(0x05) & REG_0x05_DPIHW) == REG_0x05_DPIHW_600) { +                dev->interface->write_register(REG_0x6C, 0x20); +                dev->interface->write_register(REG_0xA6, 0x44); +            } else { +                dev->interface->write_register(REG_0x6C, 0x60); +                dev->interface->write_register(REG_0xA6, 0x46); +            } + +            if (reg->state.is_xpa_on && reg->state.is_lamp_on) { +                gl843_set_xpa_lamp_power(dev, true); +            } + +            if (reg->state.is_xpa_on) { +                gl843_set_xpa_motor_power(dev, *reg, true); +            } + +            // blinking led +            dev->interface->write_register(REG_0x7E, 0x01); +            break; +        case GpioId::CANON_8400F: +        case GpioId::CANON_8600F: +            if (reg->state.is_xpa_on && reg->state.is_lamp_on) { +                gl843_set_xpa_lamp_power(dev, true); +            } +            if (reg->state.is_xpa_on) { +                gl843_set_xpa_motor_power(dev, *reg, true); +            } +            break; +        case GpioId::PLUSTEK_OPTICFILM_7200I: +        case GpioId::PLUSTEK_OPTICFILM_7300: +        case GpioId::PLUSTEK_OPTICFILM_7500I: { +            if (reg->state.is_xpa_on && reg->state.is_lamp_on) { +                gl843_set_xpa_lamp_power(dev, true); +            } +            break; +        } +        case GpioId::CANON_4400F: +        default: +            break; +    } + +    // clear scan and feed count +    dev->interface->write_register(REG_0x0D, REG_0x0D_CLRLNCNT | REG_0x0D_CLRMCNT); + +    // enable scan and motor +    uint8_t val = dev->interface->read_register(REG_0x01); +    val |= REG_0x01_SCAN; +    dev->interface->write_register(REG_0x01, val); + +    scanner_start_action(*dev, start_motor); + +    if (reg->state.is_motor_on) { +        dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +    } +    if (reg->state.is_xpa_motor_on) { +        dev->advance_head_pos_by_session(ScanHeadId::SECONDARY); +    } +} + + +// Send the stop scan command +void CommandSetGl843::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg, +                               bool check_stop) const +{ +    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop); + +    // post scan gpio +    dev->interface->write_register(0x7e, 0x00); + +    // turn off XPA lamp if needed +    // BUG: the if condition below probably shouldn't be enabled when XPA is off +    if (reg->state.is_xpa_on || reg->state.is_lamp_on) { +        gl843_set_xpa_lamp_power(dev, false); +    } + +    if (!dev->model->is_sheetfed) { +        scanner_stop_action(*dev); +    } +} + +/** @brief Moves the slider to the home (top) position slowly + * */ +void CommandSetGl843::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    scanner_move_back_home(*dev, wait_until_home); +} + +// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi +// from very top of scanner +void CommandSetGl843::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  Genesys_Register_Set local_reg; + +  int pixels = 600; +  int dpi = 300; + +  local_reg = dev->reg; + +  /* sets for a 200 lines * 600 pixels */ +  /* normal scan with no shading */ + +    // FIXME: the current approach of doing search only for one resolution does not work on scanners +    // whith employ different sensors with potentially different settings. +    const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method); + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; // we should give a small offset here - ~60 steps +    session.params.pixels = 600; +    session.params.lines = dev->model->search_lines; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::GREEN; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::IGNORE_LINE_DISTANCE | +                            ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    // send to scanner +    dev->interface->write_registers(local_reg); + +    dev->cmd_set->begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_start_position"); +        end_scan(dev, &local_reg, true); +        dev->reg = local_reg; +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    Image image = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw); + +    scanner_stop_action_no_move(*dev, local_reg); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl843_search_position.pnm", image); +    } + +    dev->cmd_set->end_scan(dev, &local_reg, true); + +  /* update regs to copy ASIC internal state */ +  dev->reg = local_reg; + +    for (auto& sensor_update : +            sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method)) +    { +        sanei_genesys_search_reference_point(dev, sensor_update, image.get_row_ptr(0), 0, dpi, +                                             pixels, dev->model->search_lines); +    } +} + +// sets up register for coarse gain calibration +// todo: check it for scanners using it +void CommandSetGl843::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::SINGLE_LINE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { +        flags |= ScanFlag::USE_XPA; +    } + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel(); +    session.params.lines = 20; +    session.params.depth = 16; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +  DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__, +      sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres); + +    dev->interface->write_registers(regs); +} + +// init registers for shading calibration shading calibration is done at dpihw +void CommandSetGl843::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int move, resolution, dpihw, factor; + +  /* initial calibration reg values */ +  regs = dev->reg; + +  dev->calib_channels = 3; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        dev->calib_lines = dev->model->shading_ta_lines; +    } else { +        dev->calib_lines = dev->model->shading_lines; +    } + +    dpihw = sensor.get_logical_hwdpi(dev->settings.xres); +  factor=sensor.optical_res/dpihw; +  resolution=dpihw; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, dev->calib_channels, +                                                       dev->settings.scan_method); + +    if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY || +         dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) && +        dev->model->model_id == ModelId::CANON_8600F && +        dev->settings.xres == 4800) +    { +        float offset = static_cast<float>(dev->model->x_offset_ta); +        offset /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        offset = static_cast<float>((offset * calib_sensor.optical_res) / MM_PER_INCH); + +        float size = static_cast<float>(dev->model->x_size_ta); +        size /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        size = static_cast<float>((size * calib_sensor.optical_res) / MM_PER_INCH); + +        dev->calib_pixels_offset = static_cast<std::size_t>(offset); +        dev->calib_pixels = static_cast<std::size_t>(size); +    } +    else +    { +        dev->calib_pixels_offset = 0; +        dev->calib_pixels = calib_sensor.sensor_pixels / factor; +    } + +  dev->calib_resolution = resolution; + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::DISABLE_BUFFER_FULL_MOVE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        // note: move_to_ta() function has already been called and the sensor is at the +        // transparency adapter +        move = static_cast<int>(dev->model->y_offset_calib_white_ta - dev->model->y_offset_sensor_to_ta); +        flags |= ScanFlag::USE_XPA; +    } else { +        move = static_cast<int>(dev->model->y_offset_calib_white); +    } + +    move = static_cast<int>((move * resolution) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = dev->calib_pixels_offset; +    session.params.starty = move; +    session.params.pixels = dev->calib_pixels; +    session.params.lines = dev->calib_lines; +    session.params.depth = 16; +    session.params.channels = dev->calib_channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +     // the pixel number may be updated to conform to scanner constraints +    dev->calib_pixels = session.output_pixels; + +    dev->calib_session = session; +    dev->calib_total_bytes_to_read = session.output_total_bytes_raw; + +    dev->interface->write_registers(regs); +} + +/** @brief set up registers for the actual scan + */ +void CommandSetGl843::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  float move; +  int move_dpi; +  float start; + +    debug_dump(DBG_info, dev->settings); + +  move_dpi = dev->motor.base_ydpi; + +    ScanFlag flags = ScanFlag::NONE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        // note: move_to_ta() function has already been called and the sensor is at the +        // transparency adapter +        if (dev->ignore_offsets) { +            move = 0; +        } else { +            move = static_cast<float>(dev->model->y_offset_ta - dev->model->y_offset_sensor_to_ta); +        } +        flags |= ScanFlag::USE_XPA; +    } else { +        if (dev->ignore_offsets) { +            move = 0; +        } else { +            move = static_cast<float>(dev->model->y_offset); +        } +    } + +    move += static_cast<float>(dev->settings.tl_y); +    move = static_cast<float>((move * move_dpi) / MM_PER_INCH); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* start */ +    if (dev->settings.scan_method==ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        start = static_cast<float>(dev->model->x_offset_ta); +    } else { +        start = static_cast<float>(dev->model->x_offset); +    } + +    if (dev->model->model_id == ModelId::CANON_8400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        // FIXME: this is probably just an artifact of a bug elsewhere +        start /= sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres); +    } + +    start = static_cast<float>(start + dev->settings.tl_x); +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->settings.pixels; +    session.params.requested_pixels = dev->settings.requested_pixels; +    session.params.lines = dev->settings.lines; +    session.params.depth = dev->settings.depth; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); +} + +/** + * This function sends gamma tables to ASIC + */ +void CommandSetGl843::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  int size; +  int i; + +  size = 256; + +  /* allocate temporary gamma tables: 16 bits words, 3 channels */ +  std::vector<uint8_t> gamma(size * 2 * 3); + +    std::vector<uint16_t> rgamma = get_gamma_table(dev, sensor, GENESYS_RED); +    std::vector<uint16_t> ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN); +    std::vector<uint16_t> bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE); + +    // copy sensor specific's gamma tables +    for (i = 0; i < size; i++) { +        gamma[i * 2 + size * 0 + 0] = rgamma[i] & 0xff; +        gamma[i * 2 + size * 0 + 1] = (rgamma[i] >> 8) & 0xff; +        gamma[i * 2 + size * 2 + 0] = ggamma[i] & 0xff; +        gamma[i * 2 + size * 2 + 1] = (ggamma[i] >> 8) & 0xff; +        gamma[i * 2 + size * 4 + 0] = bgamma[i] & 0xff; +        gamma[i * 2 + size * 4 + 1] = (bgamma[i] >> 8) & 0xff; +    } + +    dev->interface->write_gamma(0x28, 0x0000, gamma.data(), size * 2 * 3, +                                ScannerInterface::FLAG_SWAP_REGISTERS); +} + +/* this function does the led calibration by scanning one line of the calibration +   area below scanner's top on white strip. + +-needs working coarse/gain +*/ +SensorExposure CommandSetGl843::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int avg[3], avga, avge; +  int turn; +  uint16_t expr, expg, expb; + +    // offset calibration is always done in color mode +    unsigned channels = 3; + +    // take a copy, as we're going to modify exposure +    auto calib_sensor = sanei_genesys_find_sensor(dev, sensor.optical_res, channels, +                                                  dev->settings.scan_method); + +    num_pixels = (calib_sensor.sensor_pixels * calib_sensor.optical_res) / calib_sensor.optical_res; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    ScanSession session; +    session.params.xres = calib_sensor.sensor_pixels; +    session.params.yres = dev->motor.base_ydpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::SINGLE_LINE | +                            ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); + +/* +   we try to get equal bright leds here: + +   loop: +     average per color +     adjust exposure times + */ + +  expr = calib_sensor.exposure.red; +  expg = calib_sensor.exposure.green; +  expb = calib_sensor.exposure.blue; + +  turn = 0; + +    bool acceptable = false; +  do +    { + +      calib_sensor.exposure.red = expr; +      calib_sensor.exposure.green = expg; +      calib_sensor.exposure.blue = expb; + +        regs_set_exposure(dev->model->asic_type, regs, calib_sensor.exposure); + +        dev->interface->write_registers(regs); + +      DBG(DBG_info, "%s: starting first line reading\n", __func__); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("led_calibration"); +            move_back_home(dev, true); +            return calib_sensor.exposure; +        } + +        auto image = read_unshuffled_image_from_scanner(dev, session, +                                                        session.output_total_bytes_raw); +        scanner_stop_action_no_move(*dev, regs); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[30]; +            std::snprintf(fn, 30, "gl843_led_%02d.pnm", turn); +            sanei_genesys_write_pnm_file(fn, image); +	} + +        acceptable = true; + +        for (unsigned ch = 0; ch < channels; ch++) { +            avg[ch] = 0; +            for (std::size_t x = 0; x < image.get_width(); x++) { +                avg[ch] += image.get_raw_channel(x, 0, ch); +            } +            avg[ch] /= image.get_width(); +        } + +      DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +        acceptable = true; + +      if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 || +	  avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 || +	  avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95) +        acceptable = false; + +      if (!acceptable) +	{ +	  avga = (avg[0] + avg[1] + avg[2]) / 3; +	  expr = (expr * avga) / avg[0]; +	  expg = (expg * avga) / avg[1]; +	  expb = (expb * avga) / avg[2]; +/* +  keep the resulting exposures below this value. +  too long exposure drives the ccd into saturation. +  we may fix this by relying on the fact that +  we get a striped scan without shading, by means of +  statistical calculation +*/ +	  avge = (expr + expg + expb) / 3; + +	  /* don't overflow max exposure */ +	  if (avge > 3000) +	    { +	      expr = (expr * 2000) / avge; +	      expg = (expg * 2000) / avge; +	      expb = (expb * 2000) / avge; +	    } +	  if (avge < 50) +	    { +	      expr = (expr * 50) / avge; +	      expg = (expg * 50) / avge; +	      expb = (expb * 50) / avge; +	    } + +	} +        scanner_stop_action(*dev); + +      turn++; + +    } +  while (!acceptable && turn < 100); + +  DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, expr, expg, expb); + +    move_back_home(dev, true); + +    return calib_sensor.exposure; +} + + + +/** + * average dark pixels of a 8 bits scan of a given channel + */ +static int dark_average_channel(const Image& image, unsigned black, unsigned channel) +{ +    auto channels = get_pixel_channels(image.get_format()); + +    unsigned avg[3]; + +    // computes average values on black margin +    for (unsigned ch = 0; ch < channels; ch++) { +        avg[ch] = 0; +        unsigned count = 0; +        // FIXME: start with the second line because the black pixels often have noise on the first +        // line; the cause is probably incorrectly cleaned up previous scan +        for (std::size_t y = 1; y < image.get_height(); y++) { +            for (unsigned j = 0; j < black; j++) { +                avg[ch] += image.get_raw_channel(j, y, ch); +                count++; +            } +        } +        if (count > 0) { +            avg[ch] /= count; +        } +        DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, ch, avg[ch]); +    } +    DBG(DBG_info, "%s: average = %d\n", __func__, avg[channel]); +    return avg[channel]; +} + +/** @brief calibrate AFE offset + * Iterate doing scans at target dpi until AFE offset if correct. One + * color line is scanned at a time. Scanning head doesn't move. + * @param dev device to calibrate + */ +void CommandSetGl843::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    if (dev->frontend.layout.type != FrontendType::WOLFSON) +        return; + +    unsigned channels; +    int pass, resolution, lines; +  int topavg[3], bottomavg[3], avg[3]; +  int top[3], bottom[3], black_pixels, pixels, factor, dpihw; + +  /* offset calibration is always done in color mode */ +  channels = 3; +  lines = 8; + +    // compute divider factor to compute final pixels number +    dpihw = sensor.get_logical_hwdpi(dev->settings.xres); +  factor = sensor.optical_res / dpihw; +  resolution = dpihw; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                       dev->settings.scan_method); + +  int target_pixels = calib_sensor.sensor_pixels / factor; +  int start_pixel = 0; +  black_pixels = calib_sensor.black_pixels / factor; + +    if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY || +         dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) && +        dev->model->model_id == ModelId::CANON_8600F && +        dev->settings.xres == 4800) +    { +        start_pixel = static_cast<int>(dev->model->x_offset_ta); +        start_pixel /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        start_pixel = static_cast<int>((start_pixel * calib_sensor.optical_res) / MM_PER_INCH); + +        target_pixels = static_cast<int>(dev->model->x_size_ta); +        target_pixels /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution); +        target_pixels = static_cast<int>((target_pixels * calib_sensor.optical_res) / MM_PER_INCH); +    } + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::SINGLE_LINE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        flags |= ScanFlag::USE_XPA; +    } + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = start_pixel; +    session.params.starty = 0; +    session.params.pixels = target_pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = flags; +    compute_session(dev, session, calib_sensor); +    pixels = session.output_pixels; + +    DBG(DBG_io, "%s: dpihw       =%d\n", __func__, dpihw); +    DBG(DBG_io, "%s: factor      =%d\n", __func__, factor); +    DBG(DBG_io, "%s: resolution  =%d\n", __func__, resolution); +    DBG(DBG_io, "%s: pixels      =%d\n", __func__, pixels); +    DBG(DBG_io, "%s: black_pixels=%d\n", __func__, black_pixels); +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +    // init gain and offset +    for (unsigned ch = 0; ch < 3; ch++) +    { +        bottom[ch] = 10; +        dev->frontend.set_offset(ch, bottom[ch]); +        dev->frontend.set_gain(ch, 0); +    } +    dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +    // scan with bottom AFE settings +    dev->interface->write_registers(regs); +    DBG(DBG_info, "%s: starting first line reading\n", __func__); + +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("offset_calibration"); +        scanner_stop_action_no_move(*dev, regs); +        return; +    } + +    auto first_line = read_unshuffled_image_from_scanner(dev, session, +                                                         session.output_total_bytes_raw); +    scanner_stop_action_no_move(*dev, regs); + +  if (DBG_LEVEL >= DBG_data) +    { +      char fn[40]; +        std::snprintf(fn, 40, "gl843_bottom_offset_%03d_%03d_%03d.pnm", +                      bottom[0], bottom[1], bottom[2]); +        sanei_genesys_write_pnm_file(fn, first_line); +    } + +    for (unsigned ch = 0; ch < 3; ch++) { +        bottomavg[ch] = dark_average_channel(first_line, black_pixels, ch); +        DBG(DBG_io2, "%s: bottom avg %d=%d\n", __func__, ch, bottomavg[ch]); +    } + +    // now top value +    for (unsigned ch = 0; ch < 3; ch++) { +        top[ch] = 255; +        dev->frontend.set_offset(ch, top[ch]); +    } +    dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +    // scan with top AFE values +    dev->interface->write_registers(regs); +    DBG(DBG_info, "%s: starting second line reading\n", __func__); + +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); +    auto second_line = read_unshuffled_image_from_scanner(dev, session, +                                                          session.output_total_bytes_raw); +    scanner_stop_action_no_move(*dev, regs); + +    for (unsigned ch = 0; ch < 3; ch++){ +        topavg[ch] = dark_average_channel(second_line, black_pixels, ch); +        DBG(DBG_io2, "%s: top avg %d=%d\n", __func__, ch, topavg[ch]); +    } + +  pass = 0; + +  std::vector<uint8_t> debug_image; +  size_t debug_image_lines = 0; +  std::string debug_image_info; + +  /* loop until acceptable level */ +  while ((pass < 32) +	 && ((top[0] - bottom[0] > 1) +	     || (top[1] - bottom[1] > 1) || (top[2] - bottom[2] > 1))) +    { +      pass++; + +        // settings for new scan +        for (unsigned ch = 0; ch < 3; ch++) { +            if (top[ch] - bottom[ch] > 1) { +                dev->frontend.set_offset(ch, (top[ch] + bottom[ch]) / 2); +            } +        } +        dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); + +        // scan with no move +        dev->interface->write_registers(regs); +      DBG(DBG_info, "%s: starting second line reading\n", __func__); +        dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); +        second_line = read_unshuffled_image_from_scanner(dev, session, +                                                         session.output_total_bytes_raw); +        scanner_stop_action_no_move(*dev, regs); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char title[100]; +          std::snprintf(title, 100, "lines: %d pixels_per_line: %d offsets[0..2]: %d %d %d\n", +                        lines, pixels, +                        dev->frontend.get_offset(0), +                        dev->frontend.get_offset(1), +                        dev->frontend.get_offset(2)); +          debug_image_info += title; +          std::copy(second_line.get_row_ptr(0), +                    second_line.get_row_ptr(0) + second_line.get_row_bytes() * second_line.get_height(), +                    std::back_inserter(debug_image)); +          debug_image_lines += lines; +	} + +        for (unsigned ch = 0; ch < 3; ch++) { +            avg[ch] = dark_average_channel(second_line, black_pixels, ch); +            DBG(DBG_info, "%s: avg[%d]=%d offset=%d\n", __func__, ch, avg[ch], +                dev->frontend.get_offset(ch)); +        } + +        // compute new boundaries +        for (unsigned ch = 0; ch < 3; ch++) { +            if (topavg[ch] >= avg[ch]) { +                topavg[ch] = avg[ch]; +                top[ch] = dev->frontend.get_offset(ch); +            } else { +                bottomavg[ch] = avg[ch]; +                bottom[ch] = dev->frontend.get_offset(ch); +            } +        } +    } + +  if (DBG_LEVEL >= DBG_data) +    { +      sanei_genesys_write_file("gl843_offset_all_desc.txt", +                               reinterpret_cast<const std::uint8_t*>(debug_image_info.data()), +                               debug_image_info.size()); +      sanei_genesys_write_pnm_file("gl843_offset_all.pnm", +                                   debug_image.data(), session.params.depth, channels, pixels, +                                   debug_image_lines); +    } + +  DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__, +      dev->frontend.get_offset(0), +      dev->frontend.get_offset(1), +      dev->frontend.get_offset(2)); +} + + +/* alternative coarse gain calibration +   this on uses the settings from offset_calibration and +   uses only one scanline + */ +/* +  with offset and coarse calibration we only want to get our input range into +  a reasonable shape. the fine calibration of the upper and lower bounds will +  be done with shading. + */ +void CommandSetGl843::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER_ARGS(dbg, "dpi = %d", dpi); +    int factor, dpihw; +  float coeff; +    int lines; +  int resolution; + +    if (dev->frontend.layout.type != FrontendType::WOLFSON) +        return; + +    dpihw = sensor.get_logical_hwdpi(dpi); +  factor=sensor.optical_res/dpihw; + +    // coarse gain calibration is always done in color mode +    unsigned channels = 3; + +  /* follow CKSEL */ +    if (dev->model->sensor_id == SensorId::CCD_KVSS080) { +      if(dev->settings.xres<sensor.optical_res) +        { +            coeff = 0.9f; +        } +      else +        { +          coeff=1.0; +        } +    } +  else +    { +      coeff=1.0; +    } +  resolution=dpihw; +  lines=10; +  int target_pixels = sensor.sensor_pixels / factor; + +    ScanFlag flags = ScanFlag::DISABLE_SHADING | +                     ScanFlag::DISABLE_GAMMA | +                     ScanFlag::SINGLE_LINE | +                     ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        flags |= ScanFlag::USE_XPA; +    } + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels, +                                                         dev->settings.scan_method); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = target_pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = flags; +    compute_session(dev, session, calib_sensor); +    std::size_t pixels = session.output_pixels; + +    try { +        init_regs_for_scan_session(dev, calib_sensor, ®s, session); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); +        throw; +    } + +    sanei_genesys_set_motor_power(regs, false); + +    dev->interface->write_registers(regs); + +    dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET); +    dev->cmd_set->begin_scan(dev, calib_sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("coarse_gain_calibration"); +        scanner_stop_action(*dev); +        move_back_home(dev, true); +        return; +    } + +    auto line = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw); +    scanner_stop_action_no_move(*dev, regs); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl843_gain.pnm", line); +    } + +    // average value on each channel +    for (unsigned ch = 0; ch < channels; ch++) { + +        std::vector<uint16_t> values; +        // FIXME: start from the second line because the first line often has artifacts. Probably +        // caused by unclean cleanup of previous scan +        for (std::size_t x = pixels / 4; x < (pixels * 3 / 4); x++) { +            values.push_back(line.get_raw_channel(x, 1, ch)); +        } + +        // pick target value at 95th percentile of all values. There may be a lot of black values +        // in transparency scans for example +        std::sort(values.begin(), values.end()); +        uint16_t curr_output = values[unsigned((values.size() - 1) * 0.95)]; +        float target_value = calib_sensor.gain_white_ref * coeff; + +        int code = compute_frontend_gain(curr_output, target_value, dev->frontend.layout.type); +      dev->frontend.set_gain(ch, code); + +        DBG(DBG_proc, "%s: channel %d, max=%d, target=%d, setting:%d\n", __func__, ch, curr_output, +            static_cast<int>(target_value), code); +    } + +    if (dev->model->is_cis) { +        uint8_t gain0 = dev->frontend.get_gain(0); +        if (gain0 > dev->frontend.get_gain(1)) { +            gain0 = dev->frontend.get_gain(1); +        } +        if (gain0 > dev->frontend.get_gain(2)) { +            gain0 = dev->frontend.get_gain(2); +        } +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); +    } + +    if (channels == 1) { +        dev->frontend.set_gain(0, dev->frontend.get_gain(1)); +        dev->frontend.set_gain(2, dev->frontend.get_gain(1)); +    } + +    scanner_stop_action(*dev); + +    move_back_home(dev, true); +} + +// wait for lamp warmup by scanning the same line until difference +// between 2 scans is below a threshold +void CommandSetGl843::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* reg, int* channels, +                                           int* total_size) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int dpihw; +  int resolution; +  int factor; + +  /* setup scan */ +  *channels=3; +  resolution=600; +    dpihw = sensor.get_logical_hwdpi(resolution); +  resolution=dpihw; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, *channels, +                                                       dev->settings.scan_method); +  factor = calib_sensor.optical_res/dpihw; +  num_pixels = calib_sensor.sensor_pixels/(factor*2); +  *total_size = num_pixels * 3 * 1; + +  *reg = dev->reg; + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = num_pixels/2; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 8; +    session.params.channels = *channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::SINGLE_LINE | +                            ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, reg, session); + +  sanei_genesys_set_motor_power(*reg, false); +    dev->interface->write_registers(*reg); +} + +/** + * set up GPIO/GPOE for idle state +WRITE GPIO[17-21]= GPIO19 +WRITE GPOE[17-21]= GPOE21 GPOE20 GPOE19 GPOE18 +genesys_write_register(0xa8,0x3e) +GPIO(0xa8)=0x3e + */ +static void gl843_init_gpio(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    apply_registers_ordered(dev->gpo.regs, { 0x6e, 0x6f }, [&](const GenesysRegisterSetting& reg) +    { +        dev->interface->write_register(reg.address, reg.value); +    }); +} + + +/* * + * initialize ASIC from power on condition + */ +void CommandSetGl843::asic_boot(Genesys_Device* dev, bool cold) const +{ +    DBG_HELPER(dbg); +  uint8_t val; + +    if (cold) { +        dev->interface->write_register(0x0e, 0x01); +        dev->interface->write_register(0x0e, 0x00); +    } + +  if(dev->usb_mode == 1) +    { +      val = 0x14; +    } +  else +    { +      val = 0x11; +    } +    dev->interface->write_0x8c(0x0f, val); + +    // test CHKVER +    val = dev->interface->read_register(REG_0x40); +    if (val & REG_0x40_CHKVER) { +        val = dev->interface->read_register(0x00); +        DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val); +    } + +  /* Set default values for registers */ +  gl843_init_registers (dev); + +    if (dev->model->model_id == ModelId::CANON_8600F) { +        // turns on vref control for maximum current of the motor driver +        dev->interface->write_register(REG_0x6B, 0x72); +    } else { +        dev->interface->write_register(REG_0x6B, 0x02); +    } + +    // Write initial registers +    dev->interface->write_registers(dev->reg); + +  // Enable DRAM by setting a rising edge on bit 3 of reg 0x0b +    val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL; +    val = (val | REG_0x0B_ENBDRAM); +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +    if (dev->model->model_id == ModelId::CANON_8400F) { +        dev->interface->write_0x8c(0x1e, 0x01); +        dev->interface->write_0x8c(0x10, 0xb4); +        dev->interface->write_0x8c(0x0f, 0x02); +    } +    else if (dev->model->model_id == ModelId::CANON_8600F) { +        dev->interface->write_0x8c(0x10, 0xc8); +    } else if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +               dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +    { +        dev->interface->write_0x8c(0x10, 0xd4); +    } else { +        dev->interface->write_0x8c(0x10, 0xb4); +    } + +  /* CLKSET */ +    int clock_freq = REG_0x0B_48MHZ; +    switch (dev->model->model_id) { +        case ModelId::CANON_8600F: +            clock_freq = REG_0x0B_60MHZ; +            break; +        case ModelId::PLUSTEK_OPTICFILM_7200I: +            clock_freq = REG_0x0B_30MHZ; +            break; +        case ModelId::PLUSTEK_OPTICFILM_7300: +        case ModelId::PLUSTEK_OPTICFILM_7500I: +            clock_freq = REG_0x0B_40MHZ; +            break; +        default: +            break; +    } + +    val = (dev->reg.find_reg(0x0b).value & ~REG_0x0B_CLKSET) | clock_freq; + +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +  /* prevent further writings by bulk write register */ +  dev->reg.remove_reg(0x0b); + +    if (dev->model->model_id != ModelId::CANON_8600F) { +      // set up end access +      // FIXME: this is overwritten in gl843_init_gpio +        dev->interface->write_register(REG_0xA7, 0x04); +        dev->interface->write_register(REG_0xA9, 0x00); +    } + +    // set RAM read address +    dev->interface->write_register(REG_0x29, 0x00); +    dev->interface->write_register(REG_0x2A, 0x00); +    dev->interface->write_register(REG_0x2B, 0x00); + +    // setup gpio +    gl843_init_gpio(dev); + +    scanner_move(*dev, dev->model->default_method, 300, Direction::FORWARD); +    dev->interface->sleep_ms(100); +} + +/* * + * initialize backend and ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + */ +void CommandSetGl843::init(Genesys_Device* dev) const +{ +  DBG_INIT (); +    DBG_HELPER(dbg); + +    sanei_genesys_asic_init(dev, 0); +} + +void CommandSetGl843::update_hardware_sensors(Genesys_Scanner* s) const +{ +    DBG_HELPER(dbg); +  /* do what is needed to get a new set of events, but try to not lose +     any of them. +   */ + +    uint8_t val = s->dev->interface->read_register(REG_0x6D); + +  switch (s->dev->model->gpio_id) +    { +        case GpioId::KVSS080: +            s->buttons[BUTTON_SCAN_SW].write((val & 0x04) == 0); +            break; +        case GpioId::G4050: +            s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0); +            s->buttons[BUTTON_FILE_SW].write((val & 0x02) == 0); +            s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0); +            s->buttons[BUTTON_COPY_SW].write((val & 0x08) == 0); +            break; +        case GpioId::CANON_4400F: +        case GpioId::CANON_8400F: +        default: +            break; +    } +} + +/** @brief move sensor to transparency adaptor + * Move sensor to the calibration of the transparency adapator (XPA). + * @param dev device to use + */ +void CommandSetGl843::move_to_ta(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); + +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->model->default_method); +    float resolution = resolution_settings.get_min_resolution_y(); + +    unsigned multiplier = 16; +    if (dev->model->model_id == ModelId::CANON_8400F) { +        multiplier = 4; +    } +    unsigned feed = static_cast<unsigned>(multiplier * (dev->model->y_offset_sensor_to_ta * resolution) / +                                          MM_PER_INCH); +    scanner_move(*dev, dev->model->default_method, feed, Direction::FORWARD); +} + + +/** @brief search for a full width black or white strip. + * This function searches for a black or white stripe across the scanning area. + * When searching backward, the searched area must completely be of the desired + * color since this area will be used for calibration which scans forward. + * @param dev scanner device + * @param forward true if searching forward, false if searching backward + * @param black true if searching for a black strip, false for a white strip + */ +void CommandSetGl843::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   bool forward, bool black) const +{ +    DBG_HELPER_ARGS(dbg, "%s %s",  black ? "black" : "white", forward ? "forward" : "reverse"); +  unsigned int pixels, lines, channels; +  Genesys_Register_Set local_reg; +    int dpi; +  unsigned int pass, count, found, x, y; + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); +    scanner_stop_action(*dev); + +  /* set up for a gray scan at lowest dpi */ +  dpi = sanei_genesys_get_lowest_dpi(dev); +  channels = 1; + +  const auto& calib_sensor = sanei_genesys_find_sensor(dev, dpi, channels, +                                                       dev->settings.scan_method); + +  /* 10 MM */ +  /* lines = (10 * dpi) / MM_PER_INCH; */ +  /* shading calibation is done with dev->motor.base_ydpi */ +  lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi; +  pixels = (calib_sensor.sensor_pixels * dpi) / calib_sensor.optical_res; + +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +  local_reg = dev->reg; + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_SHADING; +    if (!forward) { +        session.params.flags = ScanFlag::REVERSE; +    } +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, &local_reg, session); + +    dev->interface->write_registers(local_reg); + +    dev->cmd_set->begin_scan(dev, calib_sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_strip"); +        scanner_stop_action(*dev); +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    auto data = read_unshuffled_image_from_scanner(dev, session, +                                                   session.output_total_bytes_raw); + +    scanner_stop_action(*dev); + +  pass = 0; +  if (DBG_LEVEL >= DBG_data) +    { +      char fn[40]; +        std::snprintf(fn, 40, "gl843_search_strip_%s_%s%02d.pnm", +                      black ? "black" : "white", forward ? "fwd" : "bwd", pass); +        sanei_genesys_write_pnm_file(fn, data); +    } + +  /* loop until strip is found or maximum pass number done */ +  found = 0; +  while (pass < 20 && !found) +    { +        dev->interface->write_registers(local_reg); + +        // now start scan +        dev->cmd_set->begin_scan(dev, calib_sensor, &local_reg, true); + +        wait_until_buffer_non_empty(dev); + +        // now we're on target, we can read data +        data = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw); + +        scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[40]; +            std::snprintf(fn, 40, "gl843_search_strip_%s_%s%02d.pnm", +                          black ? "black" : "white", forward ? "fwd" : "bwd", pass); +            sanei_genesys_write_pnm_file(fn, data); +	} + +      /* search data to find black strip */ +      /* when searching forward, we only need one line of the searched color since we +       * will scan forward. But when doing backward search, we need all the area of the +       * same color */ +      if (forward) +	{ +	  for (y = 0; y < lines && !found; y++) +	    { +	      count = 0; +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +                    if (black && data.get_raw_channel(x, y, 0) > 90) { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +                    if (!black && data.get_raw_channel(x, y, 0) < 60) { +		      count++; +		    } +		} + +	      /* at end of line, if count >= 3%, line is not fully of the desired color +	       * so we must go to next line of the buffer */ +	      /* count*100/pixels < 3 */ +	      if ((count * 100) / pixels < 3) +		{ +		  found = 1; +		  DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__, +		      pass, y); +		} +	      else +		{ +		  DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		      (100 * count) / pixels); +		} +	    } +	} +      else			/* since calibration scans are done forward, we need the whole area +				   to be of the required color when searching backward */ +	{ +	  count = 0; +	  for (y = 0; y < lines; y++) +	    { +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +                    // when searching for black, detect white pixels +                    if (black && data.get_raw_channel(x, y, 0) > 90) { +		      count++; +		    } +                    // when searching for white, detect black pixels +                    if (!black && data.get_raw_channel(x, y, 0) < 60) { +		      count++; +		    } +		} +	    } + +	  /* at end of area, if count >= 3%, area is not fully of the desired color +	   * so we must go to next buffer */ +	  if ((count * 100) / (pixels * lines) < 3) +	    { +	      found = 1; +	      DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); +	    } +	  else +	    { +	      DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		  (100 * count) / pixels); +	    } +	} +      pass++; +    } +  if (found) +    { +      DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white"); +    } +  else +    { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white"); +    } +} + +/** + * Send shading calibration data. The buffer is considered to always hold values + * for all the channels. + */ +void CommandSetGl843::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        uint8_t* data, int size) const +{ +    DBG_HELPER(dbg); +  uint32_t final_size, length, i; +  uint8_t *buffer; +  int count,offset; +  GenesysRegister *r; +    uint16_t strpixel, endpixel, startx; + +  offset=0; +  length=size; +    r = sanei_genesys_get_address(&dev->reg, REG_0x01); +    if (r->value & REG_0x01_SHDAREA) +    { +      /* recompute STRPIXEL used shading calibration so we can +       * compute offset within data for SHDAREA case */ + +        // FIXME: the following is likely incorrect +        // start coordinate in optical dpi coordinates +        startx = (sensor.dummy_pixel / sensor.ccd_pixels_per_system_pixel()) / dev->session.hwdpi_divisor; +        startx *= dev->session.pixel_count_multiplier; + +      /* current scan coordinates */ +        strpixel = dev->session.pixel_startx; +        endpixel = dev->session.pixel_endx; + +        if (dev->model->model_id == ModelId::CANON_4400F || +            dev->model->model_id == ModelId::CANON_8600F) +        { +            int half_ccd_factor = dev->session.optical_resolution / +                                  sensor.get_logical_hwdpi(dev->session.output_resolution); +            strpixel /= half_ccd_factor * sensor.ccd_pixels_per_system_pixel(); +            endpixel /= half_ccd_factor * sensor.ccd_pixels_per_system_pixel(); +        } + +      /* 16 bit words, 2 words per color, 3 color channels */ +      offset=(strpixel-startx)*2*2*3; +      length=(endpixel-strpixel)*2*2*3; +      DBG(DBG_info, "%s: STRPIXEL=%d, ENDPIXEL=%d, startx=%d\n", __func__, strpixel, endpixel, +          startx); +    } + +    dev->interface->record_key_value("shading_offset", std::to_string(offset)); +    dev->interface->record_key_value("shading_length", std::to_string(length)); + +  /* compute and allocate size for final data */ +  final_size = ((length+251) / 252) * 256; +  DBG(DBG_io, "%s: final shading size=%04x (length=%d)\n", __func__, final_size, length); +  std::vector<uint8_t> final_data(final_size, 0); + +  /* copy regular shading data to the expected layout */ +  buffer = final_data.data(); +  count = 0; + +  /* loop over calibration data */ +  for (i = 0; i < length; i++) +    { +      buffer[count] = data[offset+i]; +      count++; +      if ((count % (256*2)) == (252*2)) +	{ +	  count += 4*2; +	} +    } + +    dev->interface->write_buffer(0x3c, 0, final_data.data(), count, +                                 ScannerInterface::FLAG_SMALL_ADDRESS); +} + +bool CommandSetGl843::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    (void) dev; +    return true; +} + +void CommandSetGl843::wait_for_motor_stop(Genesys_Device* dev) const +{ +    (void) dev; +} + +std::unique_ptr<CommandSet> create_gl843_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl843{}); +} + +} // namespace gl843 +} // namespace genesys diff --git a/backend/genesys/gl843.h b/backend/genesys/gl843.h new file mode 100644 index 0000000..9f0a9e9 --- /dev/null +++ b/backend/genesys/gl843.h @@ -0,0 +1,139 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#include "genesys.h" +#include "command_set.h" + +#ifndef BACKEND_GENESYS_GL843_H +#define BACKEND_GENESYS_GL843_H + +namespace genesys { +namespace gl843 { + +class CommandSetGl843 : public CommandSet +{ +public: +    ~CommandSetGl843() override = default; + +    bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override; + +    void init(Genesys_Device* dev) const override; + +    void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              Genesys_Register_Set* regs, int* channels, +                              int* total_size) const override; + +    void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs) const override; + +    void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set& regs) const override; + +    void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set* reg, +                                    const ScanSession& session) const override; + +    void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override; +    void set_powersaving(Genesys_Device* dev, int delay) const override; +    void save_power(Genesys_Device* dev, bool enable) const override; + +    void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                    Genesys_Register_Set* regs, bool start_motor) const override; + +    void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override; + +    void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void search_start_position(Genesys_Device* dev) const override; + +    void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set& regs) const override; + +    void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set& regs, int dpi) const override; + +    SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   Genesys_Register_Set& regs) const override; + +    void wait_for_motor_stop(Genesys_Device* dev) const override; + +    void move_back_home(Genesys_Device* dev, bool wait_until_home) const override; + +    void update_hardware_sensors(struct Genesys_Scanner* s) const override; + +    void load_document(Genesys_Device* dev) const override; + +    void detect_document_end(Genesys_Device* dev) const override; + +    void eject_document(Genesys_Device* dev) const override; + +    void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                      bool forward, bool black) const override; + +    void move_to_ta(Genesys_Device* dev) const override; + +    void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data, +                           int size) const override; + +    ScanSession calculate_scan_session(const Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       const Genesys_Settings& settings) const override; + +    void asic_boot(Genesys_Device* dev, bool cold) const override; +}; + +enum SlopeTable +{ +    SCAN_TABLE = 0, // table 1 at 0x4000 +    BACKTRACK_TABLE = 1, // table 2 at 0x4800 +    STOP_TABLE = 2, // table 3 at 0x5000 +    FAST_TABLE = 3, // table 4 at 0x5800 +    HOME_TABLE = 4, // table 5 at 0x6000 +}; + +} // namespace gl843 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL843_H diff --git a/backend/genesys/gl843_registers.h b/backend/genesys/gl843_registers.h new file mode 100644 index 0000000..8ecb0fc --- /dev/null +++ b/backend/genesys/gl843_registers.h @@ -0,0 +1,382 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL843_REGISTERS_H +#define BACKEND_GENESYS_GL843_REGISTERS_H + +#include <cstdint> + +namespace genesys { +namespace gl843 { + +using RegAddr = std::uint16_t; +using RegMask = std::uint8_t; +using RegShift = unsigned; + +static constexpr RegAddr REG_0x01 = 0x01; +static constexpr RegMask REG_0x01_CISSET = 0x80; +static constexpr RegMask REG_0x01_DOGENB = 0x40; +static constexpr RegMask REG_0x01_DVDSET = 0x20; +static constexpr RegMask REG_0x01_STAGGER = 0x10; +static constexpr RegMask REG_0x01_COMPENB = 0x08; +static constexpr RegMask REG_0x01_TRUEGRAY = 0x04; +static constexpr RegMask REG_0x01_SHDAREA = 0x02; +static constexpr RegMask REG_0x01_SCAN = 0x01; + +static constexpr RegAddr REG_0x02 = 0x02; +static constexpr RegMask REG_0x02_NOTHOME = 0x80; +static constexpr RegMask REG_0x02_ACDCDIS = 0x40; +static constexpr RegMask REG_0x02_AGOHOME = 0x20; +static constexpr RegMask REG_0x02_MTRPWR = 0x10; +static constexpr RegMask REG_0x02_FASTFED = 0x08; +static constexpr RegMask REG_0x02_MTRREV = 0x04; +static constexpr RegMask REG_0x02_HOMENEG = 0x02; +static constexpr RegMask REG_0x02_LONGCURV = 0x01; + +static constexpr RegAddr REG_0x03 = 0x03; +static constexpr RegMask REG_0x03_LAMPDOG = 0x80; +static constexpr RegMask REG_0x03_AVEENB = 0x40; +static constexpr RegMask REG_0x03_XPASEL = 0x20; +static constexpr RegMask REG_0x03_LAMPPWR = 0x10; +static constexpr RegMask REG_0x03_LAMPTIM = 0x0f; + +static constexpr RegAddr REG_0x04 = 0x04; +static constexpr RegMask REG_0x04_LINEART = 0x80; +static constexpr RegMask REG_0x04_BITSET = 0x40; +static constexpr RegMask REG_0x04_AFEMOD = 0x30; +static constexpr RegMask REG_0x04_FILTER = 0x0c; +static constexpr RegMask REG_0x04_FESET = 0x03; + +static constexpr RegShift REG_0x04S_AFEMOD = 4; + +static constexpr RegAddr REG_0x05 = 0x05; +static constexpr RegMask REG_0x05_DPIHW = 0xc0; +static constexpr RegMask REG_0x05_DPIHW_600 = 0x00; +static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40; +static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80; +static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0; +static constexpr RegMask REG_0x05_MTLLAMP = 0x30; +static constexpr RegMask REG_0x05_GMMENB = 0x08; +static constexpr RegMask REG_0x05_MTLBASE = 0x03; + +static constexpr RegAddr REG_0x06 = 0x06; +static constexpr RegMask REG_0x06_SCANMOD = 0xe0; +static constexpr RegShift REG_0x06S_SCANMOD = 5; +static constexpr RegMask REG_0x06_PWRBIT = 0x10; +static constexpr RegMask REG_0x06_GAIN4 = 0x08; +static constexpr RegMask REG_0x06_OPTEST = 0x07; + +static constexpr RegMask REG_0x07_LAMPSIM = 0x80; + +static constexpr RegMask REG_0x08_DECFLAG = 0x40; +static constexpr RegMask REG_0x08_GMMFFR = 0x20; +static constexpr RegMask REG_0x08_GMMFFG = 0x10; +static constexpr RegMask REG_0x08_GMMFFB = 0x08; +static constexpr RegMask REG_0x08_GMMZR = 0x04; +static constexpr RegMask REG_0x08_GMMZG = 0x02; +static constexpr RegMask REG_0x08_GMMZB = 0x01; + +static constexpr RegMask REG_0x09_MCNTSET = 0xc0; +static constexpr RegMask REG_0x09_EVEN1ST = 0x20; +static constexpr RegMask REG_0x09_BLINE1ST = 0x10; +static constexpr RegMask REG_0x09_BACKSCAN = 0x08; +static constexpr RegMask REG_0x09_ENHANCE = 0x04; +static constexpr RegMask REG_0x09_SHORTTG = 0x02; +static constexpr RegMask REG_0x09_NWAIT = 0x01; + +static constexpr RegShift REG_0x09S_MCNTSET = 6; +static constexpr RegShift REG_0x09S_CLKSET = 4; + +static constexpr RegAddr REG_0x0B = 0x0b; +static constexpr RegMask REG_0x0B_DRAMSEL = 0x07; +static constexpr RegMask REG_0x0B_ENBDRAM = 0x08; +static constexpr RegMask REG_0x0B_RFHDIS = 0x10; +static constexpr RegMask REG_0x0B_CLKSET = 0xe0; +static constexpr RegMask REG_0x0B_24MHZ = 0x00; +static constexpr RegMask REG_0x0B_30MHZ = 0x20; +static constexpr RegMask REG_0x0B_40MHZ = 0x40; +static constexpr RegMask REG_0x0B_48MHZ = 0x60; +static constexpr RegMask REG_0x0B_60MHZ = 0x80; + +static constexpr RegAddr REG_0x0D = 0x0d; +static constexpr RegMask REG_0x0D_JAMPCMD = 0x80; +static constexpr RegMask REG_0x0D_DOCCMD = 0x40; +static constexpr RegMask REG_0x0D_CCDCMD = 0x20; +static constexpr RegMask REG_0x0D_FULLSTP = 0x10; +static constexpr RegMask REG_0x0D_SEND = 0x08; +static constexpr RegMask REG_0x0D_CLRMCNT = 0x04; +static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02; +static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01; + +static constexpr RegAddr REG_0x0F = 0x0f; + +static constexpr RegAddr REG_EXPR = 0x10; +static constexpr RegAddr REG_EXPG = 0x12; +static constexpr RegAddr REG_EXPB = 0x14; + +static constexpr RegMask REG_0x16_CTRLHI = 0x80; +static constexpr RegMask REG_0x16_TOSHIBA = 0x40; +static constexpr RegMask REG_0x16_TGINV = 0x20; +static constexpr RegMask REG_0x16_CK1INV = 0x10; +static constexpr RegMask REG_0x16_CK2INV = 0x08; +static constexpr RegMask REG_0x16_CTRLINV = 0x04; +static constexpr RegMask REG_0x16_CKDIS = 0x02; +static constexpr RegMask REG_0x16_CTRLDIS = 0x01; + +static constexpr RegMask REG_0x17_TGMODE = 0xc0; +static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00; +static constexpr RegMask REG_0x17_TGMODE_REF = 0x40; +static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80; +static constexpr RegMask REG_0x17_TGW = 0x3f; +static constexpr RegShift REG_0x17S_TGW = 0; + +static constexpr RegAddr REG_0x18 = 0x18; +static constexpr RegMask REG_0x18_CNSET = 0x80; +static constexpr RegMask REG_0x18_DCKSEL = 0x60; +static constexpr RegMask REG_0x18_CKTOGGLE = 0x10; +static constexpr RegMask REG_0x18_CKDELAY = 0x0c; +static constexpr RegMask REG_0x18_CKSEL = 0x03; + +static constexpr RegAddr REG_EXPDMY = 0x19; + +static constexpr RegMask REG_0x1A_TGLSW2 = 0x80; +static constexpr RegMask REG_0x1A_TGLSW1 = 0x40; +static constexpr RegMask REG_0x1A_MANUAL3 = 0x02; +static constexpr RegMask REG_0x1A_MANUAL1 = 0x01; +static constexpr RegMask REG_0x1A_CK4INV = 0x08; +static constexpr RegMask REG_0x1A_CK3INV = 0x04; +static constexpr RegMask REG_0x1A_LINECLP = 0x02; + +static constexpr RegAddr REG_0x1C = 0x1c; +static constexpr RegMask REG_0x1C_TGTIME = 0x07; + +static constexpr RegMask REG_0x1D_CK4LOW = 0x80; +static constexpr RegMask REG_0x1D_CK3LOW = 0x40; +static constexpr RegMask REG_0x1D_CK1LOW = 0x20; +static constexpr RegMask REG_0x1D_TGSHLD = 0x1f; +static constexpr RegShift REG_0x1DS_TGSHLD = 0; + + +static constexpr RegAddr REG_0x1E = 0x1e; +static constexpr RegMask REG_0x1E_WDTIME = 0xf0; +static constexpr RegShift REG_0x1ES_WDTIME = 4; +static constexpr RegMask REG_0x1E_LINESEL = 0x0f; +static constexpr RegShift REG_0x1ES_LINESEL = 0; + +static constexpr RegAddr REG_0x21 = 0x21; +static constexpr RegAddr REG_STEPNO = 0x21; +static constexpr RegAddr REG_FWDSTEP = 0x22; +static constexpr RegAddr REG_BWDSTEP = 0x23; +static constexpr RegAddr REG_FASTNO = 0x24; +static constexpr RegAddr REG_LINCNT = 0x25; + +static constexpr RegAddr REG_0x29 = 0x29; +static constexpr RegAddr REG_0x2A = 0x2a; +static constexpr RegAddr REG_0x2B = 0x2b; +static constexpr RegAddr REG_DPISET = 0x2c; +static constexpr RegAddr REG_0x2E = 0x2e; +static constexpr RegAddr REG_0x2F = 0x2f; + +static constexpr RegAddr REG_STRPIXEL = 0x30; +static constexpr RegAddr REG_ENDPIXEL = 0x32; +static constexpr RegAddr REG_DUMMY = 0x34; +static constexpr RegAddr REG_MAXWD = 0x35; +static constexpr RegAddr REG_LPERIOD = 0x38; +static constexpr RegAddr REG_FEEDL = 0x3d; + +static constexpr RegAddr REG_0x40 = 0x40; +static constexpr RegMask REG_0x40_DOCSNR = 0x80; +static constexpr RegMask REG_0x40_ADFSNR = 0x40; +static constexpr RegMask REG_0x40_COVERSNR = 0x20; +static constexpr RegMask REG_0x40_CHKVER = 0x10; +static constexpr RegMask REG_0x40_DOCJAM = 0x08; +static constexpr RegMask REG_0x40_HISPDFLG = 0x04; +static constexpr RegMask REG_0x40_MOTMFLG = 0x02; +static constexpr RegMask REG_0x40_DATAENB = 0x01; + +static constexpr RegMask REG_0x41_PWRBIT = 0x80; +static constexpr RegMask REG_0x41_BUFEMPTY = 0x40; +static constexpr RegMask REG_0x41_FEEDFSH = 0x20; +static constexpr RegMask REG_0x41_SCANFSH = 0x10; +static constexpr RegMask REG_0x41_HOMESNR = 0x08; +static constexpr RegMask REG_0x41_LAMPSTS = 0x04; +static constexpr RegMask REG_0x41_FEBUSY = 0x02; +static constexpr RegMask REG_0x41_MOTORENB = 0x01; + +static constexpr RegMask REG_0x58_VSMP = 0xf8; +static constexpr RegShift REG_0x58S_VSMP = 3; +static constexpr RegMask REG_0x58_VSMPW = 0x07; +static constexpr RegShift REG_0x58S_VSMPW = 0; + +static constexpr RegMask REG_0x59_BSMP = 0xf8; +static constexpr RegShift REG_0x59S_BSMP = 3; +static constexpr RegMask REG_0x59_BSMPW = 0x07; +static constexpr RegShift REG_0x59S_BSMPW = 0; + +static constexpr RegMask REG_0x5A_ADCLKINV = 0x80; +static constexpr RegMask REG_0x5A_RLCSEL = 0x40; +static constexpr RegMask REG_0x5A_CDSREF = 0x30; +static constexpr RegShift REG_0x5AS_CDSREF = 4; +static constexpr RegMask REG_0x5A_RLC = 0x0f; +static constexpr RegShift REG_0x5AS_RLC = 0; + +static constexpr RegAddr REG_0x5E = 0x5e; +static constexpr RegMask REG_0x5E_DECSEL = 0xe0; +static constexpr RegShift REG_0x5ES_DECSEL = 5; +static constexpr RegMask REG_0x5E_STOPTIM = 0x1f; +static constexpr RegShift REG_0x5ES_STOPTIM = 0; + +static constexpr RegAddr REG_FMOVDEC = 0x5f; + +static constexpr RegAddr REG_0x60 = 0x60; +static constexpr RegMask REG_0x60_Z1MOD = 0x1f; +static constexpr RegAddr REG_0x61 = 0x61; +static constexpr RegMask REG_0x61_Z1MOD = 0xff; +static constexpr RegAddr REG_0x62 = 0x62; +static constexpr RegMask REG_0x62_Z1MOD = 0xff; + +static constexpr RegAddr REG_0x63 = 0x63; +static constexpr RegMask REG_0x63_Z2MOD = 0x1f; +static constexpr RegAddr REG_0x64 = 0x64; +static constexpr RegMask REG_0x64_Z2MOD = 0xff; +static constexpr RegAddr REG_0x65 = 0x65; +static constexpr RegMask REG_0x65_Z2MOD = 0xff; + +static constexpr RegAddr REG_0x67 = 0x67; + +static constexpr RegAddr REG_0x68 = 0x68; + +static constexpr RegShift REG_0x67S_STEPSEL = 6; +static constexpr RegMask REG_0x67_STEPSEL = 0xc0; +static constexpr RegMask REG_0x67_FULLSTEP = 0x00; +static constexpr RegMask REG_0x67_HALFSTEP = 0x20; +static constexpr RegMask REG_0x67_EIGHTHSTEP = 0x60; +static constexpr RegMask REG_0x67_16THSTEP = 0x80; + +static constexpr RegShift REG_0x68S_FSTPSEL = 6; +static constexpr RegMask REG_0x68_FSTPSEL = 0xc0; +static constexpr RegMask REG_0x68_FULLSTEP = 0x00; +static constexpr RegMask REG_0x68_HALFSTEP = 0x20; +static constexpr RegMask REG_0x68_EIGHTHSTEP = 0x60; +static constexpr RegMask REG_0x68_16THSTEP = 0x80; + +static constexpr RegAddr REG_FSHDEC = 0x69; +static constexpr RegAddr REG_FMOVNO = 0x6a; + +static constexpr RegAddr REG_0x6B = 0x6b; +static constexpr RegMask REG_0x6B_MULTFILM = 0x80; +static constexpr RegMask REG_0x6B_GPOM13 = 0x40; +static constexpr RegMask REG_0x6B_GPOM12 = 0x20; +static constexpr RegMask REG_0x6B_GPOM11 = 0x10; +static constexpr RegMask REG_0x6B_GPOCK4 = 0x08; +static constexpr RegMask REG_0x6B_GPOCP = 0x04; +static constexpr RegMask REG_0x6B_GPOLEDB = 0x02; +static constexpr RegMask REG_0x6B_GPOADF = 0x01; + +static constexpr RegAddr REG_0x6C = 0x6c; +static constexpr RegMask REG_0x6C_GPIO16 = 0x80; +static constexpr RegMask REG_0x6C_GPIO15 = 0x40; +static constexpr RegMask REG_0x6C_GPIO14 = 0x20; +static constexpr RegMask REG_0x6C_GPIO13 = 0x10; +static constexpr RegMask REG_0x6C_GPIO12 = 0x08; +static constexpr RegMask REG_0x6C_GPIO11 = 0x04; +static constexpr RegMask REG_0x6C_GPIO10 = 0x02; +static constexpr RegMask REG_0x6C_GPIO9 = 0x01; +static constexpr RegMask REG_0x6C_GPIOH = 0xff; +static constexpr RegMask REG_0x6C_GPIOL = 0xff; + +static constexpr RegAddr REG_Z1MOD = 0x60; +static constexpr RegAddr REG_Z2MOD = 0x63; + +static constexpr RegAddr REG_0x6D = 0x6d; +static constexpr RegAddr REG_0x6E = 0x6e; +static constexpr RegAddr REG_0x6F = 0x6f; + +static constexpr RegAddr REG_CK1MAP = 0x74; +static constexpr RegAddr REG_CK3MAP = 0x77; +static constexpr RegAddr REG_CK4MAP = 0x7a; + +static constexpr RegAddr REG_0x7E = 0x7e; + +static constexpr RegAddr REG_0x9D = 0x9d; +static constexpr RegShift REG_0x9DS_STEPTIM = 2; + +static constexpr RegMask REG_0x87_LEDADD = 0x04; + +static constexpr RegAddr REG_0xA6 = 0xa6; +static constexpr RegMask REG_0xA6_GPIO24 = 0x80; +static constexpr RegMask REG_0xA6_GPIO23 = 0x40; +static constexpr RegMask REG_0xA6_GPIO22 = 0x20; +static constexpr RegMask REG_0xA6_GPIO21 = 0x10; +static constexpr RegMask REG_0xA6_GPIO20 = 0x08; +static constexpr RegMask REG_0xA6_GPIO19 = 0x04; +static constexpr RegMask REG_0xA6_GPIO18 = 0x02; +static constexpr RegMask REG_0xA6_GPIO17 = 0x01; +static constexpr RegAddr REG_0xA7 = 0xa7; +static constexpr RegMask REG_0xA7_GPOE24 = 0x80; +static constexpr RegMask REG_0xA7_GPOE23 = 0x40; +static constexpr RegMask REG_0xA7_GPOE22 = 0x20; +static constexpr RegMask REG_0xA7_GPOE21 = 0x10; +static constexpr RegMask REG_0xA7_GPOE20 = 0x08; +static constexpr RegMask REG_0xA7_GPOE19 = 0x04; +static constexpr RegMask REG_0xA7_GPOE18 = 0x02; +static constexpr RegMask REG_0xA7_GPOE17 = 0x01; +static constexpr RegAddr REG_0xA8 = 0xa8; +static constexpr RegMask REG_0xA8_GPOE27 = 0x20; +static constexpr RegMask REG_0xA8_GPOE26 = 0x10; +static constexpr RegMask REG_0xA8_GPOE25 = 0x08; +static constexpr RegMask REG_0xA8_GPO27 = 0x04; +static constexpr RegMask REG_0xA8_GPO26 = 0x02; +static constexpr RegMask REG_0xA8_GPO25 = 0x01; +static constexpr RegAddr REG_0xA9 = 0xa9; +static constexpr RegMask REG_0xA9_GPO33 = 0x20; +static constexpr RegMask REG_0xA9_GPO32 = 0x10; +static constexpr RegMask REG_0xA9_GPO31 = 0x08; +static constexpr RegMask REG_0xA9_GPO30 = 0x04; +static constexpr RegMask REG_0xA9_GPO29 = 0x02; +static constexpr RegMask REG_0xA9_GPO28 = 0x01; + +} // namespace gl843 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL843_REGISTERS_H diff --git a/backend/genesys/gl846.cpp b/backend/genesys/gl846.cpp new file mode 100644 index 0000000..d309d29 --- /dev/null +++ b/backend/genesys/gl846.cpp @@ -0,0 +1,2098 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2012-2013 Stéphane Voltz <stef.dev@free.fr> + + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +/** @file + * + * This file handles GL846 and GL845 ASICs since they are really close to each other. + */ + +#define DEBUG_DECLARE_ONLY + +#include "gl846.h" +#include "gl846_registers.h" +#include "test_settings.h" + +#include <vector> + +namespace genesys { +namespace gl846 { + +/** + * compute the step multiplier used + */ +static int +gl846_get_step_multiplier (Genesys_Register_Set * regs) +{ +    GenesysRegister *r = sanei_genesys_get_address(regs, 0x9d); +    int value = 1; +    if (r != nullptr) { +      value = (r->value & 0x0f)>>1; +      value = 1 << value; +    } +  DBG (DBG_io, "%s: step multiplier is %d\n", __func__, value); +  return value; +} + +/** @brief sensor specific settings +*/ +static void gl846_setup_sensor(Genesys_Device * dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set* regs) +{ +    DBG_HELPER(dbg); + +    for (const auto& reg : sensor.custom_regs) { +        regs->set8(reg.address, reg.value); +    } + +    regs->set16(REG_EXPR, sensor.exposure.red); +    regs->set16(REG_EXPG, sensor.exposure.green); +    regs->set16(REG_EXPB, sensor.exposure.blue); + +    dev->segment_order = sensor.segment_order; +} + + +/** @brief set all registers to default values . + * This function is called only once at the beginning and + * fills register startup values for registers reused across scans. + * Those that are rarely modified or not modified are written + * individually. + * @param dev device structure holding register set to initialize + */ +static void +gl846_init_registers (Genesys_Device * dev) +{ +    DBG_HELPER(dbg); + +    dev->reg.clear(); + +    dev->reg.init_reg(0x01, 0x60); +    dev->reg.init_reg(0x02, 0x38); +    dev->reg.init_reg(0x03, 0x03); +    dev->reg.init_reg(0x04, 0x22); +    dev->reg.init_reg(0x05, 0x60); +    dev->reg.init_reg(0x06, 0x10); +    dev->reg.init_reg(0x08, 0x60); +    dev->reg.init_reg(0x09, 0x00); +    dev->reg.init_reg(0x0a, 0x00); +    dev->reg.init_reg(0x0b, 0x8b); +    dev->reg.init_reg(0x0c, 0x00); +    dev->reg.init_reg(0x0d, 0x00); +    dev->reg.init_reg(0x10, 0x00); +    dev->reg.init_reg(0x11, 0x00); +    dev->reg.init_reg(0x12, 0x00); +    dev->reg.init_reg(0x13, 0x00); +    dev->reg.init_reg(0x14, 0x00); +    dev->reg.init_reg(0x15, 0x00); +    dev->reg.init_reg(0x16, 0xbb); // SENSOR_DEF +    dev->reg.init_reg(0x17, 0x13); // SENSOR_DEF +    dev->reg.init_reg(0x18, 0x10); // SENSOR_DEF +    dev->reg.init_reg(0x19, 0x2a); // SENSOR_DEF +    dev->reg.init_reg(0x1a, 0x34); // SENSOR_DEF +    dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x1c, 0x20); // SENSOR_DEF +    dev->reg.init_reg(0x1d, 0x06); // SENSOR_DEF +    dev->reg.init_reg(0x1e, 0xf0); +    dev->reg.init_reg(0x1f, 0x01); +    dev->reg.init_reg(0x20, 0x03); +    dev->reg.init_reg(0x21, 0x10); +    dev->reg.init_reg(0x22, 0x60); +    dev->reg.init_reg(0x23, 0x60); +    dev->reg.init_reg(0x24, 0x60); +    dev->reg.init_reg(0x25, 0x00); +    dev->reg.init_reg(0x26, 0x00); +    dev->reg.init_reg(0x27, 0x00); +    dev->reg.init_reg(0x2c, 0x00); +    dev->reg.init_reg(0x2d, 0x00); +    dev->reg.init_reg(0x2e, 0x80); +    dev->reg.init_reg(0x2f, 0x80); +    dev->reg.init_reg(0x30, 0x00); +    dev->reg.init_reg(0x31, 0x00); +    dev->reg.init_reg(0x32, 0x00); +    dev->reg.init_reg(0x33, 0x00); +    dev->reg.init_reg(0x34, 0x1f); +    dev->reg.init_reg(0x35, 0x00); +    dev->reg.init_reg(0x36, 0x40); +    dev->reg.init_reg(0x37, 0x00); +    dev->reg.init_reg(0x38, 0x2a); +    dev->reg.init_reg(0x39, 0xf8); +    dev->reg.init_reg(0x3d, 0x00); +    dev->reg.init_reg(0x3e, 0x00); +    dev->reg.init_reg(0x3f, 0x01); +    dev->reg.init_reg(0x52, 0x02); // SENSOR_DEF +    dev->reg.init_reg(0x53, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x54, 0x06); // SENSOR_DEF +    dev->reg.init_reg(0x55, 0x08); // SENSOR_DEF +    dev->reg.init_reg(0x56, 0x0a); // SENSOR_DEF +    dev->reg.init_reg(0x57, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x58, 0x59); // SENSOR_DEF +    dev->reg.init_reg(0x59, 0x31); // SENSOR_DEF +    dev->reg.init_reg(0x5a, 0x40); // SENSOR_DEF +    dev->reg.init_reg(0x5e, 0x1f); +    dev->reg.init_reg(0x5f, 0x01); +    dev->reg.init_reg(0x60, 0x00); +    dev->reg.init_reg(0x61, 0x00); +    dev->reg.init_reg(0x62, 0x00); +    dev->reg.init_reg(0x63, 0x00); +    dev->reg.init_reg(0x64, 0x00); +    dev->reg.init_reg(0x65, 0x00); +    dev->reg.init_reg(0x67, 0x7f); +    dev->reg.init_reg(0x68, 0x7f); +    dev->reg.init_reg(0x69, 0x01); +    dev->reg.init_reg(0x6a, 0x01); +    dev->reg.init_reg(0x70, 0x01); +    dev->reg.init_reg(0x71, 0x00); +    dev->reg.init_reg(0x72, 0x02); +    dev->reg.init_reg(0x73, 0x01); +    dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x76, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x79, 0x3f); // SENSOR_DEF +    dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7b, 0x09); // SENSOR_DEF +    dev->reg.init_reg(0x7c, 0x99); // SENSOR_DEF +    dev->reg.init_reg(0x7d, 0x20); +    dev->reg.init_reg(0x7f, 0x05); +    dev->reg.init_reg(0x80, 0x4f); +    dev->reg.init_reg(0x87, 0x02); +    dev->reg.init_reg(0x94, 0xff); +    dev->reg.init_reg(0x9d, 0x04); +    dev->reg.init_reg(0x9e, 0x00); +    dev->reg.init_reg(0xa1, 0xe0); +    dev->reg.init_reg(0xa2, 0x1f); +    dev->reg.init_reg(0xab, 0xc0); +    dev->reg.init_reg(0xbb, 0x00); +    dev->reg.init_reg(0xbc, 0x0f); +    dev->reg.init_reg(0xdb, 0xff); +    dev->reg.init_reg(0xfe, 0x08); +    dev->reg.init_reg(0xff, 0x02); +    dev->reg.init_reg(0x98, 0x20); +    dev->reg.init_reg(0x99, 0x00); +    dev->reg.init_reg(0x9a, 0x90); +    dev->reg.init_reg(0x9b, 0x00); +    dev->reg.init_reg(0xf8, 0x05); + +    const auto& sensor = sanei_genesys_find_sensor_any(dev); +    sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res); + +  /* initalize calibration reg */ +  dev->calib_reg = dev->reg; +} + +/**@brief send slope table for motor movement + * Send slope_table in machine byte order + * @param dev device to send slope table + * @param table_nr index of the slope table in ASIC memory + * Must be in the [0-4] range. + * @param slope_table pointer to 16 bit values array of the slope table + * @param steps number of elements in the slope table + */ +static void gl846_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps) +{ +    DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps); +  int i; +  char msg[10000]; + +  /* sanity check */ +  if(table_nr<0 || table_nr>4) +    { +        throw SaneException("invalid table number %d", table_nr); +    } + +  std::vector<uint8_t> table(steps * 2); +  for (i = 0; i < steps; i++) +    { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +    } + +  if (DBG_LEVEL >= DBG_io) +    { +        std::sprintf(msg, "write slope %d (%d)=", table_nr, steps); +      for (i = 0; i < steps; i++) +        { +            std::sprintf(msg+strlen(msg), "%d", slope_table[i]); +        } +      DBG (DBG_io, "%s: %s\n", __func__, msg); +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } +    // slope table addresses are fixed +    dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, steps * 2, table.data()); +} + +/** + * Set register values of Analog Device type frontend + * */ +static void gl846_set_adi_fe(Genesys_Device* dev, uint8_t set) +{ +    DBG_HELPER(dbg); +  int i; + +    // wait for FE to be ready +    auto status = scanner_read_status(*dev); +    while (status.is_front_end_busy) { +        dev->interface->sleep_ms(10); +        status = scanner_read_status(*dev); +    }; + +  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)); + +    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)); +    } +} + +// Set values of analog frontend +void CommandSetGl846::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" : +                               set == AFE_SET ? "set" : +                               set == AFE_POWER_SAVE ? "powersave" : "huh?"); +    (void) sensor; + +  /* route to specific analog frontend setup */ +    uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET; +    switch (frontend_type) { +      case 0x02: /* ADI FE */ +        gl846_set_adi_fe(dev, set); +        break; +      default: +            throw SaneException("unsupported frontend type %d", frontend_type); +    } +} + + +// @brief set up motor related register for scan +static void gl846_init_motor_regs_scan(Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       Genesys_Register_Set* reg, +                                       const Motor_Profile& motor_profile, +                                       unsigned int scan_exposure_time, +                                       unsigned scan_yres, +                                       unsigned int scan_lines, +                                       unsigned int scan_dummy, +                                       unsigned int feed_steps, +                                       MotorFlag flags) +{ +    DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, scan_yres=%d, step_type=%d, scan_lines=%d, " +                         "scan_dummy=%d, feed_steps=%d, flags=%x", +                    scan_exposure_time, scan_yres, static_cast<unsigned>(motor_profile.step_type), +                    scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags)); +  int use_fast_fed; +  unsigned int fast_dpi; +  unsigned int feedl, dist; +  GenesysRegister *r; +  uint32_t z1, z2; +  unsigned int min_restep = 0x20; +  uint8_t val; +  unsigned int ccdlmt,tgtime; + +    unsigned step_multiplier = gl846_get_step_multiplier(reg); + +  use_fast_fed=0; +  /* no fast fed since feed works well */ +    if (dev->settings.yres == 4444 && feed_steps > 100 && !has_flag(flags, MotorFlag::FEED)) { +        use_fast_fed = 1; +    } +  DBG (DBG_io, "%s: use_fast_fed=%d\n", __func__, use_fast_fed); + +    reg->set24(REG_LINCNT, scan_lines); +  DBG (DBG_io, "%s: lincnt=%d\n", __func__, scan_lines); + +  /* compute register 02 value */ +    r = sanei_genesys_get_address(reg, REG_0x02); +  r->value = 0x00; +  sanei_genesys_set_motor_power(*reg, true); + +  if (use_fast_fed) +        r->value |= REG_0x02_FASTFED; +  else +        r->value &= ~REG_0x02_FASTFED; + +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME; +    } + +    if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) ||(scan_yres>=sensor.optical_res)) { +        r->value |= REG_0x02_ACDCDIS; +    } +    if (has_flag(flags, MotorFlag::REVERSE)) { +        r->value |= REG_0x02_MTRREV; +    } else { +        r->value &= ~REG_0x02_MTRREV; +    } + +  /* scan and backtracking slope table */ +    auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres, +                                                scan_exposure_time, dev->motor.base_ydpi, +                                                step_multiplier, motor_profile); + +    gl846_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count); +    gl846_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count); + +  /* fast table */ +  fast_dpi=sanei_genesys_get_lowest_ydpi(dev); + +    // BUG: looks like for fast moves we use inconsistent step type +    StepType fast_step_type = motor_profile.step_type; +    if (static_cast<unsigned>(motor_profile.step_type) >= static_cast<unsigned>(StepType::QUARTER)) { +        fast_step_type = StepType::QUARTER; +    } + +    Motor_Profile fast_motor_profile = motor_profile; +    fast_motor_profile.step_type = fast_step_type; + +    auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_dpi, +                                                scan_exposure_time, dev->motor.base_ydpi, +                                                step_multiplier, fast_motor_profile); + +    gl846_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count); +    gl846_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count); +    gl846_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count); + +  /* correct move distance by acceleration and deceleration amounts */ +  feedl=feed_steps; +  if (use_fast_fed) +    { +        feedl <<= static_cast<unsigned>(fast_step_type); +        dist = (scan_table.steps_count + 2 * fast_table.steps_count); +        /* TODO read and decode REG_0xAB */ +        r = sanei_genesys_get_address (reg, 0x5e); +        dist += (r->value & 31); +        /* FEDCNT */ +        r = sanei_genesys_get_address(reg, REG_FEDCNT); +        dist += r->value; +    } +  else +    { +        feedl <<= static_cast<unsigned>(motor_profile.step_type); +        dist = scan_table.steps_count; +        if (has_flag(flags, MotorFlag::FEED)) { +            dist *= 2; +        } +    } +  DBG (DBG_io2, "%s: acceleration distance=%d\n", __func__, dist); + +  /* check for overflow */ +    if (dist < feedl) { +        feedl -= dist; +    } else { +        feedl = 0; +    } + +    reg->set24(REG_FEEDL, feedl); +  DBG (DBG_io ,"%s: feedl=%d\n",__func__,feedl); + +    r = sanei_genesys_get_address(reg, REG_0x0C); +    ccdlmt = (r->value & REG_0x0C_CCDLMT) + 1; + +    r = sanei_genesys_get_address(reg, REG_0x1C); +    tgtime = 1 << (r->value & REG_0x1C_TGTIME); + +  /* hi res motor speed GPIO */ +  /* +    uint8_t effective = dev->interface->read_register(REG_0x6C); +  */ + +  /* if quarter step, bipolar Vref2 */ +  /* XXX STEF XXX GPIO +  if (motor_profile.step_type > 1) +    { +      if (motor_profile.step_type < 3) +        { +            val = effective & ~REG_0x6C_GPIO13; +        } +      else +        { +            val = effective | REG_0x6C_GPIO13; +        } +    } +  else +    { +      val = effective; +    } +    dev->interface->write_register(REG_0x6C, val); +    */ + +  /* effective scan */ +  /* +    effective = dev->interface->read_register(REG_0x6C); +    val = effective | REG_0x6C_GPIO10; +    dev->interface->write_register(REG_0x6C, val); +  */ + +    if(dev->model->gpio_id == GpioId::IMG101) { +        if (scan_yres == sensor.get_register_hwdpi(scan_yres)) { +          val=1; +        } +      else +        { +          val=0; +        } +        dev->interface->write_register(REG_0x7E, val); +    } + +    min_restep = (scan_table.steps_count / step_multiplier) / 2 - 1; +    if (min_restep < 1) { +        min_restep = 1; +    } +    r = sanei_genesys_get_address(reg, REG_FWDSTEP); +  r->value = min_restep; +    r = sanei_genesys_get_address(reg, REG_BWDSTEP); +  r->value = min_restep; + +    sanei_genesys_calculate_zmod(use_fast_fed, +                                 scan_exposure_time*ccdlmt*tgtime, +                                 scan_table.table, +                                 scan_table.steps_count, +                                 feedl, +                                 min_restep * step_multiplier, +                                 &z1, +                                 &z2); + +  DBG(DBG_info, "%s: z1 = %d\n", __func__, z1); +    reg->set24(REG_0x60, z1 | (static_cast<unsigned>(motor_profile.step_type) << (16 + REG_0x60S_STEPSEL))); + +  DBG(DBG_info, "%s: z2 = %d\n", __func__, z2); +    reg->set24(REG_0x63, z2 | (static_cast<unsigned>(motor_profile.step_type) << (16 + REG_0x63S_FSTPSEL))); + +  r = sanei_genesys_get_address (reg, 0x1e); +  r->value &= 0xf0;		/* 0 dummy lines */ +  r->value |= scan_dummy;	/* dummy lines */ + +    r = sanei_genesys_get_address(reg, REG_0x67); +  r->value = 0x7f; + +    r = sanei_genesys_get_address(reg, REG_0x68); +  r->value = 0x7f; + +    reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FSHDEC, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier); +    reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier); +} + + +/** @brief set up registers related to sensor + * Set up the following registers +   0x01 +   0x03 +   0x10-0x015     R/G/B exposures +   0x19           EXPDMY +   0x2e           BWHI +   0x2f           BWLO +   0x04 +   0x87 +   0x05 +   0x2c,0x2d      DPISET +   0x30,0x31      STRPIXEL +   0x32,0x33      ENDPIXEL +   0x35,0x36,0x37 MAXWD [25:2] (>>2) +   0x38,0x39      LPERIOD +   0x34           DUMMY + */ +static void gl846_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set* reg, unsigned int exposure_time, +                                         const ScanSession& session) +{ +    DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time); +    unsigned int dpihw; +  GenesysRegister *r; + +    // resolution is divided according to ccd_pixels_per_system_pixel() +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); +    DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel); + +    // to manage high resolution device while keeping good low resolution scanning speed, +    // we make hardware dpi vary +    dpihw = sensor.get_register_hwdpi(session.params.xres * ccd_pixels_per_system_pixel); +    DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw); + +    gl846_setup_sensor(dev, sensor, reg); + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); + +  /* enable shading */ +    regs_set_optical_off(dev->model->asic_type, *reg); +    r = sanei_genesys_get_address(reg, REG_0x01); +    r->value |= REG_0x01_SHDAREA; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) || +        (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) +    { +        r->value &= ~REG_0x01_DVDSET; +    } +  else +    { +        r->value |= REG_0x01_DVDSET; +    } + +    r = sanei_genesys_get_address(reg, REG_0x03); +    r->value &= ~REG_0x03_AVEENB; + +    sanei_genesys_set_lamp_power(dev, sensor, *reg, +                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP)); + +  /* BW threshold */ +  r = sanei_genesys_get_address (reg, 0x2e); +  r->value = dev->settings.threshold; +  r = sanei_genesys_get_address (reg, 0x2f); +  r->value = dev->settings.threshold; + +  /* monochrome / color scan */ +    r = sanei_genesys_get_address(reg, REG_0x04); +    switch (session.params.depth) { +    case 8: +            r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +      break; +    case 16: +            r->value &= ~REG_0x04_LINEART; +            r->value |= REG_0x04_BITSET; +      break; +    } + +    r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD); +  if (session.params.channels == 1) +    { +      switch (session.params.color_filter) +        { +            case ColorFilter::RED: +                r->value |= 0x24; +                break; +            case ColorFilter::BLUE: +                r->value |= 0x2c; +                break; +            case ColorFilter::GREEN: +                r->value |= 0x28; +                break; +            default: +                break; // should not happen +        } +    } else { +        r->value |= 0x20; // mono +    } + +    sanei_genesys_set_dpihw(*reg, sensor, dpihw); + +    if (should_enable_gamma(session, sensor)) { +        reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB; +    } else { +        reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB; +    } + +  /* CIS scanners can do true gray by setting LEDADD */ +  /* we set up LEDADD only when asked */ +    if (dev->model->is_cis) { +      r = sanei_genesys_get_address (reg, 0x87); +        r->value &= ~REG_0x87_LEDADD; +        if (session.enable_ledadd) { +            r->value |= REG_0x87_LEDADD; +        } +      /* RGB weighting +      r = sanei_genesys_get_address (reg, 0x01); +        r->value &= ~REG_0x01_TRUEGRAY; +      if (session.enable_ledadd)) +        { +            r->value |= REG_0x01_TRUEGRAY; +        }*/ +    } + +    unsigned dpiset = session.params.xres * ccd_pixels_per_system_pixel; +    reg->set16(REG_DPISET, dpiset); +    DBG(DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset); + +    reg->set16(REG_STRPIXEL, session.pixel_startx); +    reg->set16(REG_ENDPIXEL, session.pixel_endx); + +    build_image_pipeline(dev, session); + +  /* MAXWD is expressed in 4 words unit */ +    // BUG: we shouldn't multiply by channels here +    reg->set24(REG_MAXWD, (session.output_line_bytes_raw * session.params.channels >> 2)); + +    reg->set16(REG_LPERIOD, exposure_time); +  DBG (DBG_io2, "%s: exposure_time used=%d\n", __func__, exposure_time); + +  r = sanei_genesys_get_address (reg, 0x34); +  r->value = sensor.dummy_pixel; +} + +void CommandSetGl846::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* reg, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +  int move; +  int exposure_time; + +  int slope_dpi = 0; +  int dummy = 0; + +  dummy = 3-session.params.channels; + +/* slope_dpi */ +/* cis color scan is effectively a gray scan with 3 gray lines per color +   line and a FILTER of 0 */ +    if (dev->model->is_cis) { +        slope_dpi = session.params.yres * session.params.channels; +    } else { +        slope_dpi = session.params.yres; +    } + +  slope_dpi = slope_dpi * (1 + dummy); + +    exposure_time = sensor.exposure_lperiod; +    const auto& motor_profile = sanei_genesys_get_motor_profile(*gl846_motor_profiles, +                                                                dev->model->motor_id, +                                                                exposure_time); + +  DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time); +    DBG(DBG_info, "%s : scan_step_type=%d\n", __func__, +        static_cast<unsigned>(motor_profile.step_type)); + +  /* we enable true gray for cis scanners only, and just when doing +   * scan since color calibration is OK for this mode +   */ +    gl846_init_optical_regs_scan(dev, sensor, reg, exposure_time, session); + +/*** motor parameters ***/ + +  /* add tl_y to base movement */ +  move = session.params.starty; +  DBG(DBG_info, "%s: move=%d steps\n", __func__, move); + +    MotorFlag mflags = MotorFlag::NONE; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) { +        mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE; +    } +    if (has_flag(session.params.flags, ScanFlag::FEEDING)) { +        mflags |= MotorFlag::FEED; +    } +    if (has_flag(session.params.flags, ScanFlag::REVERSE)) { +        mflags |= MotorFlag::REVERSE; +    } + +    gl846_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure_time, slope_dpi, +                               dev->model->is_cis ? session.output_line_count * session.params.channels +                                                  : session.output_line_count, +                               dummy, move, mflags); + +  /*** prepares data reordering ***/ + +    dev->read_buffer.clear(); +    dev->read_buffer.alloc(session.buffer_size_read); + +    dev->read_active = true; + +    dev->session = session; + +    dev->total_bytes_read = 0; +    dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines; + +    DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read); +} + +ScanSession CommandSetGl846::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +  int start; + +    DBG(DBG_info, "%s ", __func__); +    debug_dump(DBG_info, settings); + +  /* start */ +    start = static_cast<int>(dev->model->x_offset); +    start += static_cast<int>(settings.tl_x); +    start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = start; // not used +    session.params.starty = 0; // not used +    session.params.pixels = settings.pixels; +    session.params.requested_pixels = settings.requested_pixels; +    session.params.lines = settings.lines; +    session.params.depth = settings.depth; +    session.params.channels = settings.get_channels(); +    session.params.scan_method = settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; + +    compute_session(dev, session, sensor); + +    return session; +} + +// for fast power saving methods only, like disabling certain amplifiers +void CommandSetGl846::save_power(Genesys_Device* dev, bool enable) const +{ +    (void) dev; +    DBG_HELPER_ARGS(dbg, "enable = %d", enable); +} + +void CommandSetGl846::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    (void) dev; +    DBG_HELPER_ARGS(dbg, "delay = %d", delay); +} + +// Send the low-level scan command +void CommandSetGl846::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set* reg, bool start_motor) const +{ +    DBG_HELPER(dbg); +    (void) sensor; +  uint8_t val; +  GenesysRegister *r; + +  /* XXX STEF XXX SCAN GPIO */ +  /* +    val = dev->interface->read_register(REG_0x6C); +    dev->interface->write_register(REG_0x6C, val); +  */ + +    val = REG_0x0D_CLRLNCNT; +    dev->interface->write_register(REG_0x0D, val); +    val = REG_0x0D_CLRMCNT; +    dev->interface->write_register(REG_0x0D, val); + +    val = dev->interface->read_register(REG_0x01); +    val |= REG_0x01_SCAN; +    dev->interface->write_register(REG_0x01, val); +    r = sanei_genesys_get_address (reg, REG_0x01); +  r->value = val; + +    scanner_start_action(*dev, start_motor); + +    dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +} + + +// Send the stop scan command +void CommandSetGl846::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg, +                               bool check_stop) const +{ +    (void) reg; +    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop); + +    if (!dev->model->is_sheetfed) { +        scanner_stop_action(*dev); +    } +} + +// Moves the slider to the home (top) postion slowly +void CommandSetGl846::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    scanner_move_back_home(*dev, wait_until_home); +} + +// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi +// from very top of scanner +void CommandSetGl846::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  int size; +  Genesys_Register_Set local_reg; + +  int pixels = 600; +  int dpi = 300; + +  local_reg = dev->reg; + +  /* sets for a 200 lines * 600 pixels */ +  /* normal scan with no shading */ + +    // FIXME: the current approach of doing search only for one resolution does not work on scanners +    // whith employ different sensors with potentially different settings. +    const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method); + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres =  dpi; +    session.params.startx = 0; +    session.params.starty =  0;	/*we should give a small offset here~60 steps */ +    session.params.pixels = 600; +    session.params.lines = dev->model->search_lines; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::GREEN; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    // send to scanner +    dev->interface->write_registers(local_reg); + +  size = pixels * dev->model->search_lines; + +  std::vector<uint8_t> data(size); + +    begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_start_position"); +        end_scan(dev, &local_reg, true); +        dev->reg = local_reg; +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl846_search_position.pnm", data.data(), 8, 1, pixels, +                                     dev->model->search_lines); +    } + +    end_scan(dev, &local_reg, true); + +  /* update regs to copy ASIC internal state */ +  dev->reg = local_reg; + +    // TODO: find out where sanei_genesys_search_reference_point stores information, +    // and use that correctly +    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, dpi, pixels, +                                             dev->model->search_lines); +    } +} + +// sets up register for coarse gain calibration +// todo: check it for scanners using it +void CommandSetGl846::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel(); +    session.params.lines = 20; +    session.params.depth = 16; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +    DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__, +        sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres); + +    dev->interface->write_registers(regs); +} + +// init registers for shading calibration +void CommandSetGl846::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  float move; + +  dev->calib_channels = 3; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    dev->calib_resolution = sensor.get_register_hwdpi(dev->settings.xres); + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, dev->calib_resolution, +                                                         dev->calib_channels, +                                                         dev->settings.scan_method); +  dev->calib_total_bytes_to_read = 0; +  dev->calib_lines = dev->model->shading_lines; +    if (dev->calib_resolution==4800) { +        dev->calib_lines *= 2; +    } +    dev->calib_pixels = (calib_sensor.sensor_pixels * dev->calib_resolution) / +                        calib_sensor.optical_res; + +    DBG(DBG_io, "%s: calib_lines  = %zu\n", __func__, dev->calib_lines); +    DBG(DBG_io, "%s: calib_pixels = %zu\n", __func__, dev->calib_pixels); + +  /* this is aworkaround insufficent distance for slope +   * motor acceleration TODO special motor slope for shading  */ +  move=1; +  if(dev->calib_resolution<1200) +    { +      move=40; +    } + +    ScanSession session; +    session.params.xres = dev->calib_resolution; +    session.params.yres = dev->calib_resolution; +    session.params.startx = 0; +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->calib_pixels; +    session.params.lines = dev->calib_lines; +    session.params.depth = 16; +    session.params.channels = dev->calib_channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                   ScanFlag::DISABLE_GAMMA | +                   ScanFlag::DISABLE_BUFFER_FULL_MOVE | +                   ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); + +  /* we use GENESYS_FLAG_SHADING_REPARK */ +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); +} + +/** @brief set up registers for the actual scan + */ +void CommandSetGl846::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  float move; +  int move_dpi; +  float start; + +    debug_dump(DBG_info, dev->settings); + +  /* steps to move to reach scanning area: +     - first we move to physical start of scanning +     either by a fixed steps amount from the black strip +     or by a fixed amount from parking position, +     minus the steps done during shading calibration +     - then we move by the needed offset whitin physical +     scanning area + +     assumption: steps are expressed at maximum motor resolution + +     we need: +     float y_offset; +     float y_size; +     float y_offset_calib; +     mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH */ + +  /* if scanner uses GENESYS_FLAG_SEARCH_START y_offset is +     relative from origin, else, it is from parking position */ + +  move_dpi = dev->motor.base_ydpi; + +    move = static_cast<float>(dev->model->y_offset); +    move = static_cast<float>(move + dev->settings.tl_y); +    move = static_cast<float>((move * move_dpi) / MM_PER_INCH); +    move -= dev->head_pos(ScanHeadId::PRIMARY); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* fast move to scan area */ +  /* we don't move fast the whole distance since it would involve +   * computing acceleration/deceleration distance for scan +   * resolution. So leave a remainder for it so scan makes the final +   * move tuning */ +    if (dev->settings.get_channels() * dev->settings.yres >= 600 && move > 700) { +        scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move - 500), +                     Direction::FORWARD); +      move=500; +    } + +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* start */ +    start = static_cast<float>(dev->model->x_offset); +    start = static_cast<float>(start + dev->settings.tl_x); +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->settings.pixels; +    session.params.requested_pixels = dev->settings.requested_pixels; +    session.params.lines = dev->settings.lines; +    session.params.depth = dev->settings.depth; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    // backtracking isn't handled well, so don't enable it +    session.params.flags = ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); +} + + +/** + * Send shading calibration data. The buffer is considered to always hold values + * for all the channels. + */ +void CommandSetGl846::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        uint8_t* data, int size) const +{ +    DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size); +  uint32_t addr, length, i, x, factor, pixels; +    uint32_t dpiset, dpihw; +  uint8_t val,*ptr,*src; + +  /* shading data is plit in 3 (up to 5 with IR) areas +     write(0x10014000,0x00000dd8) +     URB 23429  bulk_out len  3544  wrote 0x33 0x10 0x.... +     write(0x1003e000,0x00000dd8) +     write(0x10068000,0x00000dd8) +   */ +    length = static_cast<uint32_t>(size / 3); +    unsigned strpixel = dev->session.pixel_startx; +    unsigned endpixel = dev->session.pixel_endx; + +  /* compute deletion factor */ +    dpiset = dev->reg.get16(REG_DPISET); +    dpihw = sensor.get_register_hwdpi(dpiset); +  factor=dpihw/dpiset; +  DBG(DBG_io2, "%s: factor=%d\n", __func__, factor); + +  pixels=endpixel-strpixel; + +  /* since we're using SHDAREA, substract startx coordinate from shading */ +    strpixel -= (sensor.ccd_start_xoffset * 600) / sensor.optical_res; + +  /* turn pixel value into bytes 2x16 bits words */ +  strpixel*=2*2; +  pixels*=2*2; + +    dev->interface->record_key_value("shading_offset", std::to_string(strpixel)); +    dev->interface->record_key_value("shading_pixels", std::to_string(pixels)); +    dev->interface->record_key_value("shading_length", std::to_string(length)); +    dev->interface->record_key_value("shading_factor", std::to_string(factor)); + +  std::vector<uint8_t> buffer(pixels, 0); + +  DBG(DBG_io2, "%s: using chunks of %d (0x%04x) bytes\n", __func__, pixels, pixels); + +  /* base addr of data has been written in reg D0-D4 in 4K word, so AHB address +   * is 8192*reg value */ + +  /* write actual color channel data */ +  for(i=0;i<3;i++) +    { +      /* build up actual shading data by copying the part from the full width one +       * to the one corresponding to SHDAREA */ +      ptr = buffer.data(); + +      /* iterate on both sensor segment */ +      for(x=0;x<pixels;x+=4*factor) +        { +          /* coefficient source */ +          src=(data+strpixel+i*length)+x; + +          /* coefficient copy */ +          ptr[0]=src[0]; +          ptr[1]=src[1]; +          ptr[2]=src[2]; +          ptr[3]=src[3]; + +          /* next shading coefficient */ +          ptr+=4; +        } + +        val = dev->interface->read_register(0xd0+i); +        addr = val * 8192 + 0x10000000; +        dev->interface->write_ahb(addr, pixels, buffer.data()); +    } +} + +/** @brief calibrates led exposure + * Calibrate exposure by scanning a white area until the used exposure gives + * data white enough. + * @param dev device to calibrate + */ +SensorExposure CommandSetGl846::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int total_size; +  int used_res; +  int i, j; +  int val; +    int channels; +  int avg[3], top[3], bottom[3]; +  int turn; +  uint16_t exp[3]; + +    float move = static_cast<float>(dev->model->y_offset_calib_white); +     move = static_cast<float>((move * (dev->motor.base_ydpi / 4)) / MM_PER_INCH); +  if(move>20) +    { +        scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move), +                     Direction::FORWARD); +    } +  DBG(DBG_io, "%s: move=%f steps\n", __func__, move); + +  /* offset calibration is always done in color mode */ +  channels = 3; +    used_res = sensor.get_register_hwdpi(dev->settings.xres); +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, used_res, channels, +                                                         dev->settings.scan_method); +    num_pixels = (calib_sensor.sensor_pixels * used_res) / calib_sensor.optical_res; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    ScanSession session; +    session.params.xres = used_res; +    session.params.yres = used_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    total_size = num_pixels * channels * (session.params.depth / 8) * 1; +  std::vector<uint8_t> line(total_size); + +  /* initial loop values and boundaries */ +    exp[0] = calib_sensor.exposure.red; +    exp[1] = calib_sensor.exposure.green; +    exp[2] = calib_sensor.exposure.blue; + +  bottom[0]=29000; +  bottom[1]=29000; +  bottom[2]=29000; + +  top[0]=41000; +  top[1]=51000; +  top[2]=51000; + +  turn = 0; + +  /* no move during led calibration */ +  sanei_genesys_set_motor_power(regs, false); +    bool acceptable = false; +  do +    { +        // set up exposure +        regs.set16(REG_EXPR, exp[0]); +        regs.set16(REG_EXPG, exp[1]); +        regs.set16(REG_EXPB, exp[2]); + +        // write registers and scan data +        dev->interface->write_registers(regs); + +      DBG(DBG_info, "%s: starting line reading\n", __func__); +        begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("led_calibration"); +            scanner_stop_action(*dev); +            move_back_home(dev, true); +            return calib_sensor.exposure; +        } + +        sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +        // stop scanning +        scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +        { +          char fn[30]; +            std::snprintf(fn, 30, "gl846_led_%02d.pnm", turn); +            sanei_genesys_write_pnm_file(fn, line.data(), session.params.depth, +                                         channels, num_pixels, 1); +        } + +      /* compute average */ +      for (j = 0; j < channels; j++) +        { +          avg[j] = 0; +          for (i = 0; i < num_pixels; i++) +            { +              if (dev->model->is_cis) +                val = +                  line[i * 2 + j * 2 * num_pixels + 1] * 256 + +                  line[i * 2 + j * 2 * num_pixels]; +              else +                val = +                  line[i * 2 * channels + 2 * j + 1] * 256 + +                  line[i * 2 * channels + 2 * j]; +              avg[j] += val; +            } + +          avg[j] /= num_pixels; +        } + +      DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +      /* check if exposure gives average within the boundaries */ +        acceptable = true; +      for(i=0;i<3;i++) +        { +          if(avg[i]<bottom[i]) +            { +              exp[i]=(exp[i]*bottom[i])/avg[i]; +                acceptable = false; +            } +          if(avg[i]>top[i]) +            { +              exp[i]=(exp[i]*top[i])/avg[i]; +                acceptable = false; +            } +        } + +      turn++; +    } +  while (!acceptable && turn < 100); + +  DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]); + +    // set these values as final ones for scan +    dev->reg.set16(REG_EXPR, exp[0]); +    dev->reg.set16(REG_EXPG, exp[1]); +    dev->reg.set16(REG_EXPB, exp[2]); + +  /* go back home */ +  if(move>20) +    { +        move_back_home(dev, true); +    } + +    return { exp[0], exp[1], exp[2] }; +} + +/** + * set up GPIO/GPOE for idle state + */ +static void gl846_init_gpio(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  int idx=0; + +  /* search GPIO profile */ +    while (gpios[idx].gpio_id != GpioId::UNKNOWN && dev->model->gpio_id != gpios[idx].gpio_id) { +      idx++; +    } +    if (gpios[idx].gpio_id == GpioId::UNKNOWN) +    { +        throw SaneException("failed to find GPIO profile for sensor_id=%d", +                            static_cast<unsigned>(dev->model->sensor_id)); +    } + +    dev->interface->write_register(REG_0xA7, gpios[idx].ra7); +    dev->interface->write_register(REG_0xA6, gpios[idx].ra6); + +    dev->interface->write_register(REG_0x6B, gpios[idx].r6b); +    dev->interface->write_register(REG_0x6C, gpios[idx].r6c); +    dev->interface->write_register(REG_0x6D, gpios[idx].r6d); +    dev->interface->write_register(REG_0x6E, gpios[idx].r6e); +    dev->interface->write_register(REG_0x6F, gpios[idx].r6f); + +    dev->interface->write_register(REG_0xA8, gpios[idx].ra8); +    dev->interface->write_register(REG_0xA9, gpios[idx].ra9); +} + +/** + * set memory layout by filling values in dedicated registers + */ +static void gl846_init_memory_layout(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  int idx = 0, i; +  uint8_t val; + +  /* point to per model memory layout */ +  idx = 0; +    while (layouts[idx].model != nullptr && strcmp(dev->model->name,layouts[idx].model)!=0) { +      if(strcmp(dev->model->name,layouts[idx].model)!=0) +        idx++; +    } +    if (layouts[idx].model == nullptr) { +        throw SaneException("failed to find memory layout for model %s", dev->model->name); +    } + +  /* CLKSET and DRAMSEL */ +  val = layouts[idx].dramsel; +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +  /* prevent further writings by bulk write register */ +  dev->reg.remove_reg(0x0b); + +  /* setup base address for shading and scanned data. */ +  for(i=0;i<10;i++) +    { +      dev->interface->write_register(0xe0+i, layouts[idx].rx[i]); +    } +} + +/* * + * initialize ASIC from power on condition + */ +void CommandSetGl846::asic_boot(Genesys_Device* dev, bool cold) const +{ +    DBG_HELPER(dbg); +  uint8_t val; + +    // reset ASIC if cold boot +    if (cold) { +        dev->interface->write_register(0x0e, 0x01); +        dev->interface->write_register(0x0e, 0x00); +    } + +  if(dev->usb_mode == 1) +    { +      val = 0x14; +    } +  else +    { +      val = 0x11; +    } +    dev->interface->write_0x8c(0x0f, val); + +    // test CHKVER +    val = dev->interface->read_register(REG_0x40); +    if (val & REG_0x40_CHKVER) { +        val = dev->interface->read_register(0x00); +        DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val); +    } + +  /* Set default values for registers */ +  gl846_init_registers (dev); + +    // Write initial registers +    dev->interface->write_registers(dev->reg); + +  /* Enable DRAM by setting a rising edge on bit 3 of reg 0x0b */ +    val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL; +    val = (val | REG_0x0B_ENBDRAM); +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +  /* CIS_LINE */ +  if (dev->model->is_cis) +    { +        dev->reg.init_reg(0x08, REG_0x08_CIS_LINE); +        dev->interface->write_register(0x08, dev->reg.find_reg(0x08).value); +    } + +    // set up clocks +    dev->interface->write_0x8c(0x10, 0x0e); +    dev->interface->write_0x8c(0x13, 0x0e); + +    // setup gpio +    gl846_init_gpio(dev); + +    // setup internal memory layout +    gl846_init_memory_layout(dev); + +  dev->reg.init_reg(0xf8, 0x05); +    dev->interface->write_register(0xf8, dev->reg.find_reg(0xf8).value); +} + +/** + * initialize backend and ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + */ +void CommandSetGl846::init(Genesys_Device* dev) const +{ +  DBG_INIT (); +    DBG_HELPER(dbg); + +    sanei_genesys_asic_init(dev, 0); +} + +void CommandSetGl846::update_hardware_sensors(Genesys_Scanner* s) const +{ +    DBG_HELPER(dbg); +  /* do what is needed to get a new set of events, but try to not lose +     any of them. +   */ +  uint8_t val; +  uint8_t scan, file, email, copy; +  switch(s->dev->model->gpio_id) +    { +      default: +        scan=0x01; +        file=0x02; +        email=0x04; +        copy=0x08; +    } +    val = s->dev->interface->read_register(REG_0x6D); + +    s->buttons[BUTTON_SCAN_SW].write((val & scan) == 0); +    s->buttons[BUTTON_FILE_SW].write((val & file) == 0); +    s->buttons[BUTTON_EMAIL_SW].write((val & email) == 0); +    s->buttons[BUTTON_COPY_SW].write((val & copy) == 0); +} + + +void CommandSetGl846::update_home_sensor_gpio(Genesys_Device& dev) const +{ +    DBG_HELPER(dbg); + +    std::uint8_t val = dev.interface->read_register(REG_0x6C); +    val |= 0x41; +    dev.interface->write_register(REG_0x6C, val); +} + +/** @brief search for a full width black or white strip. + * This function searches for a black or white stripe across the scanning area. + * When searching backward, the searched area must completely be of the desired + * color since this area will be used for calibration which scans forward. + * @param dev scanner device + * @param forward true if searching forward, false if searching backward + * @param black true if searching for a black strip, false for a white strip + */ +void CommandSetGl846::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward, +                                   bool black) const +{ +    DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse"); +  unsigned int pixels, lines, channels; +  Genesys_Register_Set local_reg; +  size_t size; +  unsigned int pass, count, found, x, y; +  char title[80]; + +    set_fe(dev, sensor, AFE_SET); + +    scanner_stop_action(*dev); + +    // set up for a gray scan at lowest dpi +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method); +    unsigned dpi = resolution_settings.get_min_resolution_x(); +  channels = 1; +  /* 10 MM */ +  /* lines = (10 * dpi) / MM_PER_INCH; */ +  /* shading calibation is done with dev->motor.base_ydpi */ +  lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi; +  pixels = (sensor.sensor_pixels * dpi) / sensor.optical_res; + +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +  local_reg = dev->reg; + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA; +    if (!forward) { +        session.params.flags |= ScanFlag::REVERSE; +    } +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    size = pixels * channels * lines * (session.params.depth / 8); +    std::vector<uint8_t> data(size); + +    dev->interface->write_registers(local_reg); + +    begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_strip"); +        scanner_stop_action(*dev); +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    scanner_stop_action(*dev); + +  pass = 0; +  if (DBG_LEVEL >= DBG_data) +    { +        std::sprintf(title, "gl846_search_strip_%s_%s%02d.pnm", +                     black ? "black" : "white", forward ? "fwd" : "bwd", pass); +        sanei_genesys_write_pnm_file(title, data.data(), session.params.depth, +                                     channels, pixels, lines); +    } + +  /* loop until strip is found or maximum pass number done */ +  found = 0; +  while (pass < 20 && !found) +    { +        dev->interface->write_registers(local_reg); + +        // now start scan +        begin_scan(dev, sensor, &local_reg, true); + +        wait_until_buffer_non_empty(dev); + +        // now we're on target, we can read data +        sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +        scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +        { +            std::sprintf(title, "gl846_search_strip_%s_%s%02d.pnm", +                         black ? "black" : "white", forward ? "fwd" : "bwd", pass); +            sanei_genesys_write_pnm_file(title, data.data(), session.params.depth, +                                         channels, pixels, 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 < lines && !found; y++) +            { +              count = 0; +              /* count of white/black pixels depending on the color searched */ +              for (x = 0; x < pixels; x++) +                { +                  /* when searching for black, detect white pixels */ +                  if (black && data[y * pixels + x] > 90) +                    { +                      count++; +                    } +                  /* when searching for white, detect black pixels */ +                  if (!black && data[y * 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) / pixels < 3) +                { +                  found = 1; +                  DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__, +                      pass, y); +                } +              else +                { +                  DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +                      (100 * count) / pixels); +                } +            } +        } +      else			/* since calibration scans are done forward, we need the whole area +                                   to be of the required color when searching backward */ +        { +          count = 0; +          for (y = 0; y < lines; y++) +            { +              /* count of white/black pixels depending on the color searched */ +              for (x = 0; x < pixels; x++) +                { +                  /* when searching for black, detect white pixels */ +                  if (black && data[y * pixels + x] > 90) +                    { +                      count++; +                    } +                  /* when searching for white, detect black pixels */ +                  if (!black && data[y * 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) / (pixels * lines) < 3) +            { +              found = 1; +              DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); +            } +          else +            { +              DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +                  (100 * count) / pixels); +            } +        } +      pass++; +    } + +  if (found) +    { +      DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white"); +    } +  else +    { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white"); +    } +} + +/** + * average dark pixels of a 8 bits 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; +} + +void CommandSetGl846::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    unsigned channels; +  int pass = 0, avg, total_size; +    int topavg, bottomavg, lines; +  int top, bottom, black_pixels, pixels; + +    // no gain nor offset for AKM AFE +    uint8_t reg04 = dev->interface->read_register(REG_0x04); +    if ((reg04 & REG_0x04_FESET) == 0x02) { +      return; +    } + +  /* offset calibration is always done in color mode */ +  channels = 3; +  dev->calib_pixels = sensor.sensor_pixels; +  lines=1; +    pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res; +    black_pixels = (sensor.black_pixels * sensor.optical_res) / sensor.optical_res; +  DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels); + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = sensor.optical_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +    total_size = pixels * channels * lines * (session.params.depth / 8); + +  std::vector<uint8_t> first_line(total_size); +  std::vector<uint8_t> second_line(total_size); + +  /* init gain */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); + +  /* scan with no move */ +  bottom = 10; +  dev->frontend.set_offset(0, bottom); +  dev->frontend.set_offset(1, bottom); +  dev->frontend.set_offset(2, bottom); + +    set_fe(dev, sensor, AFE_SET); +    dev->interface->write_registers(regs); +  DBG(DBG_info, "%s: starting first line reading\n", __func__); +    begin_scan(dev, sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("offset_calibration"); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +  if (DBG_LEVEL >= DBG_data) +   { +      char fn[30]; +        std::snprintf(fn, 30, "gl846_offset%03d.pnm", bottom); +        sanei_genesys_write_pnm_file(fn, first_line.data(), session.params.depth, +                                     channels, pixels, lines); +   } + +  bottomavg = dark_average(first_line.data(), pixels, lines, channels, black_pixels); +  DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg); + +  /* now top value */ +  top = 255; +  dev->frontend.set_offset(0, top); +  dev->frontend.set_offset(1, top); +  dev->frontend.set_offset(2, top); +    set_fe(dev, sensor, AFE_SET); +    dev->interface->write_registers(regs); +  DBG(DBG_info, "%s: starting second line reading\n", __func__); +    begin_scan(dev, sensor, ®s, true); +    sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +  topavg = dark_average(second_line.data(), pixels, lines, channels, black_pixels); +  DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg); + +  /* 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 +        set_fe(dev, sensor, AFE_SET); +        dev->interface->write_registers(regs); +      DBG(DBG_info, "%s: starting second line reading\n", __func__); +        begin_scan(dev, sensor, ®s, true); +        sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +      if (DBG_LEVEL >= DBG_data) +        { +          char fn[30]; +            std::snprintf(fn, 30, "gl846_offset%03d.pnm", dev->frontend.get_offset(1)); +            sanei_genesys_write_pnm_file(fn, second_line.data(), session.params.depth, +                                         channels, pixels, lines); +        } + +      avg = dark_average(second_line.data(), pixels, 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)); +} + +void CommandSetGl846::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER(dbg); +  int pixels; +  int total_size; +  int i, j, channels; +  int max[3]; +  float gain[3],coeff; +  int val, code, lines; + +  DBG(DBG_proc, "%s: dpi = %d\n", __func__, dpi); + +    // no gain nor offset for AKM AFE +    uint8_t reg04 = dev->interface->read_register(REG_0x04); +    if ((reg04 & REG_0x04_FESET) == 0x02) { +      return; +    } + +  /* coarse gain calibration is always done in color mode */ +  channels = 3; + +  /* follow CKSEL */ +  if(dev->settings.xres<sensor.optical_res) +    { +        coeff = 0.9f; +    } +  else +    { +      coeff=1.0; +    } +  lines=10; +    pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res; + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = sensor.optical_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    try { +        init_regs_for_scan_session(dev, sensor, ®s, session); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); +        throw; +    } + +    sanei_genesys_set_motor_power(regs, false); + +    dev->interface->write_registers(regs); + +    total_size = pixels * channels * (16 / session.params.depth) * lines; + +  std::vector<uint8_t> line(total_size); + +    set_fe(dev, sensor, AFE_SET); +    begin_scan(dev, sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("coarse_gain_calibration"); +        scanner_stop_action(*dev); +        move_back_home(dev, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl846_gain.pnm", line.data(), session.params.depth, +                                     channels, pixels, lines); +    } + +  /* average value on each channel */ +  for (j = 0; j < channels; j++) +    { +      max[j] = 0; +      for (i = pixels/4; i < (pixels*3/4); i++) +        { +          if (dev->model->is_cis) +            val = line[i + j * pixels]; +          else +            val = line[i * channels + j]; + +          max[j] += val; +        } +      max[j] = max[j] / (pixels/2); + +        gain[j] = (static_cast<float>(sensor.gain_white_ref) * coeff) / max[j]; + +      /* turn logical gain value into gain code, checking for overflow */ +        code = static_cast<int>(283 - 208 / gain[j]); +      if (code > 255) +        code = 255; +      else if (code < 0) +        code = 0; +      dev->frontend.set_gain(j, code); + +      DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], gain[j], +          dev->frontend.get_gain(j)); +    } + +    if (dev->model->is_cis) { +        uint8_t gain0 = dev->frontend.get_gain(0); +        if (gain0 > dev->frontend.get_gain(1)) { +            gain0 = dev->frontend.get_gain(1); +        } +        if (gain0 > dev->frontend.get_gain(2)) { +            gain0 = dev->frontend.get_gain(2); +        } +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); +    } + +    scanner_stop_action(*dev); + +    move_back_home(dev, true); +} + +bool CommandSetGl846::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    (void) dev; +    return false; +} + +void CommandSetGl846::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* regs, int* channels, +                                           int* total_size) const +{ +    (void) dev; +    (void) sensor; +    (void) regs; +    (void) channels; +    (void) total_size; +    throw SaneException("not implemented"); +} + +void CommandSetGl846::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    sanei_genesys_send_gamma_table(dev, sensor); +} + +void CommandSetGl846::wait_for_motor_stop(Genesys_Device* dev) const +{ +    (void) dev; +} + +void CommandSetGl846::load_document(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl846::detect_document_end(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl846::eject_document(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl846::move_to_ta(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +std::unique_ptr<CommandSet> create_gl846_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl846{}); +} + +} // namespace gl846 +} // namespace genesys diff --git a/backend/genesys/gl846.h b/backend/genesys/gl846.h new file mode 100644 index 0000000..258015a --- /dev/null +++ b/backend/genesys/gl846.h @@ -0,0 +1,218 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2012-2013 Stéphane Voltz <stef.dev@free.fr> + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#include "genesys.h" +#include "command_set.h" + +#ifndef BACKEND_GENESYS_GL846_H +#define BACKEND_GENESYS_GL846_H + +namespace genesys { +namespace gl846 { + +typedef struct +{ +    GpioId gpio_id; +  uint8_t r6b; +  uint8_t r6c; +  uint8_t r6d; +  uint8_t r6e; +  uint8_t r6f; +  uint8_t ra6; +  uint8_t ra7; +  uint8_t ra8; +  uint8_t ra9; +} Gpio_Profile; + +static Gpio_Profile gpios[]={ +    { GpioId::IMG101, 0x72, 0x1f, 0xa4, 0x13, 0xa7, 0x11, 0xff, 0x19, 0x05}, +    { GpioId::PLUSTEK_OPTICBOOK_3800, 0x30, 0x01, 0x80, 0x2d, 0x80, 0x0c, 0x8f, 0x08, 0x04}, +    { GpioId::UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +typedef struct +{ +  const char *model; +  uint8_t dramsel; +  /* shading data address */ +  uint8_t rd0; +  uint8_t rd1; +  uint8_t rd2; +  /* scanned data address */ +  uint8_t rx[24]; +} Memory_layout; + +static Memory_layout layouts[]={ +	/* Image formula 101 */ +	{ +          "canon-image-formula-101", +          0x8b, +          0x0a, 0x1b, 0x00, +          {                         /* RED ODD START / RED ODD END */ +            0x00, 0xb0, 0x05, 0xe7, /* [0x00b0, 0x05e7] 1336*4000w */ +                                    /* RED EVEN START / RED EVEN END */ +            0x05, 0xe8, 0x0b, 0x1f, /* [0x05e8, 0x0b1f] */ +                                    /* GREEN ODD START / GREEN ODD END */ +            0x0b, 0x20, 0x10, 0x57, /* [0x0b20, 0x1057] */ +                                    /* GREEN EVEN START / GREEN EVEN END */ +            0x10, 0x58, 0x15, 0x8f, /* [0x1058, 0x158f] */ +                                    /* BLUE ODD START / BLUE ODD END */ +            0x15, 0x90, 0x1a, 0xc7, /* [0x1590,0x1ac7] */ +                                    /* BLUE EVEN START / BLUE EVEN END */ +            0x1a, 0xc8, 0x1f, 0xff  /* [0x1ac8,0x1fff] */ +          } +	}, +        /* OpticBook 3800 */ +	{ +          "plustek-opticbook-3800", +          0x2a, +          0x0a, 0x0a, 0x0a, +          { /* RED ODD START / RED ODD END */ +            0x00, 0x68, 0x03, 0x00, +            /* RED EVEN START / RED EVEN END */ +            0x03, 0x01, 0x05, 0x99, +            /* GREEN ODD START / GREEN ODD END */ +            0x05, 0x9a, 0x08, 0x32, +            /* GREEN EVEN START / GREEN EVEN END */ +            0x08, 0x33, 0x0a, 0xcb, +            /* BLUE ODD START / BLUE ODD END */ +            0x0a, 0xcc, 0x0d, 0x64, +            /* BLUE EVEN START / BLUE EVEN END */ +            0x0d, 0x65, 0x0f, 0xfd +          } +	}, +        /* list terminating entry */ +        { nullptr, 0, 0, 0, 0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} } +}; + +class CommandSetGl846 : public CommandSet +{ +public: +    ~CommandSetGl846() override = default; + +    bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override; + +    void init(Genesys_Device* dev) const override; + +    void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              Genesys_Register_Set* regs, int* channels, +                              int* total_size) const override; + +    void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs) const override; + +    void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set& regs) const override; + +    void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set* reg, +                                    const ScanSession& session) const override; + +    void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override; +    void set_powersaving(Genesys_Device* dev, int delay) const override; +    void save_power(Genesys_Device* dev, bool enable) const override; + +    void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                    Genesys_Register_Set* regs, bool start_motor) const override; + +    void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override; + +    void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void search_start_position(Genesys_Device* dev) const override; + +    void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set& regs) const override; + +    void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set& regs, int dpi) const override; + +    SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   Genesys_Register_Set& regs) const override; + +    void wait_for_motor_stop(Genesys_Device* dev) const override; + +    void move_back_home(Genesys_Device* dev, bool wait_until_home) const override; + +    void update_hardware_sensors(struct Genesys_Scanner* s) const override; + +    bool needs_update_home_sensor_gpio() const override { return true; } + +    void update_home_sensor_gpio(Genesys_Device& dev) const override; + +    void load_document(Genesys_Device* dev) const override; + +    void detect_document_end(Genesys_Device* dev) const override; + +    void eject_document(Genesys_Device* dev) const override; + +    void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                      bool forward, bool black) const override; + +    void move_to_ta(Genesys_Device* dev) const override; + +    void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data, +                           int size) const override; + +    ScanSession calculate_scan_session(const Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       const Genesys_Settings& settings) const override; + +    void asic_boot(Genesys_Device* dev, bool cold) const override; +}; + +enum SlopeTable +{ +    SCAN_TABLE = 0, // table 1 at 0x4000 +    BACKTRACK_TABLE = 1, // table 2 at 0x4800 +    STOP_TABLE = 2, // table 3 at 0x5000 +    FAST_TABLE = 3, // table 4 at 0x5800 +    HOME_TABLE = 4, // table 5 at 0x6000 +}; + +} // namespace gl846 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL846_H diff --git a/backend/genesys/gl846_registers.h b/backend/genesys/gl846_registers.h new file mode 100644 index 0000000..39b3029 --- /dev/null +++ b/backend/genesys/gl846_registers.h @@ -0,0 +1,351 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL846_REGISTERS_H +#define BACKEND_GENESYS_GL846_REGISTERS_H + +#include <cstdint> + +namespace genesys { +namespace gl846 { + +using RegAddr = std::uint16_t; +using RegMask = std::uint8_t; +using RegShift = unsigned; + +static constexpr RegAddr REG_0x01 = 0x01; +static constexpr RegMask REG_0x01_CISSET = 0x80; +static constexpr RegMask REG_0x01_DOGENB = 0x40; +static constexpr RegMask REG_0x01_DVDSET = 0x20; +static constexpr RegMask REG_0x01_STAGGER = 0x10; +static constexpr RegMask REG_0x01_COMPENB = 0x08; +static constexpr RegMask REG_0x01_TRUEGRAY = 0x04; +static constexpr RegMask REG_0x01_SHDAREA = 0x02; +static constexpr RegMask REG_0x01_SCAN = 0x01; + +static constexpr RegAddr REG_0x02 = 0x02; +static constexpr RegMask REG_0x02_NOTHOME = 0x80; +static constexpr RegMask REG_0x02_ACDCDIS = 0x40; +static constexpr RegMask REG_0x02_AGOHOME = 0x20; +static constexpr RegMask REG_0x02_MTRPWR = 0x10; +static constexpr RegMask REG_0x02_FASTFED = 0x08; +static constexpr RegMask REG_0x02_MTRREV = 0x04; +static constexpr RegMask REG_0x02_HOMENEG = 0x02; +static constexpr RegMask REG_0x02_LONGCURV = 0x01; + +static constexpr RegAddr REG_0x03 = 0x03; +static constexpr RegMask REG_0x03_LAMPDOG = 0x80; +static constexpr RegMask REG_0x03_AVEENB = 0x40; +static constexpr RegMask REG_0x03_XPASEL = 0x20; +static constexpr RegMask REG_0x03_LAMPPWR = 0x10; +static constexpr RegMask REG_0x03_LAMPTIM = 0x0f; + +static constexpr RegAddr REG_0x04 = 0x04; +static constexpr RegMask REG_0x04_LINEART = 0x80; +static constexpr RegMask REG_0x04_BITSET = 0x40; +static constexpr RegMask REG_0x04_AFEMOD = 0x30; +static constexpr RegMask REG_0x04_FILTER = 0x0c; +static constexpr RegMask REG_0x04_FESET = 0x03; + +static constexpr RegShift REG_0x04S_AFEMOD = 4; + +static constexpr RegAddr REG_0x05 = 0x05; +static constexpr RegMask REG_0x05_DPIHW = 0xc0; +static constexpr RegMask REG_0x05_DPIHW_600 = 0x00; +static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40; +static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80; +static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0; +static constexpr RegMask REG_0x05_MTLLAMP = 0x30; +static constexpr RegMask REG_0x05_GMMENB = 0x08; +static constexpr RegMask REG_0x05_MTLBASE = 0x03; + +static constexpr RegAddr REG_0x06 = 0x06; +static constexpr RegMask REG_0x06_SCANMOD = 0xe0; +static constexpr RegShift REG_0x06S_SCANMOD = 5; +static constexpr RegMask REG_0x06_PWRBIT = 0x10; +static constexpr RegMask REG_0x06_GAIN4 = 0x08; +static constexpr RegMask REG_0x06_OPTEST = 0x07; + +static constexpr RegMask REG_0x07_LAMPSIM = 0x80; + +static constexpr RegMask REG_0x08_DRAM2X = 0x80; +static constexpr RegMask REG_0x08_MPENB = 0x20; +static constexpr RegMask REG_0x08_CIS_LINE = 0x10; +static constexpr RegMask REG_0x08_IR1ENB = 0x08; +static constexpr RegMask REG_0x08_IR2ENB = 0x04; +static constexpr RegMask REG_0x08_ENB24M = 0x01; + +static constexpr RegMask REG_0x09_MCNTSET = 0xc0; +static constexpr RegMask REG_0x09_EVEN1ST = 0x20; +static constexpr RegMask REG_0x09_BLINE1ST = 0x10; +static constexpr RegMask REG_0x09_BACKSCAN = 0x08; +static constexpr RegMask REG_0x09_ENHANCE = 0x04; +static constexpr RegMask REG_0x09_SHORTTG = 0x02; +static constexpr RegMask REG_0x09_NWAIT = 0x01; + +static constexpr RegShift REG_0x09S_MCNTSET = 6; +static constexpr RegShift REG_0x09S_CLKSET = 4; + + +static constexpr RegAddr REG_0x0A_LPWMEN = 0x10; + +static constexpr RegAddr REG_0x0B = 0x0b; +static constexpr RegMask REG_0x0B_DRAMSEL = 0x07; +static constexpr RegMask REG_0x0B_ENBDRAM = 0x08; +static constexpr RegMask REG_0x0B_RFHDIS = 0x10; +static constexpr RegMask REG_0x0B_CLKSET = 0xe0; +static constexpr RegMask REG_0x0B_24MHZ = 0x00; +static constexpr RegMask REG_0x0B_30MHZ = 0x20; +static constexpr RegMask REG_0x0B_40MHZ = 0x40; +static constexpr RegMask REG_0x0B_48MHZ = 0x60; +static constexpr RegMask REG_0x0B_60MHZ = 0x80; + +static constexpr RegAddr REG_0x0C = 0x0c; +static constexpr RegMask REG_0x0C_CCDLMT = 0x0f; + +static constexpr RegAddr REG_0x0D = 0x0d; +static constexpr RegMask REG_0x0D_SCSYNC = 0x40; +static constexpr RegMask REG_0x0D_CLRERR = 0x20; +static constexpr RegMask REG_0x0D_FULLSTP = 0x10; +static constexpr RegMask REG_0x0D_SEND = 0x80; +static constexpr RegMask REG_0x0D_CLRMCNT = 0x04; +static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02; +static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01; + +static constexpr RegAddr REG_0x0F = 0x0f; + +static constexpr RegMask REG_0x16_CTRLHI = 0x80; +static constexpr RegMask REG_0x16_TOSHIBA = 0x40; +static constexpr RegMask REG_0x16_TGINV = 0x20; +static constexpr RegMask REG_0x16_CK1INV = 0x10; +static constexpr RegMask REG_0x16_CK2INV = 0x08; +static constexpr RegMask REG_0x16_CTRLINV = 0x04; +static constexpr RegMask REG_0x16_CKDIS = 0x02; +static constexpr RegMask REG_0x16_CTRLDIS = 0x01; + +static constexpr RegMask REG_0x17_TGMODE = 0xc0; +static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00; +static constexpr RegMask REG_0x17_TGMODE_REF = 0x40; +static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80; +static constexpr RegMask REG_0x17_TGW = 0x3f; +static constexpr RegAddr REG_0x17S_TGW = 0; + +static constexpr RegAddr REG_0x18 = 0x18; +static constexpr RegMask REG_0x18_CNSET = 0x80; +static constexpr RegMask REG_0x18_DCKSEL = 0x60; +static constexpr RegMask REG_0x18_CKTOGGLE = 0x10; +static constexpr RegMask REG_0x18_CKDELAY = 0x0c; +static constexpr RegMask REG_0x18_CKSEL = 0x03; + +static constexpr RegMask REG_0x1A_SW2SET = 0x80; +static constexpr RegMask REG_0x1A_SW1SET = 0x40; +static constexpr RegMask REG_0x1A_MANUAL3 = 0x02; +static constexpr RegMask REG_0x1A_MANUAL1 = 0x01; +static constexpr RegMask REG_0x1A_CK4INV = 0x08; +static constexpr RegMask REG_0x1A_CK3INV = 0x04; +static constexpr RegMask REG_0x1A_LINECLP = 0x02; + +static constexpr RegAddr REG_0x1C = 0x1c; +static constexpr RegMask REG_0x1C_TGTIME = 0x07; + +static constexpr RegMask REG_0x1D_CK4LOW = 0x80; +static constexpr RegMask REG_0x1D_CK3LOW = 0x40; +static constexpr RegMask REG_0x1D_CK1LOW = 0x20; +static constexpr RegMask REG_0x1D_TGSHLD = 0x1f; +static constexpr RegShift REG_0x1DS_TGSHLD = 0; + + +static constexpr RegMask REG_0x1E_WDTIME = 0xf0; +static constexpr RegShift REG_0x1ES_WDTIME = 4; +static constexpr RegMask REG_0x1E_LINESEL = 0x0f; +static constexpr RegShift REG_0x1ES_LINESEL = 0; + +static constexpr RegAddr REG_FEDCNT = 0x1f; + +static constexpr RegAddr REG_0x24 = 0x1c; +static constexpr RegAddr REG_0x40 = 0x40; +static constexpr RegMask REG_0x40_DOCSNR = 0x80; +static constexpr RegMask REG_0x40_ADFSNR = 0x40; +static constexpr RegMask REG_0x40_COVERSNR = 0x20; +static constexpr RegMask REG_0x40_CHKVER = 0x10; +static constexpr RegMask REG_0x40_DOCJAM = 0x08; +static constexpr RegMask REG_0x40_HISPDFLG = 0x04; +static constexpr RegMask REG_0x40_MOTMFLG = 0x02; +static constexpr RegMask REG_0x40_DATAENB = 0x01; + +static constexpr RegMask REG_0x41_PWRBIT = 0x80; +static constexpr RegMask REG_0x41_BUFEMPTY = 0x40; +static constexpr RegMask REG_0x41_FEEDFSH = 0x20; +static constexpr RegMask REG_0x41_SCANFSH = 0x10; +static constexpr RegMask REG_0x41_HOMESNR = 0x08; +static constexpr RegMask REG_0x41_LAMPSTS = 0x04; +static constexpr RegMask REG_0x41_FEBUSY = 0x02; +static constexpr RegMask REG_0x41_MOTORENB = 0x01; + +static constexpr RegMask REG_0x58_VSMP = 0xf8; +static constexpr RegShift REG_0x58S_VSMP = 3; +static constexpr RegMask REG_0x58_VSMPW = 0x07; +static constexpr RegAddr REG_0x58S_VSMPW = 0; + +static constexpr RegMask REG_0x59_BSMP = 0xf8; +static constexpr RegAddr REG_0x59S_BSMP = 3; +static constexpr RegMask REG_0x59_BSMPW = 0x07; +static constexpr RegShift REG_0x59S_BSMPW = 0; + +static constexpr RegMask REG_0x5A_ADCLKINV = 0x80; +static constexpr RegMask REG_0x5A_RLCSEL = 0x40; +static constexpr RegMask REG_0x5A_CDSREF = 0x30; +static constexpr RegShift REG_0x5AS_CDSREF = 4; +static constexpr RegMask REG_0x5A_RLC = 0x0f; +static constexpr RegShift REG_0x5AS_RLC = 0; + +static constexpr RegMask REG_0x5E_DECSEL = 0xe0; +static constexpr RegShift REG_0x5ES_DECSEL = 5; +static constexpr RegMask REG_0x5E_STOPTIM = 0x1f; +static constexpr RegShift REG_0x5ES_STOPTIM = 0; + +static constexpr RegAddr REG_0x60 = 0x60; +static constexpr RegMask REG_0x60_Z1MOD = 0x1f; +static constexpr RegAddr REG_0x61 = 0x61; +static constexpr RegMask REG_0x61_Z1MOD = 0xff; +static constexpr RegAddr REG_0x62 = 0x62; +static constexpr RegMask REG_0x62_Z1MOD = 0xff; + +static constexpr RegAddr REG_0x63 = 0x63; +static constexpr RegMask REG_0x63_Z2MOD = 0x1f; +static constexpr RegAddr REG_0x64 = 0x64; +static constexpr RegMask REG_0x64_Z2MOD = 0xff; +static constexpr RegAddr REG_0x65 = 0x65; +static constexpr RegMask REG_0x65_Z2MOD = 0xff; + +static constexpr RegShift REG_0x60S_STEPSEL = 5; +static constexpr RegMask REG_0x60_STEPSEL = 0xe0; +static constexpr RegMask REG_0x60_FULLSTEP = 0x00; +static constexpr RegMask REG_0x60_HALFSTEP = 0x20; +static constexpr RegMask REG_0x60_EIGHTHSTEP = 0x60; +static constexpr RegMask REG_0x60_16THSTEP = 0x80; + +static constexpr RegShift REG_0x63S_FSTPSEL = 5; +static constexpr RegMask REG_0x63_FSTPSEL = 0xe0; +static constexpr RegMask REG_0x63_FULLSTEP = 0x00; +static constexpr RegMask REG_0x63_HALFSTEP = 0x20; +static constexpr RegMask REG_0x63_EIGHTHSTEP = 0x60; +static constexpr RegMask REG_0x63_16THSTEP = 0x80; + +static constexpr RegAddr REG_0x67 = 0x67; +static constexpr RegMask REG_0x67_MTRPWM = 0x80; + +static constexpr RegAddr REG_0x68 = 0x68; +static constexpr RegMask REG_0x68_FASTPWM = 0x80; + +static constexpr RegAddr REG_0x6B = 0x6b; +static constexpr RegMask REG_0x6B_MULTFILM = 0x80; +static constexpr RegMask REG_0x6B_GPOM13 = 0x40; +static constexpr RegMask REG_0x6B_GPOM12 = 0x20; +static constexpr RegMask REG_0x6B_GPOM11 = 0x10; +static constexpr RegMask REG_0x6B_GPO18 = 0x02; +static constexpr RegMask REG_0x6B_GPO17 = 0x01; + +static constexpr RegAddr REG_0x6C = 0x6c; +static constexpr RegMask REG_0x6C_GPIO16 = 0x80; +static constexpr RegMask REG_0x6C_GPIO15 = 0x40; +static constexpr RegMask REG_0x6C_GPIO14 = 0x20; +static constexpr RegMask REG_0x6C_GPIO13 = 0x10; +static constexpr RegMask REG_0x6C_GPIO12 = 0x08; +static constexpr RegMask REG_0x6C_GPIO11 = 0x04; +static constexpr RegMask REG_0x6C_GPIO10 = 0x02; +static constexpr RegMask REG_0x6C_GPIO9 = 0x01; +static constexpr RegMask REG_0x6C_GPIOH = 0xff; +static constexpr RegMask REG_0x6C_GPIOL = 0xff; + +static constexpr RegAddr REG_0x6D = 0x6d; +static constexpr RegAddr REG_0x6E = 0x6e; +static constexpr RegAddr REG_0x6F = 0x6f; +static constexpr RegAddr REG_0x7E = 0x7e; + +static constexpr RegMask REG_0x87_ACYCNRLC = 0x10; +static constexpr RegMask REG_0x87_ENOFFSET = 0x08; +static constexpr RegMask REG_0x87_LEDADD = 0x04; +static constexpr RegMask REG_0x87_CK4ADC = 0x02; +static constexpr RegMask REG_0x87_AUTOCONF = 0x01; + +static constexpr RegAddr REG_0x9E = 0x9e; +static constexpr RegAddr REG_0x9F = 0x9f; + +static constexpr RegAddr REG_0xA6 = 0xa6; +static constexpr RegAddr REG_0xA7 = 0xa7; +static constexpr RegAddr REG_0xA8 = 0xa8; +static constexpr RegAddr REG_0xA9 = 0xa9; +static constexpr RegAddr REG_0xAB = 0xab; + +static constexpr RegAddr REG_EXPR = 0x10; +static constexpr RegAddr REG_EXPG = 0x12; +static constexpr RegAddr REG_EXPB = 0x14; +static constexpr RegAddr REG_EXPDMY = 0x19; +static constexpr RegAddr REG_STEPNO = 0x21; +static constexpr RegAddr REG_FWDSTEP = 0x22; +static constexpr RegAddr REG_BWDSTEP = 0x23; +static constexpr RegAddr REG_FASTNO = 0x24; +static constexpr RegAddr REG_DPISET = 0x2c; +static constexpr RegAddr REG_STRPIXEL = 0x30; +static constexpr RegAddr REG_ENDPIXEL = 0x32; +static constexpr RegAddr REG_LINCNT = 0x25; +static constexpr RegAddr REG_MAXWD = 0x35; +static constexpr RegAddr REG_LPERIOD = 0x38; +static constexpr RegAddr REG_FEEDL = 0x3d; +static constexpr RegAddr REG_FMOVDEC = 0x5f; +static constexpr RegAddr REG_FSHDEC = 0x69; +static constexpr RegAddr REG_FMOVNO = 0x6a; +static constexpr RegAddr REG_CK1MAP = 0x74; +static constexpr RegAddr REG_CK3MAP = 0x77; +static constexpr RegAddr REG_CK4MAP = 0x7a; + +static constexpr RegAddr REG_0xF8 = 0xf8; +static constexpr RegMask REG_0xF8_MAXSEL = 0xf0; +static constexpr RegShift REG_0xF8_SMAXSEL = 4; +static constexpr RegMask REG_0xF8_MINSEL = 0x0f; + +} // namespace gl846 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL846_REGISTERS_H diff --git a/backend/genesys/gl847.cpp b/backend/genesys/gl847.cpp new file mode 100644 index 0000000..cb0b527 --- /dev/null +++ b/backend/genesys/gl847.cpp @@ -0,0 +1,2140 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "gl847.h" +#include "gl847_registers.h" +#include "test_settings.h" + +#include <vector> + +namespace genesys { +namespace gl847 { + +/** + * compute the step multiplier used + */ +static int +gl847_get_step_multiplier (Genesys_Register_Set * regs) +{ +    GenesysRegister *r = sanei_genesys_get_address(regs, 0x9d); +    int value = 1; +    if (r != nullptr) +    { +      value = (r->value & 0x0f)>>1; +      value = 1 << value; +    } +  DBG (DBG_io, "%s: step multiplier is %d\n", __func__, value); +  return value; +} + +/** @brief sensor specific settings +*/ +static void gl847_setup_sensor(Genesys_Device * dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set* regs) +{ +    DBG_HELPER(dbg); + +    for (const auto& reg : sensor.custom_regs) { +        regs->set8(reg.address, reg.value); +    } + +    regs->set16(REG_EXPR, sensor.exposure.red); +    regs->set16(REG_EXPG, sensor.exposure.green); +    regs->set16(REG_EXPB, sensor.exposure.blue); + +    dev->segment_order = sensor.segment_order; +} + + +/** @brief set all registers to default values . + * This function is called only once at the beginning and + * fills register startup values for registers reused across scans. + * Those that are rarely modified or not modified are written + * individually. + * @param dev device structure holding register set to initialize + */ +static void +gl847_init_registers (Genesys_Device * dev) +{ +    DBG_HELPER(dbg); +  int lide700=0; +  uint8_t val; + +  /* 700F class needs some different initial settings */ +    if (dev->model->model_id == ModelId::CANON_LIDE_700F) { +       lide700 = 1; +    } + +    dev->reg.clear(); + +    dev->reg.init_reg(0x01, 0x82); +    dev->reg.init_reg(0x02, 0x18); +    dev->reg.init_reg(0x03, 0x50); +    dev->reg.init_reg(0x04, 0x12); +    dev->reg.init_reg(0x05, 0x80); +    dev->reg.init_reg(0x06, 0x50); // FASTMODE + POWERBIT +    dev->reg.init_reg(0x08, 0x10); +    dev->reg.init_reg(0x09, 0x01); +    dev->reg.init_reg(0x0a, 0x00); +    dev->reg.init_reg(0x0b, 0x01); +    dev->reg.init_reg(0x0c, 0x02); + +    // LED exposures +    dev->reg.init_reg(0x10, 0x00); +    dev->reg.init_reg(0x11, 0x00); +    dev->reg.init_reg(0x12, 0x00); +    dev->reg.init_reg(0x13, 0x00); +    dev->reg.init_reg(0x14, 0x00); +    dev->reg.init_reg(0x15, 0x00); + +    dev->reg.init_reg(0x16, 0x10); // SENSOR_DEF +    dev->reg.init_reg(0x17, 0x08); // SENSOR_DEF +    dev->reg.init_reg(0x18, 0x00); // SENSOR_DEF + +    // EXPDMY +    dev->reg.init_reg(0x19, 0x50); // SENSOR_DEF + +    dev->reg.init_reg(0x1a, 0x34); // SENSOR_DEF +    dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x1c, 0x02); // SENSOR_DEF +    dev->reg.init_reg(0x1d, 0x04); // SENSOR_DEF +    dev->reg.init_reg(0x1e, 0x10); +    dev->reg.init_reg(0x1f, 0x04); +    dev->reg.init_reg(0x20, 0x02); +    dev->reg.init_reg(0x21, 0x10); +    dev->reg.init_reg(0x22, 0x7f); +    dev->reg.init_reg(0x23, 0x7f); +    dev->reg.init_reg(0x24, 0x10); +    dev->reg.init_reg(0x25, 0x00); +    dev->reg.init_reg(0x26, 0x00); +    dev->reg.init_reg(0x27, 0x00); +    dev->reg.init_reg(0x2c, 0x09); +    dev->reg.init_reg(0x2d, 0x60); +    dev->reg.init_reg(0x2e, 0x80); +    dev->reg.init_reg(0x2f, 0x80); +    dev->reg.init_reg(0x30, 0x00); +    dev->reg.init_reg(0x31, 0x10); +    dev->reg.init_reg(0x32, 0x15); +    dev->reg.init_reg(0x33, 0x0e); +    dev->reg.init_reg(0x34, 0x40); +    dev->reg.init_reg(0x35, 0x00); +    dev->reg.init_reg(0x36, 0x2a); +    dev->reg.init_reg(0x37, 0x30); +    dev->reg.init_reg(0x38, 0x2a); +    dev->reg.init_reg(0x39, 0xf8); +    dev->reg.init_reg(0x3d, 0x00); +    dev->reg.init_reg(0x3e, 0x00); +    dev->reg.init_reg(0x3f, 0x00); +    dev->reg.init_reg(0x52, 0x03); // SENSOR_DEF +    dev->reg.init_reg(0x53, 0x07); // SENSOR_DEF +    dev->reg.init_reg(0x54, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x55, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x56, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x57, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x58, 0x2a); // SENSOR_DEF +    dev->reg.init_reg(0x59, 0xe1); // SENSOR_DEF +    dev->reg.init_reg(0x5a, 0x55); // SENSOR_DEF +    dev->reg.init_reg(0x5e, 0x41); +    dev->reg.init_reg(0x5f, 0x40); +    dev->reg.init_reg(0x60, 0x00); +    dev->reg.init_reg(0x61, 0x21); +    dev->reg.init_reg(0x62, 0x40); +    dev->reg.init_reg(0x63, 0x00); +    dev->reg.init_reg(0x64, 0x21); +    dev->reg.init_reg(0x65, 0x40); +    dev->reg.init_reg(0x67, 0x80); +    dev->reg.init_reg(0x68, 0x80); +    dev->reg.init_reg(0x69, 0x20); +    dev->reg.init_reg(0x6a, 0x20); + +    // CK1MAP +    dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF + +    // CK3MAP +    dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF + +    // CK4MAP +    dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF +    dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF + +    dev->reg.init_reg(0x7d, 0x00); + +    // NOTE: autoconf is a non working option +    dev->reg.init_reg(0x87, 0x02); +    dev->reg.init_reg(0x9d, 0x06); +    dev->reg.init_reg(0xa2, 0x0f); +    dev->reg.init_reg(0xbd, 0x18); +    dev->reg.init_reg(0xfe, 0x08); + +    // gamma[0] and gamma[256] values +    dev->reg.init_reg(0xbe, 0x00); +    dev->reg.init_reg(0xc5, 0x00); +    dev->reg.init_reg(0xc6, 0x00); +    dev->reg.init_reg(0xc7, 0x00); +    dev->reg.init_reg(0xc8, 0x00); +    dev->reg.init_reg(0xc9, 0x00); +    dev->reg.init_reg(0xca, 0x00); + +  /* LiDE 700 fixups */ +    if (lide700) { +        dev->reg.init_reg(0x5f, 0x04); +        dev->reg.init_reg(0x7d, 0x80); + +      /* we write to these registers only once */ +      val=0; +        dev->interface->write_register(REG_0x7E, val); +        dev->interface->write_register(REG_0x9E, val); +        dev->interface->write_register(REG_0x9F, val); +        dev->interface->write_register(REG_0xAB, val); +    } + +    const auto& sensor = sanei_genesys_find_sensor_any(dev); +    sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res); + +  /* initalize calibration reg */ +  dev->calib_reg = dev->reg; +} + +/**@brief send slope table for motor movement + * Send slope_table in machine byte order + * @param dev device to send slope table + * @param table_nr index of the slope table in ASIC memory + * Must be in the [0-4] range. + * @param slope_table pointer to 16 bit values array of the slope table + * @param steps number of elements in the slope table + */ +static void gl847_send_slope_table(Genesys_Device* dev, int table_nr, +                                   const std::vector<uint16_t>& slope_table, +                                   int steps) +{ +    DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps); +  int i; +  char msg[10000]; + +  /* sanity check */ +  if(table_nr<0 || table_nr>4) +    { +        throw SaneException("invalid table number %d", table_nr); +    } + +  std::vector<uint8_t> table(steps * 2); +  for (i = 0; i < steps; i++) +    { +      table[i * 2] = slope_table[i] & 0xff; +      table[i * 2 + 1] = slope_table[i] >> 8; +    } + +  if (DBG_LEVEL >= DBG_io) +    { +        std::sprintf(msg, "write slope %d (%d)=", table_nr, steps); +      for (i = 0; i < steps; i++) +	{ +            std::sprintf(msg + std::strlen(msg), "%d", slope_table[i]); +	} +      DBG (DBG_io, "%s: %s\n", __func__, msg); +    } + +    if (dev->interface->is_mock()) { +        dev->interface->record_slope_table(table_nr, slope_table); +    } +    // slope table addresses are fixed +    dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, steps * 2, table.data()); +} + +/** + * Set register values of Analog Device type frontend + * */ +static void gl847_set_ad_fe(Genesys_Device* dev, uint8_t set) +{ +    DBG_HELPER(dbg); +  int i; + +    // wait for FE to be ready +    auto status = scanner_read_status(*dev); +    while (status.is_front_end_busy) { +        dev->interface->sleep_ms(10); +        status = scanner_read_status(*dev); +    }; + +  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 DAC +    dev->interface->write_fe_register(0x00, 0x80); + +    // write them to analog frontend +    dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00)); + +    dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01)); + +    for (i = 0; i < 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)); +    } +} + +// Set values of analog frontend +void CommandSetGl847::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const +{ +    DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" : +                               set == AFE_SET ? "set" : +                               set == AFE_POWER_SAVE ? "powersave" : "huh?"); + +    (void) sensor; + +    uint8_t val = dev->interface->read_register(REG_0x04); +    uint8_t frontend_type = val & REG_0x04_FESET; + +    // route to AD devices +    if (frontend_type == 0x02) { +        gl847_set_ad_fe(dev, set); +        return; +    } + +    throw SaneException("unsupported frontend type %d", frontend_type); +} + + +// @brief set up motor related register for scan +static void gl847_init_motor_regs_scan(Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       Genesys_Register_Set* reg, +                                       const Motor_Profile& motor_profile, +                                       unsigned int scan_exposure_time, +                                       unsigned scan_yres, +                                       unsigned int scan_lines, +                                       unsigned int scan_dummy, +                                       unsigned int feed_steps, +                                       MotorFlag flags) +{ +    DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, can_yres=%d, step_type=%d, scan_lines=%d, " +                         "scan_dummy=%d, feed_steps=%d, flags=%x", +                    scan_exposure_time, scan_yres, static_cast<unsigned>(motor_profile.step_type), +                    scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags)); +  int use_fast_fed; +  unsigned int fast_dpi; +  unsigned int feedl, dist; +  GenesysRegister *r; +  uint32_t z1, z2; +  unsigned int min_restep = 0x20; +    uint8_t val; +  unsigned int ccdlmt,tgtime; + +    unsigned step_multiplier = gl847_get_step_multiplier (reg); + +  use_fast_fed=0; +  /* no fast fed since feed works well */ +    if (dev->settings.yres==4444 && feed_steps > 100 && (!has_flag(flags, MotorFlag::FEED))) +    { +      use_fast_fed=1; +    } +  DBG(DBG_io, "%s: use_fast_fed=%d\n", __func__, use_fast_fed); + +    reg->set24(REG_LINCNT, scan_lines); +  DBG(DBG_io, "%s: lincnt=%d\n", __func__, scan_lines); + +  /* compute register 02 value */ +    r = sanei_genesys_get_address(reg, REG_0x02); +  r->value = 0x00; +  sanei_genesys_set_motor_power(*reg, true); + +    if (use_fast_fed) { +        r->value |= REG_0x02_FASTFED; +    } else { +        r->value &= ~REG_0x02_FASTFED; +    } + +    if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) { +        r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME; +    } + +  if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) +      ||(scan_yres>=sensor.optical_res)) +    { +        r->value |= REG_0x02_ACDCDIS; +    } + +    if (has_flag(flags, MotorFlag::REVERSE)) { +        r->value |= REG_0x02_MTRREV; +    } else { +        r->value &= ~REG_0x02_MTRREV; +    } + +  /* scan and backtracking slope table */ +    auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres, +                                                scan_exposure_time, dev->motor.base_ydpi, +                                                step_multiplier, motor_profile); +    gl847_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count); +    gl847_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count); + +  /* fast table */ +  fast_dpi=sanei_genesys_get_lowest_ydpi(dev); +    StepType fast_step_type = motor_profile.step_type; +    if (static_cast<unsigned>(motor_profile.step_type) >= static_cast<unsigned>(StepType::QUARTER)) { +        fast_step_type = StepType::QUARTER; +    } + +    Motor_Profile fast_motor_profile = motor_profile; +    fast_motor_profile.step_type = fast_step_type; + +    auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_dpi, +                                                scan_exposure_time, dev->motor.base_ydpi, +                                                step_multiplier, fast_motor_profile); + +    gl847_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count); +    gl847_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count); +    gl847_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count); + +  /* correct move distance by acceleration and deceleration amounts */ +  feedl=feed_steps; +  if (use_fast_fed) +    { +        feedl <<= static_cast<unsigned>(fast_step_type); +        dist = (scan_table.steps_count + 2 * fast_table.steps_count); +        /* TODO read and decode REG_0xAB */ +        r = sanei_genesys_get_address (reg, 0x5e); +        dist += (r->value & 31); +        /* FEDCNT */ +        r = sanei_genesys_get_address (reg, REG_FEDCNT); +        dist += r->value; +    } +  else +    { +        feedl <<= static_cast<unsigned>(motor_profile.step_type); +        dist = scan_table.steps_count; +        if (has_flag(flags, MotorFlag::FEED)) { +            dist *= 2; +        } +    } +  DBG(DBG_io2, "%s: acceleration distance=%d\n", __func__, dist); + +  /* check for overflow */ +    if (dist < feedl) { +        feedl -= dist; +    } else { +        feedl = 0; +    } + +    reg->set24(REG_FEEDL, feedl); +  DBG(DBG_io ,"%s: feedl=%d\n", __func__, feedl); + +    r = sanei_genesys_get_address(reg, REG_0x0C); +    ccdlmt = (r->value & REG_0x0C_CCDLMT) + 1; + +    r = sanei_genesys_get_address(reg, REG_0x1C); +    tgtime = 1<<(r->value & REG_0x1C_TGTIME); + +    // hi res motor speed GPIO +    uint8_t effective = dev->interface->read_register(REG_0x6C); + +    // if quarter step, bipolar Vref2 + +    if (motor_profile.step_type == StepType::QUARTER) { +        val = effective & ~REG_0x6C_GPIO13; +    } else if (static_cast<unsigned>(motor_profile.step_type) > static_cast<unsigned>(StepType::QUARTER)) { +        val = effective | REG_0x6C_GPIO13; +    } else { +        val = effective; +    } +    dev->interface->write_register(REG_0x6C, val); + +    // effective scan +    effective = dev->interface->read_register(REG_0x6C); +    val = effective | REG_0x6C_GPIO10; +    dev->interface->write_register(REG_0x6C, val); + +    min_restep = scan_table.steps_count / (2 * step_multiplier) - 1; +    if (min_restep < 1) { +        min_restep = 1; +    } +    r = sanei_genesys_get_address(reg, REG_FWDSTEP); +  r->value = min_restep; +    r = sanei_genesys_get_address(reg, REG_BWDSTEP); +  r->value = min_restep; + +    sanei_genesys_calculate_zmod(use_fast_fed, +			         scan_exposure_time*ccdlmt*tgtime, +                                 scan_table.table, +                                 scan_table.steps_count, +				 feedl, +                                 min_restep * step_multiplier, +                                 &z1, +                                 &z2); + +  DBG(DBG_info, "%s: z1 = %d\n", __func__, z1); +    reg->set24(REG_0x60, z1 | (static_cast<unsigned>(motor_profile.step_type) << (16+REG_0x60S_STEPSEL))); + +  DBG(DBG_info, "%s: z2 = %d\n", __func__, z2); +    reg->set24(REG_0x63, z2 | (static_cast<unsigned>(motor_profile.step_type) << (16+REG_0x63S_FSTPSEL))); + +  r = sanei_genesys_get_address (reg, 0x1e); +  r->value &= 0xf0;		/* 0 dummy lines */ +  r->value |= scan_dummy;	/* dummy lines */ + +    r = sanei_genesys_get_address(reg, REG_0x67); +    r->value = REG_0x67_MTRPWM; + +    r = sanei_genesys_get_address(reg, REG_0x68); +    r->value = REG_0x68_FASTPWM; + +    reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FSHDEC, scan_table.steps_count / step_multiplier); +    reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier); +    reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier); +} + + +/** @brief set up registers related to sensor + * Set up the following registers +   0x01 +   0x03 +   0x10-0x015     R/G/B exposures +   0x19           EXPDMY +   0x2e           BWHI +   0x2f           BWLO +   0x04 +   0x87 +   0x05 +   0x2c,0x2d      DPISET +   0x30,0x31      STRPIXEL +   0x32,0x33      ENDPIXEL +   0x35,0x36,0x37 MAXWD [25:2] (>>2) +   0x38,0x39      LPERIOD +   0x34           DUMMY + */ +static void gl847_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set* reg, unsigned int exposure_time, +                                         const ScanSession& session) +{ +    DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time); +    unsigned dpihw; +  GenesysRegister *r; + +    // resolution is divided according to ccd_pixels_per_system_pixel() +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); +    DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel); + +    // to manage high resolution device while keeping good low resolution scanning speed, we make +    // hardware dpi vary +    dpihw = sensor.get_register_hwdpi(session.params.xres * ccd_pixels_per_system_pixel); +  DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw); + +    gl847_setup_sensor(dev, sensor, reg); + +    dev->cmd_set->set_fe(dev, sensor, AFE_SET); + +  /* enable shading */ +    regs_set_optical_off(dev->model->asic_type, *reg); +    r = sanei_genesys_get_address(reg, REG_0x01); +    r->value |= REG_0x01_SHDAREA; + +    if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) || +        (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) +    { +        r->value &= ~REG_0x01_DVDSET; +    } +  else +    { +        r->value |= REG_0x01_DVDSET; +    } + +  r = sanei_genesys_get_address (reg, REG_0x03); +  r->value &= ~REG_0x03_AVEENB; + +    sanei_genesys_set_lamp_power(dev, sensor, *reg, +                                 !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP)); + +  /* BW threshold */ +    r = sanei_genesys_get_address (reg, 0x2e); +  r->value = dev->settings.threshold; +    r = sanei_genesys_get_address (reg, 0x2f); +  r->value = dev->settings.threshold; + +  /* monochrome / color scan */ +    r = sanei_genesys_get_address (reg, REG_0x04); +    switch (session.params.depth) { +    case 8: +            r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET); +      break; +    case 16: +            r->value &= ~REG_0x04_LINEART; +            r->value |= REG_0x04_BITSET; +      break; +    } + +    r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD); +  if (session.params.channels == 1) +    { +      switch (session.params.color_filter) +	{ + +           case ColorFilter::RED: +               r->value |= 0x14; +               break; +           case ColorFilter::BLUE: +               r->value |= 0x1c; +               break; +           case ColorFilter::GREEN: +               r->value |= 0x18; +               break; +           default: +               break; // should not happen +	} +    } else { +        r->value |= 0x10; // mono +    } + +    sanei_genesys_set_dpihw(*reg, sensor, dpihw); + +    if (should_enable_gamma(session, sensor)) { +        reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB; +    } else { +        reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB; +    } + +  /* CIS scanners can do true gray by setting LEDADD */ +  /* we set up LEDADD only when asked */ +    if (dev->model->is_cis) { +        r = sanei_genesys_get_address (reg, 0x87); +        r->value &= ~REG_0x87_LEDADD; +        if (session.enable_ledadd) { +            r->value |= REG_0x87_LEDADD; +        } +      /* RGB weighting +        r = sanei_genesys_get_address (reg, 0x01); +        r->value &= ~REG_0x01_TRUEGRAY; +        if (session.enable_ledadd) { +            r->value |= REG_0x01_TRUEGRAY; +        } +        */ +    } + +    unsigned dpiset = session.params.xres * ccd_pixels_per_system_pixel; +    reg->set16(REG_DPISET, dpiset); +    DBG (DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset); + +    reg->set16(REG_STRPIXEL, session.pixel_startx); +    reg->set16(REG_ENDPIXEL, session.pixel_endx); + +    build_image_pipeline(dev, session); + +  /* MAXWD is expressed in 4 words unit */ +    // BUG: we shouldn't multiply by channels here +    reg->set24(REG_MAXWD, (session.output_line_bytes_raw * session.params.channels >> 2)); + +    reg->set16(REG_LPERIOD, exposure_time); +  DBG(DBG_io2, "%s: exposure_time used=%d\n", __func__, exposure_time); + +  r = sanei_genesys_get_address (reg, 0x34); +  r->value = sensor.dummy_pixel; +} + +void CommandSetGl847::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                 Genesys_Register_Set* reg, +                                                 const ScanSession& session) const +{ +    DBG_HELPER(dbg); +    session.assert_computed(); + +  int move; +  int exposure_time; + +  int slope_dpi = 0; +  int dummy = 0; + +    dummy = 3 - session.params.channels; + +/* slope_dpi */ +/* cis color scan is effectively a gray scan with 3 gray lines per color +   line and a FILTER of 0 */ +    if (dev->model->is_cis) { +        slope_dpi = session.params.yres * session.params.channels; +    } else { +        slope_dpi = session.params.yres; +    } + +  slope_dpi = slope_dpi * (1 + dummy); + +    exposure_time = sensor.exposure_lperiod; +    const auto& motor_profile = sanei_genesys_get_motor_profile(*gl847_motor_profiles, +                                                                dev->model->motor_id, +                                                                exposure_time); + +  DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time); +    DBG(DBG_info, "%s : scan_step_type=%d\n", __func__, +        static_cast<unsigned>(motor_profile.step_type)); + +  /* we enable true gray for cis scanners only, and just when doing +   * scan since color calibration is OK for this mode +   */ +    gl847_init_optical_regs_scan(dev, sensor, reg, exposure_time, session); + +    move = session.params.starty; +    DBG(DBG_info, "%s: move=%d steps\n", __func__, move); + +    MotorFlag mflags = MotorFlag::NONE; +    if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) { +        mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE; +    } +    if (has_flag(session.params.flags, ScanFlag::FEEDING)) { +        mflags |= MotorFlag::FEED; +  } +    if (has_flag(session.params.flags, ScanFlag::REVERSE)) { +        mflags |= MotorFlag::REVERSE; +    } + +    gl847_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure_time, slope_dpi, +                               dev->model->is_cis ? session.output_line_count * session.params.channels +                                                  : session.output_line_count, +                               dummy, move, mflags); + +    dev->read_buffer.clear(); +    dev->read_buffer.alloc(session.buffer_size_read); + +    dev->read_active = true; + +    dev->session = session; + +    dev->total_bytes_read = 0; +    dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines; + +    DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read); +} + +ScanSession CommandSetGl847::calculate_scan_session(const Genesys_Device* dev, +                                                    const Genesys_Sensor& sensor, +                                                    const Genesys_Settings& settings) const +{ +  int start; + +    DBG(DBG_info, "%s ", __func__); +    debug_dump(DBG_info, settings); + +  /* start */ +    start = static_cast<int>(dev->model->x_offset); +    start = static_cast<int>(start + settings.tl_x); +    start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = settings.xres; +    session.params.yres = settings.yres; +    session.params.startx = start; // not used +    session.params.starty = 0; // not used +    session.params.pixels = settings.pixels; +    session.params.requested_pixels = settings.requested_pixels; +    session.params.lines = settings.lines; +    session.params.depth = settings.depth; +    session.params.channels = settings.get_channels(); +    session.params.scan_method = settings.scan_method; +    session.params.scan_mode = settings.scan_mode; +    session.params.color_filter = settings.color_filter; +    session.params.flags = ScanFlag::NONE; + +    compute_session(dev, session, sensor); + +    return session; +} + +// for fast power saving methods only, like disabling certain amplifiers +void CommandSetGl847::save_power(Genesys_Device* dev, bool enable) const +{ +    DBG_HELPER_ARGS(dbg, "enable = %d", enable); +    (void) dev; +} + +void CommandSetGl847::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const +{ +    (void) dev; +    DBG_HELPER_ARGS(dbg, "delay = %d", delay); +} + +// Send the low-level scan command +void CommandSetGl847::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set* reg, bool start_motor) const +{ +    DBG_HELPER(dbg); +    (void) sensor; +  uint8_t val; +  GenesysRegister *r; + +    // clear GPIO 10 +    if (dev->model->gpio_id != GpioId::CANON_LIDE_700F) { +        val = dev->interface->read_register(REG_0x6C); +        val &= ~REG_0x6C_GPIO10; +        dev->interface->write_register(REG_0x6C, val); +    } + +    val = REG_0x0D_CLRLNCNT; +    dev->interface->write_register(REG_0x0D, val); +    val = REG_0x0D_CLRMCNT; +    dev->interface->write_register(REG_0x0D, val); + +    val = dev->interface->read_register(REG_0x01); +    val |= REG_0x01_SCAN; +    dev->interface->write_register(REG_0x01, val); +    r = sanei_genesys_get_address (reg, REG_0x01); +  r->value = val; + +    scanner_start_action(*dev, start_motor); + +    dev->advance_head_pos_by_session(ScanHeadId::PRIMARY); +} + + +// Send the stop scan command +void CommandSetGl847::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg, +                               bool check_stop) const +{ +    (void) reg; +    DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop); + +    if (!dev->model->is_sheetfed) { +        scanner_stop_action(*dev); +    } +} + +/** Park head + * Moves the slider to the home (top) position slowly + * @param dev device to park + * @param wait_until_home true to make the function waiting for head + * to be home before returning, if fals returne immediately +*/ +void CommandSetGl847::move_back_home(Genesys_Device* dev, bool wait_until_home) const +{ +    scanner_move_back_home(*dev, wait_until_home); +} + +// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi +// from very top of scanner +void CommandSetGl847::search_start_position(Genesys_Device* dev) const +{ +    DBG_HELPER(dbg); +  int size; +  Genesys_Register_Set local_reg; + +  int pixels = 600; +  int dpi = 300; + +  local_reg = dev->reg; + +  /* sets for a 200 lines * 600 pixels */ +  /* normal scan with no shading */ + +    // FIXME: the current approach of doing search only for one resolution does not work on scanners +    // whith employ different sensors with potentially different settings. +    const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method); + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx =  0; +    session.params.starty =  0; /*we should give a small offset here~60 steps */ +    session.params.pixels = 600; +    session.params.lines = dev->model->search_lines; +    session.params.depth = 8; +    session.params.channels =  1; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::GREEN; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    // send to scanner +    dev->interface->write_registers(local_reg); + +  size = pixels * dev->model->search_lines; + +  std::vector<uint8_t> data(size); + +    begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_start_position"); +        end_scan(dev, &local_reg, true); +        dev->reg = local_reg; +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl847_search_position.pnm", data.data(), 8, 1, pixels, +                                     dev->model->search_lines); +    } + +    end_scan(dev, &local_reg, true); + +  /* update regs to copy ASIC internal state */ +  dev->reg = local_reg; + +    // TODO: find out where sanei_genesys_search_reference_point stores information, +    // and use that correctly +    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, dpi, pixels, +                                             dev->model->search_lines); +    } +} + +// sets up register for coarse gain calibration +// todo: check it for scanners using it +void CommandSetGl847::init_regs_for_coarse_calibration(Genesys_Device* dev, +                                                       const Genesys_Sensor& sensor, +                                                       Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel(); +    session.params.lines = 20; +    session.params.depth = 16; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__, +      sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres); + +    dev->interface->write_registers(regs); +} + +// init registers for shading calibration +void CommandSetGl847::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); + +  dev->calib_channels = 3; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    dev->calib_resolution = sensor.get_register_hwdpi(dev->settings.xres); + +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, dev->calib_resolution, +                                                         dev->calib_channels, +                                                         dev->settings.scan_method); + +  dev->calib_total_bytes_to_read = 0; +  dev->calib_lines = dev->model->shading_lines; +    if (dev->calib_resolution == 4800) { +        dev->calib_lines *= 2; +    } +    dev->calib_pixels = (calib_sensor.sensor_pixels * dev->calib_resolution) / +                        calib_sensor.optical_res; + +    DBG(DBG_io, "%s: calib_lines  = %zu\n", __func__, dev->calib_lines); +    DBG(DBG_io, "%s: calib_pixels = %zu\n", __func__, dev->calib_pixels); + +    ScanSession session; +    session.params.xres = dev->calib_resolution; +    session.params.yres = dev->motor.base_ydpi; +    session.params.startx = 0; +    session.params.starty = 20; +    session.params.pixels = dev->calib_pixels; +    session.params.lines = dev->calib_lines; +    session.params.depth = 16; +    session.params.channels = dev->calib_channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::DISABLE_BUFFER_FULL_MOVE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    dev->interface->write_registers(regs); + +  /* we use GENESYS_FLAG_SHADING_REPARK */ +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); +} + +/** @brief set up registers for the actual scan + */ +void CommandSetGl847::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    DBG_HELPER(dbg); +  float move; +  int move_dpi; +  float start; + +    debug_dump(DBG_info, dev->settings); + +  /* steps to move to reach scanning area: +     - first we move to physical start of scanning +     either by a fixed steps amount from the black strip +     or by a fixed amount from parking position, +     minus the steps done during shading calibration +     - then we move by the needed offset whitin physical +     scanning area + +     assumption: steps are expressed at maximum motor resolution + +     we need: +     float y_offset; +     float y_size; +     float y_offset_calib; +     mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH */ + +  /* if scanner uses GENESYS_FLAG_SEARCH_START y_offset is +     relative from origin, else, it is from parking position */ + +  move_dpi = dev->motor.base_ydpi; + +    move = static_cast<float>(dev->model->y_offset); +    move = static_cast<float>(move + dev->settings.tl_y); +    move = static_cast<float>((move * move_dpi) / MM_PER_INCH); +    move -= dev->head_pos(ScanHeadId::PRIMARY); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* fast move to scan area */ +  /* we don't move fast the whole distance since it would involve +   * computing acceleration/deceleration distance for scan +   * resolution. So leave a remainder for it so scan makes the final +   * move tuning */ +    if (dev->settings.get_channels() * dev->settings.yres >= 600 && move > 700) { +        scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move - 500), +                     Direction::FORWARD); +      move=500; +    } + +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); +  DBG(DBG_info, "%s: move=%f steps\n", __func__, move); + +  /* start */ +    start = static_cast<float>(dev->model->x_offset); +    start = static_cast<float>(start + dev->settings.tl_x); +    start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH); + +    ScanSession session; +    session.params.xres = dev->settings.xres; +    session.params.yres = dev->settings.yres; +    session.params.startx = static_cast<unsigned>(start); +    session.params.starty = static_cast<unsigned>(move); +    session.params.pixels = dev->settings.pixels; +    session.params.requested_pixels = dev->settings.requested_pixels; +    session.params.lines = dev->settings.lines; +    session.params.depth = dev->settings.depth; +    session.params.channels = dev->settings.get_channels(); +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = dev->settings.scan_mode; +    session.params.color_filter = dev->settings.color_filter; +    // backtracking isn't handled well, so don't enable it +    session.params.flags = ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, &dev->reg, session); +} + + +/** + * Send shading calibration data. The buffer is considered to always hold values + * for all the channels. + */ +void CommandSetGl847::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                        uint8_t* data, int size) const +{ +    DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size); +  uint32_t addr, length, i, x, factor, pixels; +    uint32_t dpiset, dpihw; +  uint8_t val,*ptr,*src; + +  /* shading data is plit in 3 (up to 5 with IR) areas +     write(0x10014000,0x00000dd8) +     URB 23429  bulk_out len  3544  wrote 0x33 0x10 0x.... +     write(0x1003e000,0x00000dd8) +     write(0x10068000,0x00000dd8) +   */ +    length = static_cast<std::uint32_t>(size / 3); +    std::uint32_t strpixel = dev->session.pixel_startx; +    std::uint32_t endpixel = dev->session.pixel_endx; + +  /* compute deletion factor */ +    dpiset = dev->reg.get16(REG_DPISET); +    dpihw = sensor.get_register_hwdpi(dpiset); +  factor=dpihw/dpiset; +  DBG(DBG_io2, "%s: factor=%d\n", __func__, factor); + +  pixels=endpixel-strpixel; + +  /* since we're using SHDAREA, substract startx coordinate from shading */ +    strpixel -= (sensor.ccd_start_xoffset * 600) / sensor.optical_res; + +  /* turn pixel value into bytes 2x16 bits words */ +  strpixel*=2*2; +  pixels*=2*2; + +    dev->interface->record_key_value("shading_offset", std::to_string(strpixel)); +    dev->interface->record_key_value("shading_pixels", std::to_string(pixels)); +    dev->interface->record_key_value("shading_length", std::to_string(length)); +    dev->interface->record_key_value("shading_factor", std::to_string(factor)); + +  std::vector<uint8_t> buffer(pixels, 0); + +  DBG(DBG_io2, "%s: using chunks of %d (0x%04x) bytes\n", __func__, pixels, pixels); + +  /* base addr of data has been written in reg D0-D4 in 4K word, so AHB address +   * is 8192*reg value */ + +  /* write actual color channel data */ +  for(i=0;i<3;i++) +    { +      /* build up actual shading data by copying the part from the full width one +       * to the one corresponding to SHDAREA */ +      ptr = buffer.data(); + +      /* iterate on both sensor segment */ +      for(x=0;x<pixels;x+=4*factor) +        { +          /* coefficient source */ +          src=(data+strpixel+i*length)+x; + +          /* coefficient copy */ +          ptr[0]=src[0]; +          ptr[1]=src[1]; +          ptr[2]=src[2]; +          ptr[3]=src[3]; + +          /* next shading coefficient */ +          ptr+=4; +        } + +        val = dev->interface->read_register(0xd0+i); +        addr = val * 8192 + 0x10000000; +        dev->interface->write_ahb(addr, pixels, buffer.data()); +    } +} + +/** @brief calibrates led exposure + * Calibrate exposure by scanning a white area until the used exposure gives + * data white enough. + * @param dev device to calibrate + */ +SensorExposure CommandSetGl847::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                                Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +  int num_pixels; +  int total_size; +  int used_res; +  int i, j; +  int val; +    int channels; +  int avg[3], top[3], bottom[3]; +  int turn; +  uint16_t exp[3]; +  float move; + +    move = static_cast<float>(dev->model->y_offset_calib_white); +    move = static_cast<float>((move * (dev->motor.base_ydpi / 4)) / MM_PER_INCH); +    if (move > 20) { +        scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move), +                     Direction::FORWARD); +    } +  DBG(DBG_io, "%s: move=%f steps\n", __func__, move); + +  /* offset calibration is always done in color mode */ +  channels = 3; +    used_res = sensor.get_register_hwdpi(dev->settings.xres); +    const auto& calib_sensor = sanei_genesys_find_sensor(dev, used_res, channels, +                                                         dev->settings.scan_method); +    num_pixels = (calib_sensor.sensor_pixels * used_res) / calib_sensor.optical_res; + +  /* initial calibration reg values */ +  regs = dev->reg; + +    ScanSession session; +    session.params.xres = used_res; +    session.params.yres = used_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = num_pixels; +    session.params.lines = 1; +    session.params.depth = 16; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, calib_sensor); + +    init_regs_for_scan_session(dev, calib_sensor, ®s, session); + +    total_size = num_pixels * channels * (session.params.depth/8) * 1; +  std::vector<uint8_t> line(total_size); + +    // initial loop values and boundaries +    exp[0] = calib_sensor.exposure.red; +    exp[1] = calib_sensor.exposure.green; +    exp[2] = calib_sensor.exposure.blue; + +    bottom[0] = 28000; +    bottom[1] = 28000; +    bottom[2] = 28000; + +    top[0] = 32000; +    top[1] = 32000; +    top[2] = 32000; + +  turn = 0; + +  /* no move during led calibration */ +    bool acceptable = false; +  sanei_genesys_set_motor_power(regs, false); +  do +    { +        // set up exposure +        regs.set16(REG_EXPR,exp[0]); +        regs.set16(REG_EXPG,exp[1]); +        regs.set16(REG_EXPB,exp[2]); + +        // write registers and scan data +        dev->interface->write_registers(regs); + +      DBG(DBG_info, "%s: starting line reading\n", __func__); +        begin_scan(dev, calib_sensor, ®s, true); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("led_calibration"); +            scanner_stop_action(*dev); +            move_back_home(dev, true); +            return calib_sensor.exposure; +        } + +        sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +        // stop scanning +        scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[30]; +            std::snprintf(fn, 30, "gl847_led_%02d.pnm", turn); +            sanei_genesys_write_pnm_file(fn, line.data(), session.params.depth, +                                         channels, num_pixels, 1); +	} + +      /* compute average */ +      for (j = 0; j < channels; j++) +	{ +	  avg[j] = 0; +	  for (i = 0; i < num_pixels; i++) +	    { +	      if (dev->model->is_cis) +		val = +		  line[i * 2 + j * 2 * num_pixels + 1] * 256 + +		  line[i * 2 + j * 2 * num_pixels]; +	      else +		val = +		  line[i * 2 * channels + 2 * j + 1] * 256 + +		  line[i * 2 * channels + 2 * j]; +	      avg[j] += val; +	    } + +	  avg[j] /= num_pixels; +	} + +      DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]); + +      /* check if exposure gives average within the boundaries */ +        acceptable = true; +      for(i=0;i<3;i++) +        { +            if (avg[i] < bottom[i] || avg[i] > top[i]) { +                auto target = (bottom[i] + top[i]) / 2; +                exp[i] = (exp[i] * target) / avg[i]; +                acceptable = false; +            } +        } + +      turn++; +    } +  while (!acceptable && turn < 100); + +  DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]); + +    // set these values as final ones for scan +    dev->reg.set16(REG_EXPR, exp[0]); +    dev->reg.set16(REG_EXPG, exp[1]); +    dev->reg.set16(REG_EXPB, exp[2]); + +    // go back home +    if (move>20) { +        move_back_home(dev, true); +    } + +    return { exp[0], exp[1], exp[2] }; +} + +/** + * set up GPIO/GPOE for idle state + */ +static void gl847_init_gpio(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  int idx=0; + +  /* search GPIO profile */ +    while(gpios[idx].gpio_id != GpioId::UNKNOWN && dev->model->gpio_id != gpios[idx].gpio_id) { +      idx++; +    } +    if (gpios[idx].gpio_id == GpioId::UNKNOWN) { +        throw SaneException("failed to find GPIO profile for sensor_id=%d", +                            static_cast<unsigned>(dev->model->sensor_id)); +    } + +    dev->interface->write_register(REG_0xA7, gpios[idx].ra7); +    dev->interface->write_register(REG_0xA6, gpios[idx].ra6); + +    dev->interface->write_register(REG_0x6E, gpios[idx].r6e); +    dev->interface->write_register(REG_0x6C, 0x00); + +    dev->interface->write_register(REG_0x6B, gpios[idx].r6b); +    dev->interface->write_register(REG_0x6C, gpios[idx].r6c); +    dev->interface->write_register(REG_0x6D, gpios[idx].r6d); +    dev->interface->write_register(REG_0x6E, gpios[idx].r6e); +    dev->interface->write_register(REG_0x6F, gpios[idx].r6f); + +    dev->interface->write_register(REG_0xA8, gpios[idx].ra8); +    dev->interface->write_register(REG_0xA9, gpios[idx].ra9); +} + +/** + * set memory layout by filling values in dedicated registers + */ +static void gl847_init_memory_layout(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +  int idx = 0; +  uint8_t val; + +  /* point to per model memory layout */ +  idx = 0; +    if (dev->model->model_id == ModelId::CANON_LIDE_100) { +      idx = 0; +    } +    if (dev->model->model_id == ModelId::CANON_LIDE_200) { +      idx = 1; +    } +    if (dev->model->model_id == ModelId::CANON_5600F) { +      idx = 2; +    } +    if (dev->model->model_id == ModelId::CANON_LIDE_700F) { +      idx = 3; +    } + +  /* CLKSET nd DRAMSEL */ +  val = layouts[idx].dramsel; +    dev->interface->write_register(REG_0x0B, val); +  dev->reg.find_reg(0x0b).value = val; + +  /* prevent further writings by bulk write register */ +  dev->reg.remove_reg(0x0b); + +  /* setup base address for shading data. */ +  /* values must be multiplied by 8192=0x4000 to give address on AHB */ +  /* R-Channel shading bank0 address setting for CIS */ +    dev->interface->write_register(0xd0, layouts[idx].rd0); +  /* G-Channel shading bank0 address setting for CIS */ +    dev->interface->write_register(0xd1, layouts[idx].rd1); +  /* B-Channel shading bank0 address setting for CIS */ +    dev->interface->write_register(0xd2, layouts[idx].rd2); + +  /* setup base address for scanned data. */ +  /* values must be multiplied by 1024*2=0x0800 to give address on AHB */ +  /* R-Channel ODD image buffer 0x0124->0x92000 */ +  /* size for each buffer is 0x16d*1k word */ +    dev->interface->write_register(0xe0, layouts[idx].re0); +    dev->interface->write_register(0xe1, layouts[idx].re1); +  /* R-Channel ODD image buffer end-address 0x0291->0x148800 => size=0xB6800*/ +    dev->interface->write_register(0xe2, layouts[idx].re2); +    dev->interface->write_register(0xe3, layouts[idx].re3); + +  /* R-Channel EVEN image buffer 0x0292 */ +    dev->interface->write_register(0xe4, layouts[idx].re4); +    dev->interface->write_register(0xe5, layouts[idx].re5); +  /* R-Channel EVEN image buffer end-address 0x03ff*/ +    dev->interface->write_register(0xe6, layouts[idx].re6); +    dev->interface->write_register(0xe7, layouts[idx].re7); + +  /* same for green, since CIS, same addresses */ +    dev->interface->write_register(0xe8, layouts[idx].re0); +    dev->interface->write_register(0xe9, layouts[idx].re1); +    dev->interface->write_register(0xea, layouts[idx].re2); +    dev->interface->write_register(0xeb, layouts[idx].re3); +    dev->interface->write_register(0xec, layouts[idx].re4); +    dev->interface->write_register(0xed, layouts[idx].re5); +    dev->interface->write_register(0xee, layouts[idx].re6); +    dev->interface->write_register(0xef, layouts[idx].re7); + +/* same for blue, since CIS, same addresses */ +    dev->interface->write_register(0xf0, layouts[idx].re0); +    dev->interface->write_register(0xf1, layouts[idx].re1); +    dev->interface->write_register(0xf2, layouts[idx].re2); +    dev->interface->write_register(0xf3, layouts[idx].re3); +    dev->interface->write_register(0xf4, layouts[idx].re4); +    dev->interface->write_register(0xf5, layouts[idx].re5); +    dev->interface->write_register(0xf6, layouts[idx].re6); +    dev->interface->write_register(0xf7, layouts[idx].re7); +} + +/* * + * initialize ASIC from power on condition + */ +void CommandSetGl847::asic_boot(Genesys_Device* dev, bool cold) const +{ +    DBG_HELPER(dbg); + +    // reset ASIC if cold boot +    if (cold) { +        dev->interface->write_register(0x0e, 0x01); +        dev->interface->write_register(0x0e, 0x00); +    } + +    // test CHKVER +    uint8_t val = dev->interface->read_register(REG_0x40); +    if (val & REG_0x40_CHKVER) { +        val = dev->interface->read_register(0x00); +        DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val); +    } + +  /* Set default values for registers */ +  gl847_init_registers (dev); + +    // Write initial registers +    dev->interface->write_registers(dev->reg); + +  /* Enable DRAM by setting a rising edge on bit 3 of reg 0x0b */ +    val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL; +    val = (val | REG_0x0B_ENBDRAM); +    dev->interface->write_register(REG_0x0B, val); +    dev->reg.find_reg(0x0b).value = val; + +  /* CIS_LINE */ +    dev->reg.init_reg(0x08, REG_0x08_CIS_LINE); +    dev->interface->write_register(0x08, dev->reg.find_reg(0x08).value); + +    // set up end access +    dev->interface->write_0x8c(0x10, 0x0b); +    dev->interface->write_0x8c(0x13, 0x0e); + +    // setup gpio +    gl847_init_gpio(dev); + +    // setup internal memory layout +    gl847_init_memory_layout (dev); + +    dev->reg.init_reg(0xf8, 0x01); +    dev->interface->write_register(0xf8, dev->reg.find_reg(0xf8).value); +} + +/** + * initialize backend and ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home + */ +void CommandSetGl847::init(Genesys_Device* dev) const +{ +  DBG_INIT (); +    DBG_HELPER(dbg); + +    sanei_genesys_asic_init(dev, 0); +} + +void CommandSetGl847::update_hardware_sensors(Genesys_Scanner* s) const +{ +    DBG_HELPER(dbg); +  /* do what is needed to get a new set of events, but try to not lose +     any of them. +   */ +  uint8_t val; +  uint8_t scan, file, email, copy; +    switch(s->dev->model->gpio_id) { +    case GpioId::CANON_LIDE_700F: +        scan=0x04; +        file=0x02; +        email=0x01; +        copy=0x08; +        break; +    default: +        scan=0x01; +        file=0x02; +        email=0x04; +        copy=0x08; +    } +    val = s->dev->interface->read_register(REG_0x6D); + +    s->buttons[BUTTON_SCAN_SW].write((val & scan) == 0); +    s->buttons[BUTTON_FILE_SW].write((val & file) == 0); +    s->buttons[BUTTON_EMAIL_SW].write((val & email) == 0); +    s->buttons[BUTTON_COPY_SW].write((val & copy) == 0); +} + +void CommandSetGl847::update_home_sensor_gpio(Genesys_Device& dev) const +{ +    DBG_HELPER(dbg); + +    if (dev.model->gpio_id == GpioId::CANON_LIDE_700F) { +        std::uint8_t val = dev.interface->read_register(REG_0x6C); +        val &= ~REG_0x6C_GPIO10; +        dev.interface->write_register(REG_0x6C, val); +    } else { +        std::uint8_t val = dev.interface->read_register(REG_0x6C); +        val |= REG_0x6C_GPIO10; +        dev.interface->write_register(REG_0x6C, val); +    } +} + +/** @brief search for a full width black or white strip. + * This function searches for a black or white stripe across the scanning area. + * When searching backward, the searched area must completely be of the desired + * color since this area will be used for calibration which scans forward. + * @param dev scanner device + * @param forward true if searching forward, false if searching backward + * @param black true if searching for a black strip, false for a white strip + */ +void CommandSetGl847::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward, +                                   bool black) const +{ +    DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse"); +  unsigned int pixels, lines, channels; +  Genesys_Register_Set local_reg; +  size_t size; +  unsigned int pass, count, found, x, y; +  char title[80]; + +    set_fe(dev, sensor, AFE_SET); +    scanner_stop_action(*dev); + +    // set up for a gray scan at lowest dpi +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method); +    unsigned dpi = resolution_settings.get_min_resolution_x(); +  channels = 1; +  /* 10 MM */ +  /* lines = (10 * dpi) / MM_PER_INCH; */ +  /* shading calibation is done with dev->motor.base_ydpi */ +  lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi; +  pixels = (sensor.sensor_pixels * dpi) / sensor.optical_res; +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +  local_reg = dev->reg; + +    ScanSession session; +    session.params.xres = dpi; +    session.params.yres = dpi; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA; +    if (!forward) { +        session.params.flags |= ScanFlag::REVERSE; +    } +    compute_session(dev, session, sensor); + +    size = pixels * channels * lines * (session.params.depth / 8); +    std::vector<uint8_t> data(size); + +    init_regs_for_scan_session(dev, sensor, &local_reg, session); + +    dev->interface->write_registers(local_reg); + +    begin_scan(dev, sensor, &local_reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("search_strip"); +        scanner_stop_action(*dev); +        return; +    } + +    wait_until_buffer_non_empty(dev); + +    // now we're on target, we can read data +    sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    scanner_stop_action(*dev); + +  pass = 0; +  if (DBG_LEVEL >= DBG_data) +    { +        std::sprintf(title, "gl847_search_strip_%s_%s%02d.pnm", +                     black ? "black" : "white", forward ? "fwd" : "bwd", pass); +        sanei_genesys_write_pnm_file(title, data.data(), session.params.depth, +                                     channels, pixels, lines); +    } + +  /* loop until strip is found or maximum pass number done */ +  found = 0; +  while (pass < 20 && !found) +    { +        dev->interface->write_registers(local_reg); + +        // now start scan +        begin_scan(dev, sensor, &local_reg, true); + +        wait_until_buffer_non_empty(dev); + +        // now we're on target, we can read data +        sanei_genesys_read_data_from_scanner(dev, data.data(), size); + +    scanner_stop_action(*dev); + +      if (DBG_LEVEL >= DBG_data) +	{ +            std::sprintf(title, "gl847_search_strip_%s_%s%02d.pnm", +                         black ? "black" : "white", +                         forward ? "fwd" : "bwd", static_cast<int>(pass)); +            sanei_genesys_write_pnm_file(title, data.data(), session.params.depth, +                                         channels, pixels, 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 < lines && !found; y++) +	    { +	      count = 0; +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +		  if (black && data[y * pixels + x] > 90) +		    { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +		  if (!black && data[y * 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) / pixels < 3) +		{ +		  found = 1; +		  DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__, +		      pass, y); +		} +	      else +		{ +		  DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		      (100 * count) / pixels); +		} +	    } +	} +      else			/* since calibration scans are done forward, we need the whole area +				   to be of the required color when searching backward */ +	{ +	  count = 0; +	  for (y = 0; y < lines; y++) +	    { +	      /* count of white/black pixels depending on the color searched */ +	      for (x = 0; x < pixels; x++) +		{ +		  /* when searching for black, detect white pixels */ +		  if (black && data[y * pixels + x] > 90) +		    { +		      count++; +		    } +		  /* when searching for white, detect black pixels */ +		  if (!black && data[y * 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) / (pixels * lines) < 3) +	    { +	      found = 1; +	      DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); +	    } +	  else +	    { +	      DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count, +		  (100 * count) / pixels); +	    } +	} +      pass++; +    } + +  if (found) +    { +      DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white"); +    } +  else +    { +        throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white"); +    } +} + +/** + * average dark pixels of a 8 bits 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; +} + +void CommandSetGl847::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                         Genesys_Register_Set& regs) const +{ +    DBG_HELPER(dbg); +    unsigned channels; +  int pass = 0, avg, total_size; +    int topavg, bottomavg, lines; +  int top, bottom, black_pixels, pixels; + +    // no gain nor offset for AKM AFE +    uint8_t reg04 = dev->interface->read_register(REG_0x04); +    if ((reg04 & REG_0x04_FESET) == 0x02) { +      return; +    } + +  /* offset calibration is always done in color mode */ +  channels = 3; +  dev->calib_pixels = sensor.sensor_pixels; +  lines=1; +    pixels= (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res; +    black_pixels = (sensor.black_pixels * sensor.optical_res) / sensor.optical_res; +  DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels); + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = sensor.optical_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    init_regs_for_scan_session(dev, sensor, ®s, session); + +  sanei_genesys_set_motor_power(regs, false); + +  /* allocate memory for scans */ +  total_size = pixels * channels * lines * (session.params.depth / 8);	/* colors * bytes_per_color * scan lines */ + +  std::vector<uint8_t> first_line(total_size); +  std::vector<uint8_t> second_line(total_size); + +  /* init gain */ +  dev->frontend.set_gain(0, 0); +  dev->frontend.set_gain(1, 0); +  dev->frontend.set_gain(2, 0); + +  /* scan with no move */ +  bottom = 10; +  dev->frontend.set_offset(0, bottom); +  dev->frontend.set_offset(1, bottom); +  dev->frontend.set_offset(2, bottom); + +    set_fe(dev, sensor, AFE_SET); +    dev->interface->write_registers(regs); +  DBG(DBG_info, "%s: starting first line reading\n", __func__); +    begin_scan(dev, sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("offset_calibration"); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +  if (DBG_LEVEL >= DBG_data) +   { +      char fn[30]; +        std::snprintf(fn, 30, "gl847_offset%03d.pnm", bottom); +        sanei_genesys_write_pnm_file(fn, first_line.data(), session.params.depth, +                                     channels, pixels, lines); +   } + +  bottomavg = dark_average (first_line.data(), pixels, lines, channels, black_pixels); +  DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg); + +  /* now top value */ +  top = 255; +  dev->frontend.set_offset(0, top); +  dev->frontend.set_offset(1, top); +  dev->frontend.set_offset(2, top); +    set_fe(dev, sensor, AFE_SET); +    dev->interface->write_registers(regs); +  DBG(DBG_info, "%s: starting second line reading\n", __func__); +    begin_scan(dev, sensor, ®s, true); +    sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +  topavg = dark_average(second_line.data(), pixels, lines, channels, black_pixels); +  DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg); + +  /* 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 +        set_fe(dev, sensor, AFE_SET); +        dev->interface->write_registers(regs); +      DBG(DBG_info, "%s: starting second line reading\n", __func__); +        begin_scan(dev, sensor, ®s, true); +        sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); + +      if (DBG_LEVEL >= DBG_data) +	{ +          char fn[30]; +          std::snprintf(fn, 30, "gl847_offset%03d.pnm", dev->frontend.get_offset(1)); +          sanei_genesys_write_pnm_file(fn, second_line.data(), session.params.depth, +                                       channels, pixels, lines); +	} + +      avg = dark_average(second_line.data(), pixels, 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)); +} + +void CommandSetGl847::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                              Genesys_Register_Set& regs, int dpi) const +{ +    DBG_HELPER_ARGS(dbg, "dpi = %d", dpi); +  int pixels; +  int total_size; +  int i, j, channels; +  int max[3]; +  float gain[3],coeff; +  int val, code, lines; + +    // no gain nor offset for AKM AFE +    uint8_t reg04 = dev->interface->read_register(REG_0x04); +    if ((reg04 & REG_0x04_FESET) == 0x02) { +      return; +    } + +  /* coarse gain calibration is always done in color mode */ +  channels = 3; + +  /* follow CKSEL */ +  if(dev->settings.xres<sensor.optical_res) +    { +        coeff = 0.9f; +    } +  else +    { +      coeff=1.0; +    } +  lines=10; +    pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res; + +    ScanSession session; +    session.params.xres = sensor.optical_res; +    session.params.yres = sensor.optical_res; +    session.params.startx = 0; +    session.params.starty = 0; +    session.params.pixels = pixels; +    session.params.lines = lines; +    session.params.depth = 8; +    session.params.channels = channels; +    session.params.scan_method = dev->settings.scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    session.params.color_filter = dev->settings.color_filter; +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::SINGLE_LINE | +                           ScanFlag::IGNORE_LINE_DISTANCE; +    compute_session(dev, session, sensor); + +    try { +        init_regs_for_scan_session(dev, sensor, ®s, session); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); +        throw; +    } + +    sanei_genesys_set_motor_power(regs, false); + +    dev->interface->write_registers(regs); + +    total_size = pixels * channels * (16 / session.params.depth) * lines; + +  std::vector<uint8_t> line(total_size); + +    set_fe(dev, sensor, AFE_SET); +    begin_scan(dev, sensor, ®s, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("coarse_gain_calibration"); +        scanner_stop_action(*dev); +        move_back_home(dev, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, line.data(), total_size); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file("gl847_gain.pnm", line.data(), session.params.depth, +                                     channels, pixels, lines); +    } + +  /* average value on each channel */ +  for (j = 0; j < channels; j++) +    { +      max[j] = 0; +      for (i = pixels/4; i < (pixels*3/4); i++) +	{ +            if (dev->model->is_cis) { +                val = line[i + j * pixels]; +            } else { +                val = line[i * channels + j]; +            } + +	    max[j] += val; +	} +      max[j] = max[j] / (pixels/2); + +      gain[j] = (static_cast<float>(sensor.gain_white_ref) * coeff) / max[j]; + +      /* turn logical gain value into gain code, checking for overflow */ +        code = static_cast<int>(283 - 208 / gain[j]); +      if (code > 255) +	code = 255; +      else if (code < 0) +	code = 0; +      dev->frontend.set_gain(j, code); + +      DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], gain[j], +          dev->frontend.get_gain(j)); +    } + +    if (dev->model->is_cis) { +        uint8_t gain0 = dev->frontend.get_gain(0); +        if (gain0 > dev->frontend.get_gain(1)) { +            gain0 = dev->frontend.get_gain(1); +        } +        if (gain0 > dev->frontend.get_gain(2)) { +            gain0 = dev->frontend.get_gain(2); +        } +        dev->frontend.set_gain(0, gain0); +        dev->frontend.set_gain(1, gain0); +        dev->frontend.set_gain(2, gain0); +    } + +    if (channels == 1) { +        dev->frontend.set_gain(0, dev->frontend.get_gain(1)); +        dev->frontend.set_gain(2, dev->frontend.get_gain(1)); +    } + +    scanner_stop_action(*dev); + +    move_back_home(dev, true); +} + +bool CommandSetGl847::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const +{ +    (void) dev; +    return false; +} + +void CommandSetGl847::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                           Genesys_Register_Set* regs, int* channels, +                                           int* total_size) const +{ +    (void) dev; +    (void) sensor; +    (void) regs; +    (void) channels; +    (void) total_size; +    throw SaneException("not implemented"); +} + +void CommandSetGl847::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const +{ +    sanei_genesys_send_gamma_table(dev, sensor); +} + +void CommandSetGl847::wait_for_motor_stop(Genesys_Device* dev) const +{ +    (void) dev; +} + +void CommandSetGl847::load_document(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl847::detect_document_end(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl847::eject_document(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +void CommandSetGl847::move_to_ta(Genesys_Device* dev) const +{ +    (void) dev; +    throw SaneException("not implemented"); +} + +std::unique_ptr<CommandSet> create_gl847_cmd_set() +{ +    return std::unique_ptr<CommandSet>(new CommandSetGl847{}); +} + +} // namespace gl847 +} // namespace genesys diff --git a/backend/genesys/gl847.h b/backend/genesys/gl847.h new file mode 100644 index 0000000..a51c293 --- /dev/null +++ b/backend/genesys/gl847.h @@ -0,0 +1,206 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#ifndef BACKEND_GENESYS_GL847_H +#define BACKEND_GENESYS_GL847_H + +#include "genesys.h" +#include "command_set.h" + +namespace genesys { +namespace gl847 { + +typedef struct +{ +    GpioId gpio_id; +  uint8_t r6b; +  uint8_t r6c; +  uint8_t r6d; +  uint8_t r6e; +  uint8_t r6f; +  uint8_t ra6; +  uint8_t ra7; +  uint8_t ra8; +  uint8_t ra9; +} Gpio_Profile; + +static Gpio_Profile gpios[]={ +    { GpioId::CANON_LIDE_200, 0x02, 0xf9, 0x20, 0xff, 0x00, 0x04, 0x04, 0x00, 0x00}, +    { GpioId::CANON_LIDE_700F, 0x06, 0xdb, 0xff, 0xff, 0x80, 0x15, 0x07, 0x20, 0x10}, +    { GpioId::UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +typedef struct +{ +  uint8_t dramsel; +  uint8_t rd0; +  uint8_t rd1; +  uint8_t rd2; +  uint8_t re0; +  uint8_t re1; +  uint8_t re2; +  uint8_t re3; +  uint8_t re4; +  uint8_t re5; +  uint8_t re6; +  uint8_t re7; +} Memory_layout; + +static Memory_layout layouts[]={ +	/* LIDE 100 */ +	{ +                0x29, +		0x0a, 0x15, 0x20, +		0x00, 0xac, 0x02, 0x55, 0x02, 0x56, 0x03, 0xff +	}, +	/* LIDE 200 */ +	{ +                0x29, +		0x0a, 0x1f, 0x34, +		0x01, 0x24, 0x02, 0x91, 0x02, 0x92, 0x03, 0xff +	}, +	/* 5600F */ +	{ +                0x29, +		0x0a, 0x1f, 0x34, +		0x01, 0x24, 0x02, 0x91, 0x02, 0x92, 0x03, 0xff +	}, +	/* LIDE 700F */ +	{ +                0x2a, +		0x0a, 0x33, 0x5c, +		0x02, 0x14, 0x09, 0x09, 0x09, 0x0a, 0x0f, 0xff +	} +}; + +class CommandSetGl847 : public CommandSet +{ +public: +    ~CommandSetGl847() override = default; + +    bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override; + +    void init(Genesys_Device* dev) const override; + +    void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor, +                              Genesys_Register_Set* regs, int* channels, +                              int* total_size) const override; + +    void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                          Genesys_Register_Set& regs) const override; + +    void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                               Genesys_Register_Set& regs) const override; + +    void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                    Genesys_Register_Set* reg, +                                    const ScanSession& session) const override; + +    void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override; +    void set_powersaving(Genesys_Device* dev, int delay) const override; +    void save_power(Genesys_Device* dev, bool enable) const override; + +    void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor, +                    Genesys_Register_Set* regs, bool start_motor) const override; + +    void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override; + +    void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override; + +    void search_start_position(Genesys_Device* dev) const override; + +    void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                            Genesys_Register_Set& regs) const override; + +    void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                 Genesys_Register_Set& regs, int dpi) const override; + +    SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                   Genesys_Register_Set& regs) const override; + +    void wait_for_motor_stop(Genesys_Device* dev) const override; + +    void move_back_home(Genesys_Device* dev, bool wait_until_home) const override; + +    void update_hardware_sensors(struct Genesys_Scanner* s) const override; + +    bool needs_update_home_sensor_gpio() const override { return true; } + +    void update_home_sensor_gpio(Genesys_Device& dev) const override; + +    void load_document(Genesys_Device* dev) const override; + +    void detect_document_end(Genesys_Device* dev) const override; + +    void eject_document(Genesys_Device* dev) const override; + +    void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, +                      bool forward, bool black) const override; + +    void move_to_ta(Genesys_Device* dev) const override; + +    void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data, +                           int size) const override; + +    ScanSession calculate_scan_session(const Genesys_Device* dev, +                                       const Genesys_Sensor& sensor, +                                       const Genesys_Settings& settings) const override; + +    void asic_boot(Genesys_Device* dev, bool cold) const override; +}; + +enum SlopeTable +{ +    SCAN_TABLE = 0, // table 1 at 0x4000 +    BACKTRACK_TABLE = 1, // table 2 at 0x4800 +    STOP_TABLE = 2, // table 3 at 0x5000 +    FAST_TABLE = 3, // table 4 at 0x5800 +    HOME_TABLE = 4, // table 5 at 0x6000 +}; + +} // namespace gl847 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL847_H diff --git a/backend/genesys/gl847_registers.h b/backend/genesys/gl847_registers.h new file mode 100644 index 0000000..0603a6a --- /dev/null +++ b/backend/genesys/gl847_registers.h @@ -0,0 +1,333 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_GL847_REGISTERS_H +#define BACKEND_GENESYS_GL847_REGISTERS_H + +#include <cstdint> + +namespace genesys { +namespace gl847 { + +using RegAddr = std::uint16_t; +using RegMask = std::uint8_t; +using RegShift = unsigned; + +static constexpr RegAddr REG_0x01 = 0x01; +static constexpr RegMask REG_0x01_CISSET = 0x80; +static constexpr RegMask REG_0x01_DOGENB = 0x40; +static constexpr RegMask REG_0x01_DVDSET = 0x20; +static constexpr RegMask REG_0x01_STAGGER = 0x10; +static constexpr RegMask REG_0x01_COMPENB = 0x08; +static constexpr RegMask REG_0x01_TRUEGRAY = 0x04; +static constexpr RegMask REG_0x01_SHDAREA = 0x02; +static constexpr RegMask REG_0x01_SCAN = 0x01; + +static constexpr RegAddr REG_0x02 = 0x02; +static constexpr RegMask REG_0x02_NOTHOME = 0x80; +static constexpr RegMask REG_0x02_ACDCDIS = 0x40; +static constexpr RegMask REG_0x02_AGOHOME = 0x20; +static constexpr RegMask REG_0x02_MTRPWR = 0x10; +static constexpr RegMask REG_0x02_FASTFED = 0x08; +static constexpr RegMask REG_0x02_MTRREV = 0x04; +static constexpr RegMask REG_0x02_HOMENEG = 0x02; +static constexpr RegMask REG_0x02_LONGCURV = 0x01; + +static constexpr RegAddr REG_0x03 = 0x03; +static constexpr RegMask REG_0x03_LAMPDOG = 0x80; +static constexpr RegMask REG_0x03_AVEENB = 0x40; +static constexpr RegMask REG_0x03_XPASEL = 0x20; +static constexpr RegMask REG_0x03_LAMPPWR = 0x10; +static constexpr RegMask REG_0x03_LAMPTIM = 0x0f; + +static constexpr RegAddr REG_0x04 = 0x04; +static constexpr RegMask REG_0x04_LINEART = 0x80; +static constexpr RegMask REG_0x04_BITSET = 0x40; +static constexpr RegMask REG_0x04_AFEMOD = 0x30; +static constexpr RegMask REG_0x04_FILTER = 0x0c; +static constexpr RegMask REG_0x04_FESET = 0x03; +static constexpr RegShift REG_0x04S_AFEMOD = 4; + +static constexpr RegAddr REG_0x05 = 0x05; +static constexpr RegMask REG_0x05_DPIHW = 0xc0; +static constexpr RegMask REG_0x05_DPIHW_600 = 0x00; +static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40; +static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80; +static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0; +static constexpr RegMask REG_0x05_MTLLAMP = 0x30; +static constexpr RegMask REG_0x05_GMMENB = 0x08; +static constexpr RegMask REG_0x05_MTLBASE = 0x03; + +static constexpr RegAddr REG_0x06 = 0x06; +static constexpr RegMask REG_0x06_SCANMOD = 0xe0; +static constexpr RegMask REG_0x06S_SCANMOD = 5; +static constexpr RegMask REG_0x06_PWRBIT = 0x10; +static constexpr RegMask REG_0x06_GAIN4 = 0x08; +static constexpr RegMask REG_0x06_OPTEST = 0x07; + +static constexpr RegMask REG_0x07_LAMPSIM = 0x80; + +static constexpr RegMask REG_0x08_DRAM2X = 0x80; +static constexpr RegMask REG_0x08_MPENB = 0x20; +static constexpr RegMask REG_0x08_CIS_LINE = 0x10; +static constexpr RegMask REG_0x08_IR1ENB = 0x08; +static constexpr RegMask REG_0x08_IR2ENB = 0x04; +static constexpr RegMask REG_0x08_ENB24M = 0x01; + +static constexpr RegMask REG_0x09_MCNTSET = 0xc0; +static constexpr RegMask REG_0x09_EVEN1ST = 0x20; +static constexpr RegMask REG_0x09_BLINE1ST = 0x10; +static constexpr RegMask REG_0x09_BACKSCAN = 0x08; +static constexpr RegMask REG_0x09_ENHANCE = 0x04; +static constexpr RegMask REG_0x09_SHORTTG = 0x02; +static constexpr RegMask REG_0x09_NWAIT = 0x01; + +static constexpr RegShift REG_0x09S_MCNTSET = 6; +static constexpr RegShift REG_0x09S_CLKSET = 4; + +static constexpr RegMask REG_0x0A_LPWMEN = 0x10; + +static constexpr RegAddr REG_0x0B = 0x0b; +static constexpr RegMask REG_0x0B_DRAMSEL = 0x07; +static constexpr RegMask REG_0x0B_ENBDRAM = 0x08; +static constexpr RegMask REG_0x0B_RFHDIS = 0x10; +static constexpr RegMask REG_0x0B_CLKSET = 0xe0; +static constexpr RegMask REG_0x0B_24MHZ = 0x00; +static constexpr RegMask REG_0x0B_30MHZ = 0x20; +static constexpr RegMask REG_0x0B_40MHZ = 0x40; +static constexpr RegMask REG_0x0B_48MHZ = 0x60; +static constexpr RegMask REG_0x0B_60MHZ = 0x80; + +static constexpr RegAddr REG_0x0C = 0x0c; +static constexpr RegMask REG_0x0C_CCDLMT = 0x0f; + +static constexpr RegAddr REG_0x0D = 0x0d; +static constexpr RegMask REG_0x0D_FULLSTP = 0x10; +static constexpr RegMask REG_0x0D_SEND = 0x80; +static constexpr RegMask REG_0x0D_CLRMCNT = 0x04; +static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02; +static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01; + +static constexpr RegAddr REG_0x0F = 0x0f; + +static constexpr RegMask REG_0x16_CTRLHI = 0x80; +static constexpr RegMask REG_0x16_TOSHIBA = 0x40; +static constexpr RegMask REG_0x16_TGINV = 0x20; +static constexpr RegMask REG_0x16_CK1INV = 0x10; +static constexpr RegMask REG_0x16_CK2INV = 0x08; +static constexpr RegMask REG_0x16_CTRLINV = 0x04; +static constexpr RegMask REG_0x16_CKDIS = 0x02; +static constexpr RegMask REG_0x16_CTRLDIS = 0x01; + +static constexpr RegMask REG_0x17_TGMODE = 0xc0; +static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00; +static constexpr RegMask REG_0x17_TGMODE_REF = 0x40; +static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80; +static constexpr RegMask REG_0x17_TGW = 0x3f; +static constexpr RegMask REG_0x17S_TGW = 0; + +static constexpr RegAddr REG_0x18 = 0x18; +static constexpr RegMask REG_0x18_CNSET = 0x80; +static constexpr RegMask REG_0x18_DCKSEL = 0x60; +static constexpr RegMask REG_0x18_CKTOGGLE = 0x10; +static constexpr RegMask REG_0x18_CKDELAY = 0x0c; +static constexpr RegMask REG_0x18_CKSEL = 0x03; + +static constexpr RegMask REG_0x1A_SW2SET = 0x80; +static constexpr RegMask REG_0x1A_SW1SET = 0x40; +static constexpr RegMask REG_0x1A_MANUAL3 = 0x02; +static constexpr RegMask REG_0x1A_MANUAL1 = 0x01; +static constexpr RegMask REG_0x1A_CK4INV = 0x08; +static constexpr RegMask REG_0x1A_CK3INV = 0x04; +static constexpr RegMask REG_0x1A_LINECLP = 0x02; + +static constexpr RegAddr REG_0x1C = 0x1c; +static constexpr RegMask REG_0x1C_TGTIME = 0x07; + +static constexpr RegMask REG_0x1D_CK4LOW = 0x80; +static constexpr RegMask REG_0x1D_CK3LOW = 0x40; +static constexpr RegMask REG_0x1D_CK1LOW = 0x20; +static constexpr RegMask REG_0x1D_TGSHLD = 0x1f; +static constexpr RegMask REG_0x1DS_TGSHLD = 0; + +static constexpr RegMask REG_0x1E_WDTIME = 0xf0; +static constexpr RegMask REG_0x1ES_WDTIME = 4; +static constexpr RegMask REG_0x1E_LINESEL = 0x0f; +static constexpr RegMask REG_0x1ES_LINESEL = 0; + +static constexpr RegAddr REG_FEDCNT = 0x1f; + +static constexpr RegAddr REG_0x24 = 0x1c; +static constexpr RegAddr REG_0x40 = 0x40; +static constexpr RegMask REG_0x40_CHKVER = 0x10; +static constexpr RegMask REG_0x40_HISPDFLG = 0x04; +static constexpr RegMask REG_0x40_MOTMFLG = 0x02; +static constexpr RegMask REG_0x40_DATAENB = 0x01; + +static constexpr RegMask REG_0x41_PWRBIT = 0x80; +static constexpr RegMask REG_0x41_BUFEMPTY = 0x40; +static constexpr RegMask REG_0x41_FEEDFSH = 0x20; +static constexpr RegMask REG_0x41_SCANFSH = 0x10; +static constexpr RegMask REG_0x41_HOMESNR = 0x08; +static constexpr RegMask REG_0x41_LAMPSTS = 0x04; +static constexpr RegMask REG_0x41_FEBUSY = 0x02; +static constexpr RegMask REG_0x41_MOTORENB = 0x01; + +static constexpr RegMask REG_0x58_VSMP = 0xf8; +static constexpr RegShift REG_0x58S_VSMP = 3; +static constexpr RegMask REG_0x58_VSMPW = 0x07; +static constexpr RegShift REG_0x58S_VSMPW = 0; + +static constexpr RegMask REG_0x59_BSMP = 0xf8; +static constexpr RegShift REG_0x59S_BSMP = 3; +static constexpr RegMask REG_0x59_BSMPW = 0x07; +static constexpr RegShift REG_0x59S_BSMPW = 0; + +static constexpr RegMask REG_0x5A_ADCLKINV = 0x80; +static constexpr RegMask REG_0x5A_RLCSEL = 0x40; +static constexpr RegMask REG_0x5A_CDSREF = 0x30; +static constexpr RegShift REG_0x5AS_CDSREF = 4; +static constexpr RegMask REG_0x5A_RLC = 0x0f; +static constexpr RegShift REG_0x5AS_RLC = 0; + +static constexpr RegMask REG_0x5E_DECSEL = 0xe0; +static constexpr RegShift REG_0x5ES_DECSEL = 5; +static constexpr RegMask REG_0x5E_STOPTIM = 0x1f; +static constexpr RegShift REG_0x5ES_STOPTIM = 0; + +static constexpr RegAddr REG_0x60 = 0x60; +static constexpr RegMask REG_0x60_Z1MOD = 0x1f; +static constexpr RegAddr REG_0x61 = 0x61; +static constexpr RegMask REG_0x61_Z1MOD = 0xff; +static constexpr RegAddr REG_0x62 = 0x62; +static constexpr RegMask REG_0x62_Z1MOD = 0xff; + +static constexpr RegAddr REG_0x63 = 0x63; +static constexpr RegMask REG_0x63_Z2MOD = 0x1f; +static constexpr RegAddr REG_0x64 = 0x64; +static constexpr RegMask REG_0x64_Z2MOD = 0xff; +static constexpr RegAddr REG_0x65 = 0x65; +static constexpr RegMask REG_0x65_Z2MOD = 0xff; + +static constexpr RegShift REG_0x60S_STEPSEL = 5; +static constexpr RegMask REG_0x60_STEPSEL = 0xe0; +static constexpr RegMask REG_0x60_FULLSTEP = 0x00; +static constexpr RegMask REG_0x60_HALFSTEP = 0x20; +static constexpr RegMask REG_0x60_EIGHTHSTEP = 0x60; +static constexpr RegMask REG_0x60_16THSTEP = 0x80; + +static constexpr RegShift REG_0x63S_FSTPSEL = 5; +static constexpr RegMask REG_0x63_FSTPSEL = 0xe0; +static constexpr RegMask REG_0x63_FULLSTEP = 0x00; +static constexpr RegMask REG_0x63_HALFSTEP = 0x20; +static constexpr RegMask REG_0x63_EIGHTHSTEP = 0x60; +static constexpr RegMask REG_0x63_16THSTEP = 0x80; + +static constexpr RegAddr REG_0x67 = 0x67; +static constexpr RegMask REG_0x67_MTRPWM = 0x80; + +static constexpr RegAddr REG_0x68 = 0x68; +static constexpr RegMask REG_0x68_FASTPWM = 0x80; + +static constexpr RegAddr REG_0x6B = 0x6b; +static constexpr RegMask REG_0x6B_MULTFILM = 0x80; +static constexpr RegMask REG_0x6B_GPOM13 = 0x40; +static constexpr RegMask REG_0x6B_GPOM12 = 0x20; +static constexpr RegMask REG_0x6B_GPOM11 = 0x10; +static constexpr RegMask REG_0x6B_GPO18 = 0x02; +static constexpr RegMask REG_0x6B_GPO17 = 0x01; + +static constexpr RegShift REG_0x6C = 0x6c; +static constexpr RegMask REG_0x6C_GPIO16 = 0x80; +static constexpr RegMask REG_0x6C_GPIO15 = 0x40; +static constexpr RegMask REG_0x6C_GPIO14 = 0x20; +static constexpr RegMask REG_0x6C_GPIO13 = 0x10; +static constexpr RegMask REG_0x6C_GPIO12 = 0x08; +static constexpr RegMask REG_0x6C_GPIO11 = 0x04; +static constexpr RegMask REG_0x6C_GPIO10 = 0x02; +static constexpr RegMask REG_0x6C_GPIO9 = 0x01; +static constexpr RegMask REG_0x6C_GPIOH = 0xff; +static constexpr RegMask REG_0x6C_GPIOL = 0xff; + +static constexpr RegAddr REG_0x6D = 0x6d; +static constexpr RegAddr REG_0x6E = 0x6e; +static constexpr RegAddr REG_0x6F = 0x6f; +static constexpr RegAddr REG_0x7E = 0x7e; + +static constexpr RegMask REG_0x87_LEDADD = 0x04; + +static constexpr RegAddr REG_0x9E = 0x9e; +static constexpr RegAddr REG_0x9F = 0x9f; + +static constexpr RegAddr REG_0xA6 = 0xa6; +static constexpr RegAddr REG_0xA7 = 0xa7; +static constexpr RegAddr REG_0xA8 = 0xa8; +static constexpr RegAddr REG_0xA9 = 0xa9; +static constexpr RegAddr REG_0xAB = 0xab; + +static constexpr RegAddr REG_EXPR = 0x10; +static constexpr RegAddr REG_EXPG = 0x12; +static constexpr RegAddr REG_EXPB = 0x14; +static constexpr RegAddr REG_EXPDMY = 0x19; +static constexpr RegAddr REG_STEPNO = 0x21; +static constexpr RegAddr REG_FWDSTEP = 0x22; +static constexpr RegAddr REG_BWDSTEP = 0x23; +static constexpr RegAddr REG_FASTNO = 0x24; +static constexpr RegAddr REG_DPISET = 0x2c; +static constexpr RegAddr REG_STRPIXEL = 0x30; +static constexpr RegAddr REG_ENDPIXEL = 0x32; +static constexpr RegAddr REG_LINCNT = 0x25; +static constexpr RegAddr REG_MAXWD = 0x35; +static constexpr RegAddr REG_LPERIOD = 0x38; +static constexpr RegAddr REG_FEEDL = 0x3d; +static constexpr RegAddr REG_FMOVDEC = 0x5f; +static constexpr RegAddr REG_FSHDEC = 0x69; +static constexpr RegAddr REG_FMOVNO = 0x6a; +static constexpr RegAddr REG_CK1MAP = 0x74; +static constexpr RegAddr REG_CK3MAP = 0x77; +static constexpr RegAddr REG_CK4MAP = 0x7a; + +} // namespace gl847 +} // namespace genesys + +#endif // BACKEND_GENESYS_GL847_REGISTERS_H diff --git a/backend/genesys/image.cpp b/backend/genesys/image.cpp new file mode 100644 index 0000000..7d386c6 --- /dev/null +++ b/backend/genesys/image.cpp @@ -0,0 +1,204 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "image.h" + +#include <array> + +namespace genesys { + +Image::Image() = default; + +Image::Image(std::size_t width, std::size_t height, PixelFormat format) : +    width_{width}, +    height_{height}, +    format_{format}, +    row_bytes_{get_pixel_row_bytes(format_, width_)} +{ +    data_.resize(get_row_bytes() * height); +} + +std::uint8_t* Image::get_row_ptr(std::size_t y) +{ +    return data_.data() + row_bytes_ * y; +} + +const std::uint8_t* Image::get_row_ptr(std::size_t y) const +{ +    return data_.data() + row_bytes_ * y; +} + +Pixel Image::get_pixel(std::size_t x, std::size_t y) const +{ +    return get_pixel_from_row(get_row_ptr(y), x, format_); +} + +void Image::set_pixel(std::size_t x, std::size_t y, const Pixel& pixel) +{ +    set_pixel_to_row(get_row_ptr(y), x, pixel, format_); +} + +RawPixel Image::get_raw_pixel(std::size_t x, std::size_t y) const +{ +    return get_raw_pixel_from_row(get_row_ptr(y), x, format_); +} + +std::uint16_t Image::get_raw_channel(std::size_t x, std::size_t y, unsigned channel) const +{ +    return get_raw_channel_from_row(get_row_ptr(y), x, channel, format_); +} + +void Image::set_raw_pixel(std::size_t x, std::size_t y, const RawPixel& pixel) +{ +    set_raw_pixel_to_row(get_row_ptr(y), x, pixel, format_); +} + +void Image::resize(std::size_t width, std::size_t height, PixelFormat format) +{ +    width_ = width; +    height_ = height; +    format_ = format; +    row_bytes_ = get_pixel_row_bytes(format_, width_); +    data_.resize(get_row_bytes() * height); +} + +template<PixelFormat SrcFormat, PixelFormat DstFormat> +void convert_pixel_row_impl2(const std::uint8_t* in_data, std::uint8_t* out_data, +                             std::size_t count) +{ +    for (std::size_t i = 0; i < count; ++i) { +        Pixel pixel = get_pixel_from_row(in_data, i, SrcFormat); +        set_pixel_to_row(out_data, i, pixel, DstFormat); +    } +} + +template<PixelFormat SrcFormat> +void convert_pixel_row_impl(const std::uint8_t* in_data, std::uint8_t* out_data, +                            PixelFormat out_format, std::size_t count) +{ +    switch (out_format) { +        case PixelFormat::I1: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::I1>(in_data, out_data, count); +            return; +        } +        case PixelFormat::RGB111: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::RGB111>(in_data, out_data, count); +            return; +        } +        case PixelFormat::I8: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::I8>(in_data, out_data, count); +            return; +        } +        case PixelFormat::RGB888: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::RGB888>(in_data, out_data, count); +            return; +        } +        case PixelFormat::BGR888: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::BGR888>(in_data, out_data, count); +            return; +        } +        case PixelFormat::I16: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::I16>(in_data, out_data, count); +            return; +        } +        case PixelFormat::RGB161616: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::RGB161616>(in_data, out_data, count); +            return; +        } +        case PixelFormat::BGR161616: { +            convert_pixel_row_impl2<SrcFormat, PixelFormat::BGR161616>(in_data, out_data, count); +            return; +        } +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(out_format)); +    } +} +void convert_pixel_row_format(const std::uint8_t* in_data, PixelFormat in_format, +                              std::uint8_t* out_data, PixelFormat out_format, std::size_t count) +{ +    if (in_format == out_format) { +        std::memcpy(out_data, in_data, get_pixel_row_bytes(in_format, count)); +        return; +    } + +    switch (in_format) { +        case PixelFormat::I1: { +            convert_pixel_row_impl<PixelFormat::I1>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::RGB111: { +            convert_pixel_row_impl<PixelFormat::RGB111>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::I8: { +            convert_pixel_row_impl<PixelFormat::I8>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::RGB888: { +            convert_pixel_row_impl<PixelFormat::RGB888>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::BGR888: { +            convert_pixel_row_impl<PixelFormat::BGR888>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::I16: { +            convert_pixel_row_impl<PixelFormat::I16>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::RGB161616: { +            convert_pixel_row_impl<PixelFormat::RGB161616>(in_data, out_data, out_format, count); +            return; +        } +        case PixelFormat::BGR161616: { +            convert_pixel_row_impl<PixelFormat::BGR161616>(in_data, out_data, out_format, count); +            return; +        } +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(in_format)); +    } +} + +} // namespace genesys diff --git a/backend/genesys/image.h b/backend/genesys/image.h new file mode 100644 index 0000000..c96b1bb --- /dev/null +++ b/backend/genesys/image.h @@ -0,0 +1,87 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_H +#define BACKEND_GENESYS_IMAGE_H + +#include "image_pixel.h" +#include <vector> + +namespace genesys { + +class Image +{ +public: +    Image(); +    Image(std::size_t width, std::size_t height, PixelFormat format); + +    std::size_t get_width() const { return width_; } +    std::size_t get_height() const { return height_; } +    PixelFormat get_format() const { return format_; } +    std::size_t get_row_bytes() const { return row_bytes_; } + +    std::uint8_t* get_row_ptr(std::size_t y); +    const std::uint8_t* get_row_ptr(std::size_t y) const; + +    Pixel get_pixel(std::size_t x, std::size_t y) const; +    void set_pixel(std::size_t x, std::size_t y, const Pixel& pixel); + +    RawPixel get_raw_pixel(std::size_t x, std::size_t y) const; +    std::uint16_t get_raw_channel(std::size_t x, std::size_t y, unsigned channel) const; +    void set_raw_pixel(std::size_t x, std::size_t y, const RawPixel& pixel); + +    void resize(std::size_t width, std::size_t height, PixelFormat format); +private: +    std::size_t width_ = 0; +    std::size_t height_ = 0; +    PixelFormat format_ = PixelFormat::UNKNOWN; +    std::size_t row_bytes_ = 0; +    std::vector<std::uint8_t> data_; +}; + +void convert_pixel_row_format(const std::uint8_t* in_data, PixelFormat in_format, +                              std::uint8_t* out_data, PixelFormat out_format, std::size_t count); + +} // namespace genesys + +#endif // ifndef BACKEND_GENESYS_IMAGE_H diff --git a/backend/genesys/image_buffer.cpp b/backend/genesys/image_buffer.cpp new file mode 100644 index 0000000..07c6987 --- /dev/null +++ b/backend/genesys/image_buffer.cpp @@ -0,0 +1,203 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "image_buffer.h" +#include "image.h" + +namespace genesys { + +ImageBuffer::ImageBuffer(std::size_t size, ProducerCallback producer) : +    producer_{producer}, +    size_{size}, +    buffer_offset_{size} +{ +    buffer_.resize(size_); +} + +bool ImageBuffer::get_data(std::size_t size, std::uint8_t* out_data) +{ +    const std::uint8_t* out_data_end = out_data + size; + +    auto copy_buffer = [&]() +    { +        std::size_t bytes_copy = std::min<std::size_t>(out_data_end - out_data, available()); +        std::memcpy(out_data, buffer_.data() + buffer_offset_, bytes_copy); +        out_data += bytes_copy; +        buffer_offset_ += bytes_copy; +    }; + +    // first, read remaining data from buffer +    if (available() > 0) { +        copy_buffer(); +    } + +    if (out_data == out_data_end) { +        return true; +    } + +    // now the buffer is empty and there's more data to be read +    bool got_data = true; +    do { +        buffer_offset_ = 0; +        got_data &= producer_(size_, buffer_.data()); + +        copy_buffer(); +    } while(out_data < out_data_end && got_data); + +    return got_data; +} + +void FakeBufferModel::push_step(std::size_t buffer_size, std::size_t row_bytes) +{ +    sizes_.push_back(buffer_size); +    available_sizes_.push_back(0); +    row_bytes_.push_back(row_bytes); +} + +std::size_t FakeBufferModel::available_space() const +{ +    if (sizes_.empty()) +        throw SaneException("Model has not been setup"); +    return sizes_.front() - available_sizes_.front(); +} + +void FakeBufferModel::simulate_read(std::size_t size) +{ +    if (sizes_.empty()) { +        throw SaneException("Model has not been setup"); +    } +    if (available_space() < size) { +        throw SaneException("Attempted to simulate read of too much memory"); +    } + +    available_sizes_.front() += size; + +    for (unsigned i = 1; i < sizes_.size(); ++i) { +        auto avail_src = available_sizes_[i - 1]; +        auto avail_dst = sizes_[i] - available_sizes_[i]; + +        auto avail = (std::min(avail_src, avail_dst) / row_bytes_[i]) * row_bytes_[i]; +        available_sizes_[i - 1] -= avail; +        available_sizes_[i] += avail; +    } +    available_sizes_.back() = 0; +} + +ImageBufferGenesysUsb::ImageBufferGenesysUsb(std::size_t total_size, +                                             const FakeBufferModel& buffer_model, +                                             ProducerCallback producer) : +    remaining_size_{total_size}, +    buffer_model_{buffer_model}, +    producer_{producer} +{} + +bool ImageBufferGenesysUsb::get_data(std::size_t size, std::uint8_t* out_data) +{ +    const std::uint8_t* out_data_end = out_data + size; + +    auto copy_buffer = [&]() +    { +        std::size_t bytes_copy = std::min<std::size_t>(out_data_end - out_data, available()); +        std::memcpy(out_data, buffer_.data() + buffer_offset_, bytes_copy); +        out_data += bytes_copy; +        buffer_offset_ += bytes_copy; +    }; + +    // first, read remaining data from buffer +    if (available() > 0) { +        copy_buffer(); +    } + +    if (out_data == out_data_end) { +        return true; +    } + +    // now the buffer is empty and there's more data to be read +    do { +        if (remaining_size_ == 0) +            return false; + +        auto bytes_to_read = get_read_size(); +        buffer_offset_ = 0; +        buffer_end_ = bytes_to_read; +        buffer_.resize(bytes_to_read); + +        producer_(bytes_to_read, buffer_.data()); + +        if (remaining_size_ < bytes_to_read) { +            remaining_size_ = 0; +        } else { +            remaining_size_ -= bytes_to_read; +        } + +        copy_buffer(); +    } while(out_data < out_data_end); +    return true; +} + +std::size_t ImageBufferGenesysUsb::get_read_size() +{ +    std::size_t size = buffer_model_.available_space(); + +    // never read an odd number. exception: last read +    // the chip internal counter does not count half words. +    size &= ~1; + +    // Some setups need the reads to be multiples of 256 bytes +    size &= ~0xff; + +    if (remaining_size_ < size) { +        size = remaining_size_; +        /*round up to a multiple of 256 bytes */ +        size += (size & 0xff) ? 0x100 : 0x00; +        size &= ~0xff; +    } + +    buffer_model_.simulate_read(size); + +    return size; +} + +} // namespace genesys diff --git a/backend/genesys/image_buffer.h b/backend/genesys/image_buffer.h new file mode 100644 index 0000000..43c3eb7 --- /dev/null +++ b/backend/genesys/image_buffer.h @@ -0,0 +1,129 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_BUFFER_H +#define BACKEND_GENESYS_IMAGE_BUFFER_H + +#include "enums.h" +#include "row_buffer.h" +#include <algorithm> +#include <functional> + +namespace genesys { + +// This class allows reading from row-based source in smaller or larger chunks of data +class ImageBuffer +{ +public: +    using ProducerCallback = std::function<bool(std::size_t size, std::uint8_t* out_data)>; + +    ImageBuffer() {} +    ImageBuffer(std::size_t size, ProducerCallback producer); + +    std::size_t size() const { return size_; } +    std::size_t available() const { return size_ - buffer_offset_; } + +    bool get_data(std::size_t size, std::uint8_t* out_data); + +private: +    ProducerCallback producer_; +    std::size_t size_ = 0; + +    std::size_t buffer_offset_ = 0; +    std::vector<std::uint8_t> buffer_; +}; + +class FakeBufferModel +{ +public: +    FakeBufferModel() {} + +    void push_step(std::size_t buffer_size, std::size_t row_bytes); + +    std::size_t available_space() const; + +    void simulate_read(std::size_t size); + +private: +    std::vector<std::size_t> sizes_; +    std::vector<std::size_t> available_sizes_; +    std::vector<std::size_t> row_bytes_; +}; + +// This class is similar to ImageBuffer, but preserves historical peculiarities of buffer handling +// in the backend to preserve exact behavior +class ImageBufferGenesysUsb +{ +public: +    using ProducerCallback = std::function<void(std::size_t size, std::uint8_t* out_data)>; + +    ImageBufferGenesysUsb() {} +    ImageBufferGenesysUsb(std::size_t total_size, const FakeBufferModel& buffer_model, +                          ProducerCallback producer); + +    std::size_t remaining_size() const { return remaining_size_; } + +    void set_remaining_size(std::size_t bytes) { remaining_size_ = bytes; } + +    std::size_t available() const { return buffer_end_ - buffer_offset_; } + +    bool get_data(std::size_t size, std::uint8_t* out_data); + +private: + +    std::size_t get_read_size(); + +    std::size_t remaining_size_ = 0; + +    std::size_t buffer_offset_ = 0; +    std::size_t buffer_end_ = 0; +    std::vector<std::uint8_t> buffer_; + +    FakeBufferModel buffer_model_; + +    ProducerCallback producer_; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_IMAGE_BUFFER_H diff --git a/backend/genesys/image_pipeline.cpp b/backend/genesys/image_pipeline.cpp new file mode 100644 index 0000000..c01b7f4 --- /dev/null +++ b/backend/genesys/image_pipeline.cpp @@ -0,0 +1,839 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "image_pipeline.h" +#include "image.h" +#include "low.h" +#include <cmath> +#include <numeric> + +namespace genesys { + +ImagePipelineNode::~ImagePipelineNode() {} + +std::size_t ImagePipelineNodeBytesSource::consume_remaining_bytes(std::size_t bytes) +{ +    if (bytes > remaining_bytes_) { +        bytes = remaining_bytes_; +    } +    remaining_bytes_ -= bytes; +    return bytes; +} + +bool ImagePipelineNodeCallableSource::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = producer_(get_row_bytes(), out_data); +    if (!got_data) +        eof_ = true; +    return got_data; +} + +ImagePipelineNodeBufferedCallableSource::ImagePipelineNodeBufferedCallableSource( +        std::size_t width, std::size_t height, PixelFormat format, std::size_t input_batch_size, +        ProducerCallback producer) : +    width_{width}, +    height_{height}, +    format_{format}, +    buffer_{input_batch_size, producer} +{ +    set_remaining_bytes(height_ * get_row_bytes()); +} + +bool ImagePipelineNodeBufferedCallableSource::get_next_row_data(std::uint8_t* out_data) +{ +    if (curr_row_ >= get_height()) { +        DBG(DBG_warn, "%s: reading out of bounds. Row %zu, height: %zu\n", __func__, +            curr_row_, get_height()); +        eof_ = true; +        return false; +    } + +    bool got_data = true; + +    auto row_bytes = get_row_bytes(); +    auto bytes_to_ask = consume_remaining_bytes(row_bytes); +    if (bytes_to_ask < row_bytes) { +        got_data = false; +    } + +    got_data &= buffer_.get_data(bytes_to_ask, out_data); +    curr_row_++; +    if (!got_data) { +        eof_ = true; +    } +    return got_data; +} + + +ImagePipelineNodeBufferedGenesysUsb::ImagePipelineNodeBufferedGenesysUsb( +        std::size_t width, std::size_t height, PixelFormat format, std::size_t total_size, +        const FakeBufferModel& buffer_model, ProducerCallback producer) : +    width_{width}, +    height_{height}, +    format_{format}, +    buffer_{total_size, buffer_model, producer} +{ +    set_remaining_bytes(total_size); +} + +bool ImagePipelineNodeBufferedGenesysUsb::get_next_row_data(std::uint8_t* out_data) +{ +    if (remaining_bytes() != buffer_.remaining_size() + buffer_.available()) { +        buffer_.set_remaining_size(remaining_bytes() - buffer_.available()); +    } +    bool got_data = true; + +    std::size_t row_bytes = get_row_bytes(); +    std::size_t ask_bytes = consume_remaining_bytes(row_bytes); +    if (ask_bytes < row_bytes) { +        got_data = false; +    } +    got_data &= buffer_.get_data(ask_bytes, out_data); +    if (!got_data) { +        eof_ = true; +    } +    return got_data; +} + +ImagePipelineNodeArraySource::ImagePipelineNodeArraySource(std::size_t width, std::size_t height, +                                                           PixelFormat format, +                                                           std::vector<std::uint8_t> data) : +    width_{width}, +    height_{height}, +    format_{format}, +    data_{std::move(data)}, +    next_row_{0} +{ +    auto size = get_row_bytes() * height_; +    if (data_.size() < size) { +        throw SaneException("The given array is too small (%zu bytes). Need at least %zu", +                            data_.size(), size); +    } +    set_remaining_bytes(size); +} + +bool ImagePipelineNodeArraySource::get_next_row_data(std::uint8_t* out_data) +{ +    if (next_row_ >= height_) { +        eof_ = true; +        return false; +    } + +    bool got_data = true; + +    auto row_bytes = get_row_bytes(); +    auto bytes_to_ask = consume_remaining_bytes(row_bytes); +    if (bytes_to_ask < row_bytes) { +        got_data = false; +    } + +    std::memcpy(out_data, data_.data() + get_row_bytes() * next_row_, bytes_to_ask); +    next_row_++; + +    if (!got_data) { +        eof_ = true; +    } +    return got_data; +} + + +ImagePipelineNodeImageSource::ImagePipelineNodeImageSource(const Image& source) : +    source_{source} +{} + +bool ImagePipelineNodeImageSource::get_next_row_data(std::uint8_t* out_data) +{ +    if (next_row_ >= get_height()) { +        return false; +    } +    std::memcpy(out_data, source_.get_row_ptr(next_row_), get_row_bytes()); +    next_row_++; +    return true; +} + +bool ImagePipelineNodeFormatConvert::get_next_row_data(std::uint8_t* out_data) +{ +    auto src_format = source_.get_format(); +    if (src_format == dst_format_) { +        return source_.get_next_row_data(out_data); +    } + +    buffer_.clear(); +    buffer_.resize(source_.get_row_bytes()); +    bool got_data = source_.get_next_row_data(buffer_.data()); + +    convert_pixel_row_format(buffer_.data(), src_format, out_data, dst_format_, get_width()); +    return got_data; +} + +ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source, +                                                       std::size_t output_width, +                                                       const std::vector<unsigned>& segment_order, +                                                       std::size_t segment_pixels, +                                                       std::size_t interleaved_lines, +                                                       std::size_t pixels_per_chunk) : +    source_(source), +    output_width_{output_width}, +    segment_order_{segment_order}, +    segment_pixels_{segment_pixels}, +    interleaved_lines_{interleaved_lines}, +    pixels_per_chunk_{pixels_per_chunk}, +    buffer_{source_.get_row_bytes()} +{ +    DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, " +                         "pixels_per_shunk=%zu", segment_order.size(), segment_pixels, +                    interleaved_lines, pixels_per_chunk); + +    if (source_.get_height() % interleaved_lines_ > 0) { +        throw SaneException("Height is not a multiple of the number of lines to interelave %zu/%zu", +                            source_.get_height(), interleaved_lines_); +    } +} + +ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source, +                                                       std::size_t output_width, +                                                       std::size_t segment_count, +                                                       std::size_t segment_pixels, +                                                       std::size_t interleaved_lines, +                                                       std::size_t pixels_per_chunk) : +    source_(source), +    output_width_{output_width}, +    segment_pixels_{segment_pixels}, +    interleaved_lines_{interleaved_lines}, +    pixels_per_chunk_{pixels_per_chunk}, +    buffer_{source_.get_row_bytes()} +{ +    DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, " +                    "pixels_per_shunk=%zu", segment_count, segment_pixels, interleaved_lines, +                    pixels_per_chunk); + +    segment_order_.resize(segment_count); +    std::iota(segment_order_.begin(), segment_order_.end(), 0); +} + +bool ImagePipelineNodeDesegment::get_next_row_data(uint8_t* out_data) +{ +    bool got_data = true; + +    buffer_.clear(); +    for (std::size_t i = 0; i < interleaved_lines_; ++i) { +        buffer_.push_back(); +        got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i)); +    } +    if (!buffer_.is_linear()) { +        throw SaneException("Buffer is not linear"); +    } + +    auto format = get_format(); +    auto segment_count = segment_order_.size(); + +    const std::uint8_t* in_data = buffer_.get_row_ptr(0); + +    std::size_t groups_count = output_width_ / (segment_order_.size() * pixels_per_chunk_); + +    for (std::size_t igroup = 0; igroup < groups_count; ++igroup) { +        for (std::size_t isegment = 0; isegment < segment_count; ++isegment) { +            auto input_offset = igroup * pixels_per_chunk_; +            input_offset += segment_pixels_ * segment_order_[isegment]; +            auto output_offset = (igroup * segment_count + isegment) * pixels_per_chunk_; + +            for (std::size_t ipixel = 0; ipixel < pixels_per_chunk_; ++ipixel) { +                auto pixel = get_raw_pixel_from_row(in_data, input_offset + ipixel, format); +                set_raw_pixel_to_row(out_data, output_offset + ipixel, pixel, format); +            } +        } +    } +    return got_data; +} + +ImagePipelineNodeDeinterleaveLines::ImagePipelineNodeDeinterleaveLines( +        ImagePipelineNode& source, std::size_t interleaved_lines, std::size_t pixels_per_chunk) : +    ImagePipelineNodeDesegment(source, source.get_width() * interleaved_lines, +                               interleaved_lines, source.get_width(), +                               interleaved_lines, pixels_per_chunk) +{} + +ImagePipelineNodeSwap16BitEndian::ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source) : +    source_(source), +    needs_swapping_{false} +{ +    if (get_pixel_format_depth(source_.get_format()) == 16) { +        needs_swapping_ = true; +    } else { +        DBG(DBG_info, "%s: this pipeline node does nothing for non 16-bit formats", __func__); +    } +} + +bool ImagePipelineNodeSwap16BitEndian::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = source_.get_next_row_data(out_data); +    if (needs_swapping_) { +        std::size_t pixels = get_row_bytes() / 2; +        for (std::size_t i = 0; i < pixels; ++i) { +            std::swap(*out_data, *(out_data + 1)); +            out_data += 2; +        } +    } +    return got_data; +} + +ImagePipelineNodeMergeMonoLines::ImagePipelineNodeMergeMonoLines(ImagePipelineNode& source, +                                                                 ColorOrder color_order) : +    source_(source), +    buffer_(source_.get_row_bytes()) +{ +    DBG_HELPER_ARGS(dbg, "color_order %d", static_cast<unsigned>(color_order)); + +    output_format_ = get_output_format(source_.get_format(), color_order); +} + +bool ImagePipelineNodeMergeMonoLines::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = true; + +    buffer_.clear(); +    for (unsigned i = 0; i < 3; ++i) { +        buffer_.push_back(); +        got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i)); +    } + +    const auto* row0 = buffer_.get_row_ptr(0); +    const auto* row1 = buffer_.get_row_ptr(1); +    const auto* row2 = buffer_.get_row_ptr(2); + +    auto format = source_.get_format(); + +    for (std::size_t x = 0, width = get_width(); x < width; ++x) { +        std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format); +        std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 0, format); +        std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 0, format); +        set_raw_channel_to_row(out_data, x, 0, ch0, output_format_); +        set_raw_channel_to_row(out_data, x, 1, ch1, output_format_); +        set_raw_channel_to_row(out_data, x, 2, ch2, output_format_); +    } +    return got_data; +} + +PixelFormat ImagePipelineNodeMergeMonoLines::get_output_format(PixelFormat input_format, +                                                               ColorOrder order) +{ +    switch (input_format) { +        case PixelFormat::I1: { +            if (order == ColorOrder::RGB) { +                return PixelFormat::RGB111; +            } +            break; +        } +        case PixelFormat::I8: { +            if (order == ColorOrder::RGB) { +                return PixelFormat::RGB888; +            } +            if (order == ColorOrder::BGR) { +                return PixelFormat::BGR888; +            } +            break; +        } +        case PixelFormat::I16: { +            if (order == ColorOrder::RGB) { +                return PixelFormat::RGB161616; +            } +            if (order == ColorOrder::BGR) { +                return PixelFormat::BGR161616; +            } +            break; +        } +        default: break; +    } +    throw SaneException("Unsupported format combidation %d %d", +                        static_cast<unsigned>(input_format), +                        static_cast<unsigned>(order)); +} + +ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) : +    source_(source), +    next_channel_{0} +{ +    output_format_ = get_output_format(source_.get_format()); +} + +bool ImagePipelineNodeSplitMonoLines::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = true; + +    if (next_channel_ == 0) { +        buffer_.resize(source_.get_row_bytes()); +        got_data &= source_.get_next_row_data(buffer_.data()); +    } + +    const auto* row = buffer_.data(); +    auto format = source_.get_format(); + +    for (std::size_t x = 0, width = get_width(); x < width; ++x) { +        std::uint16_t ch = get_raw_channel_from_row(row, x, next_channel_, format); +        set_raw_channel_to_row(out_data, x, 0, ch, output_format_); +    } +    next_channel_ = (next_channel_ + 1) % 3; + +    return got_data; +} + +PixelFormat ImagePipelineNodeSplitMonoLines::get_output_format(PixelFormat input_format) +{ +    switch (input_format) { +        case PixelFormat::RGB111: return PixelFormat::I1; +        case PixelFormat::RGB888: +        case PixelFormat::BGR888: return PixelFormat::I8; +        case PixelFormat::RGB161616: +        case PixelFormat::BGR161616: return PixelFormat::I16; +        default: break; +    } +    throw SaneException("Unsupported input format %d", static_cast<unsigned>(input_format)); +} + +ImagePipelineNodeComponentShiftLines::ImagePipelineNodeComponentShiftLines( +        ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b) : +    source_(source), +    buffer_{source.get_row_bytes()} +{ +    DBG_HELPER_ARGS(dbg, "shifts={%d, %d, %d}", shift_r, shift_g, shift_b); + +    switch (source.get_format()) { +        case PixelFormat::RGB111: +        case PixelFormat::RGB888: +        case PixelFormat::RGB161616: { +            channel_shifts_ = { shift_r, shift_g, shift_b }; +            break; +        } +        case PixelFormat::BGR888: +        case PixelFormat::BGR161616: { +            channel_shifts_ = { shift_b, shift_g, shift_r }; +            break; +        } +        default: +            throw SaneException("Unsupported input format %d", +                                static_cast<unsigned>(source.get_format())); +    } +    extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end()); +} + +bool ImagePipelineNodeComponentShiftLines::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = true; + +    if (!buffer_.empty()) { +        buffer_.pop_front(); +    } +    while (buffer_.height() < extra_height_ + 1) { +        buffer_.push_back(); +        got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr()); +    } + +    auto format = get_format(); +    const auto* row0 = buffer_.get_row_ptr(channel_shifts_[0]); +    const auto* row1 = buffer_.get_row_ptr(channel_shifts_[1]); +    const auto* row2 = buffer_.get_row_ptr(channel_shifts_[2]); + +    for (std::size_t x = 0, width = get_width(); x < width; ++x) { +        std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format); +        std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 1, format); +        std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 2, format); +        set_raw_channel_to_row(out_data, x, 0, ch0, format); +        set_raw_channel_to_row(out_data, x, 1, ch1, format); +        set_raw_channel_to_row(out_data, x, 2, ch2, format); +    } +    return got_data; +} + +ImagePipelineNodePixelShiftLines::ImagePipelineNodePixelShiftLines( +        ImagePipelineNode& source, const std::vector<std::size_t>& shifts) : +    source_(source), +    pixel_shifts_{shifts}, +    buffer_{get_row_bytes()} +{ +    DBG_HELPER(dbg); +    DBG(DBG_proc, "%s: shifts={", __func__); +    for (auto el : pixel_shifts_) { +        DBG(DBG_proc, " %zu", el); +    } +    DBG(DBG_proc, " }\n"); + +    if (pixel_shifts_.size() > MAX_SHIFTS) { +        throw SaneException("Unsupported number of shift configurations %zu", pixel_shifts_.size()); +    } + +    extra_height_ = *std::max_element(pixel_shifts_.begin(), pixel_shifts_.end()); +} + +bool ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = true; + +    if (!buffer_.empty()) { +        buffer_.pop_front(); +    } +    while (buffer_.height() < extra_height_ + 1) { +        buffer_.push_back(); +        got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr()); +    } + +    auto format = get_format(); +    auto shift_count = pixel_shifts_.size(); + +    std::array<std::uint8_t*, MAX_SHIFTS> rows; + +    for (std::size_t irow = 0; irow < shift_count; ++irow) { +        rows[irow] = buffer_.get_row_ptr(pixel_shifts_[irow]); +    } + +    for (std::size_t x = 0, width = get_width(); x < width;) { +        for (std::size_t irow = 0; irow < shift_count && x < width; irow++, x++) { +            RawPixel pixel = get_raw_pixel_from_row(rows[irow], x, format); +            set_raw_pixel_to_row(out_data, x, pixel, format); +        } +    } +    return got_data; +} + +ImagePipelineNodeExtract::ImagePipelineNodeExtract(ImagePipelineNode& source, +                                                   std::size_t offset_x, std::size_t offset_y, +                                                   std::size_t width, std::size_t height) : +    source_(source), +    offset_x_{offset_x}, +    offset_y_{offset_y}, +    width_{width}, +    height_{height} +{ +    cached_line_.resize(source_.get_row_bytes()); +} + +ImagePipelineNodeExtract::~ImagePipelineNodeExtract() {} + +ImagePipelineNodeScaleRows::ImagePipelineNodeScaleRows(ImagePipelineNode& source, +                                                       std::size_t width) : +    source_(source), +    width_{width} +{ +    cached_line_.resize(source_.get_row_bytes()); +} + +bool ImagePipelineNodeScaleRows::get_next_row_data(std::uint8_t* out_data) +{ +    auto src_width = source_.get_width(); +    auto dst_width = width_; + +    bool got_data = source_.get_next_row_data(cached_line_.data()); + +    const auto* src_data = cached_line_.data(); +    auto format = get_format(); +    auto channels = get_pixel_channels(format); + +    if (src_width > dst_width) { +        // average +        std::uint32_t counter = src_width / 2; +        unsigned src_x = 0; +        for (unsigned dst_x = 0; dst_x < dst_width; dst_x++) { +            unsigned avg[3] = {0, 0, 0}; +            unsigned count = 0; +            while (counter < src_width && src_x < src_width) { +                counter += dst_width; + +                for (unsigned c = 0; c < channels; c++) { +                    avg[c] += get_raw_channel_from_row(src_data, src_x, c, format); +                } + +                src_x++; +                count++; +            } +            counter -= src_width; + +            for (unsigned c = 0; c < channels; c++) { +                set_raw_channel_to_row(out_data, dst_x, c, avg[c] / count, format); +            } +        } +    } else { +        // interpolate and copy pixels +        std::uint32_t counter = dst_width / 2; +        unsigned dst_x = 0; + +        for (unsigned src_x = 0; src_x < src_width; src_x++) { +            unsigned avg[3] = {0, 0, 0}; +            for (unsigned c = 0; c < channels; c++) { +                avg[c] += get_raw_channel_from_row(src_data, src_x, c, format); +            } +            while ((counter < dst_width || src_x + 1 == src_width) && dst_x < dst_width) { +                counter += src_width; + +                for (unsigned c = 0; c < channels; c++) { +                    set_raw_channel_to_row(out_data, dst_x, c, avg[c], format); +                } +                dst_x++; +            } +            counter -= dst_width; +        } +    } +    return got_data; +} + +bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data) +{ +    bool got_data = true; + +    while (current_line_ < offset_y_) { +        got_data &= source_.get_next_row_data(cached_line_.data()); +        current_line_++; +    } +    if (current_line_ >= offset_y_ + source_.get_height()) { +        std::fill(out_data, out_data + get_row_bytes(), 0); +        current_line_++; +        return got_data; +    } +    // now we're sure that the following holds: +    // offset_y_ <= current_line_ < offset_y_ + source_.get_height()) +    got_data &= source_.get_next_row_data(cached_line_.data()); + +    auto format = get_format(); +    auto x_src_width = source_.get_width() > offset_x_ ? source_.get_width() - offset_x_ : 0; +    x_src_width = std::min(x_src_width, width_); +    auto x_pad_after = width_ > x_src_width ? width_ - x_src_width : 0; + +    if (get_pixel_format_depth(format) < 8) { +        // we need to copy pixels one-by-one as there's no per-bit addressing +        for (std::size_t i = 0; i < x_src_width; ++i) { +            auto pixel = get_raw_pixel_from_row(cached_line_.data(), i + offset_x_, format); +            set_raw_pixel_to_row(out_data, i, pixel, format); +        } +        for (std::size_t i = 0; i < x_pad_after; ++i) { +            set_raw_pixel_to_row(out_data, i + x_src_width, RawPixel{}, format); +        } +    } else { +        std::size_t bpp = get_pixel_format_depth(format) / 8; +        if (x_src_width > 0) { +            std::memcpy(out_data, cached_line_.data() + offset_x_ * bpp, +                        x_src_width * bpp); +        } +        if (x_pad_after > 0) { +            std::fill(out_data + x_src_width * bpp, +                      out_data + (x_src_width + x_pad_after) * bpp, 0); +        } +    } + +    current_line_++; + +    return got_data; +} + +ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source, +                                                       const std::vector<std::uint16_t>& bottom, +                                                       const std::vector<std::uint16_t>& top) : +    source_{source} +{ +    auto size = std::min(bottom.size(), top.size()); +    offset_.reserve(size); +    multiplier_.reserve(size); + +    for (std::size_t i = 0; i < size; ++i) { +        offset_.push_back(bottom[i] / 65535.0f); +        multiplier_.push_back(65535.0f / (top[i] - bottom[i])); +    } +} + +bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data) +{ +    bool ret = source_.get_next_row_data(out_data); + +    auto format = get_format(); +    auto depth = get_pixel_format_depth(format); +    std::size_t max_value = 1; +    switch (depth) { +        case 8: max_value = 255; break; +        case 16: max_value = 65535; break; +        default: +            throw SaneException("Unsupported depth for calibration %d", depth); +    } +    unsigned channels = get_pixel_channels(format); + +    std::size_t max_calib_i = offset_.size(); +    std::size_t curr_calib_i = 0; + +    for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) { +        for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) { +            std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format); + +            float value_f = static_cast<float>(value) / max_value; +            value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i]; +            value_f = std::round(value_f * max_value); +            value = clamp<std::int32_t>(static_cast<std::int32_t>(value_f), 0, max_value); +            set_raw_channel_to_row(out_data, x, ch, value, format); + +            curr_calib_i++; +        } +    } +    return ret; +} + +ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source, +                                               const std::string& path) : +    source_(source), +    path_{path}, +    buffer_{source_.get_row_bytes()} +{} + +ImagePipelineNodeDebug::~ImagePipelineNodeDebug() +{ +    catch_all_exceptions(__func__, [&]() +    { +        if (buffer_.empty()) +            return; + +        auto format = get_format(); +        buffer_.linearize(); +        sanei_genesys_write_pnm_file(path_.c_str(), buffer_.get_front_row_ptr(), +                                     get_pixel_format_depth(format), +                                     get_pixel_channels(format), +                                     get_width(), buffer_.height()); +    }); +} + +bool ImagePipelineNodeDebug::get_next_row_data(std::uint8_t* out_data) +{ +    buffer_.push_back(); +    bool got_data = source_.get_next_row_data(out_data); +    std::memcpy(buffer_.get_back_row_ptr(), out_data, get_row_bytes()); +    return got_data; +} + +std::size_t ImagePipelineStack::get_input_width() const +{ +    ensure_node_exists(); +    return nodes_.front()->get_width(); +} + +std::size_t ImagePipelineStack::get_input_height() const +{ +    ensure_node_exists(); +    return nodes_.front()->get_height(); +} + +PixelFormat ImagePipelineStack::get_input_format() const +{ +    ensure_node_exists(); +    return nodes_.front()->get_format(); +} + +std::size_t ImagePipelineStack::get_input_row_bytes() const +{ +    ensure_node_exists(); +    return nodes_.front()->get_row_bytes(); +} + +std::size_t ImagePipelineStack::get_output_width() const +{ +    ensure_node_exists(); +    return nodes_.back()->get_width(); +} + +std::size_t ImagePipelineStack::get_output_height() const +{ +    ensure_node_exists(); +    return nodes_.back()->get_height(); +} + +PixelFormat ImagePipelineStack::get_output_format() const +{ +    ensure_node_exists(); +    return nodes_.back()->get_format(); +} + +std::size_t ImagePipelineStack::get_output_row_bytes() const +{ +    ensure_node_exists(); +    return nodes_.back()->get_row_bytes(); +} + +void ImagePipelineStack::ensure_node_exists() const +{ +    if (nodes_.empty()) { +        throw SaneException("The pipeline does not contain any nodes"); +    } +} + +void ImagePipelineStack::clear() +{ +    // we need to destroy the nodes back to front, so that the destructors still have valid +    // references to sources +    for (auto it = nodes_.rbegin(); it != nodes_.rend(); ++it) { +        it->reset(); +    } +    nodes_.clear(); +} + +std::vector<std::uint8_t> ImagePipelineStack::get_all_data() +{ +    auto row_bytes = get_output_row_bytes(); +    auto height = get_output_height(); + +    std::vector<std::uint8_t> ret; +    ret.resize(row_bytes * height); + +    for (std::size_t i = 0; i < height; ++i) { +        get_next_row_data(ret.data() + row_bytes * i); +    } +    return ret; +} + +Image ImagePipelineStack::get_image() +{ +    auto height = get_output_height(); + +    Image ret; +    ret.resize(get_output_width(), height, get_output_format()); + +    for (std::size_t i = 0; i < height; ++i) { +        get_next_row_data(ret.get_row_ptr(i)); +    } +    return ret; +} + +} // namespace genesys diff --git a/backend/genesys/image_pipeline.h b/backend/genesys/image_pipeline.h new file mode 100644 index 0000000..2986837 --- /dev/null +++ b/backend/genesys/image_pipeline.h @@ -0,0 +1,572 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H +#define BACKEND_GENESYS_IMAGE_PIPELINE_H + +#include "image.h" +#include "image_pixel.h" +#include "image_buffer.h" + +#include <algorithm> +#include <functional> +#include <memory> + +namespace genesys { + +class ImagePipelineNode +{ +public: +    virtual ~ImagePipelineNode(); + +    virtual std::size_t get_width() const = 0; +    virtual std::size_t get_height() const = 0; +    virtual PixelFormat get_format() const = 0; + +    std::size_t get_row_bytes() const +    { +        return get_pixel_row_bytes(get_format(), get_width()); +    } + +    virtual bool eof() const = 0; + +    // returns true if the row was filled successfully, false otherwise (e.g. if not enough data +    // was available. +    virtual bool get_next_row_data(std::uint8_t* out_data) = 0; +}; + +class ImagePipelineNodeBytesSource : public ImagePipelineNode +{ +public: +    std::size_t remaining_bytes() const { return remaining_bytes_; } +    void set_remaining_bytes(std::size_t bytes) { remaining_bytes_ = bytes; } + +    std::size_t consume_remaining_bytes(std::size_t bytes); + +private: +    std::size_t remaining_bytes_ = 0; +}; + +// A pipeline node that produces data from a callable +class ImagePipelineNodeCallableSource : public ImagePipelineNode +{ +public: +    using ProducerCallback = std::function<bool(std::size_t size, std::uint8_t* out_data)>; + +    ImagePipelineNodeCallableSource(std::size_t width, std::size_t height, PixelFormat format, +                                    ProducerCallback producer) : +        producer_{producer}, +        width_{width}, +        height_{height}, +        format_{format} +    {} + +    std::size_t get_width() const override { return width_; } +    std::size_t get_height() const override { return height_; } +    PixelFormat get_format() const override { return format_; } + +    bool eof() const override { return eof_; } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ProducerCallback producer_; +    std::size_t width_ = 0; +    std::size_t height_ = 0; +    PixelFormat format_ = PixelFormat::UNKNOWN; +    bool eof_ = false; +}; + +// A pipeline node that produces data from a callable requesting fixed-size chunks. +class ImagePipelineNodeBufferedCallableSource : public ImagePipelineNodeBytesSource +{ +public: +    using ProducerCallback = std::function<bool(std::size_t size, std::uint8_t* out_data)>; + +    ImagePipelineNodeBufferedCallableSource(std::size_t width, std::size_t height, +                                            PixelFormat format, std::size_t input_batch_size, +                                            ProducerCallback producer); + +    std::size_t get_width() const override { return width_; } +    std::size_t get_height() const override { return height_; } +    PixelFormat get_format() const override { return format_; } + +    bool eof() const override { return eof_; } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +    std::size_t buffer_size() const { return buffer_.size(); } +    std::size_t buffer_available() const { return buffer_.available(); } + +private: +    ProducerCallback producer_; +    std::size_t width_ = 0; +    std::size_t height_ = 0; +    PixelFormat format_ = PixelFormat::UNKNOWN; + +    bool eof_ = false; +    std::size_t curr_row_ = 0; + +    ImageBuffer buffer_; +}; + +class ImagePipelineNodeBufferedGenesysUsb : public ImagePipelineNodeBytesSource +{ +public: +    using ProducerCallback = std::function<void(std::size_t size, std::uint8_t* out_data)>; + +    ImagePipelineNodeBufferedGenesysUsb(std::size_t width, std::size_t height, +                                        PixelFormat format, std::size_t total_size, +                                        const FakeBufferModel& buffer_model, +                                        ProducerCallback producer); + +    std::size_t get_width() const override { return width_; } +    std::size_t get_height() const override { return height_; } +    PixelFormat get_format() const override { return format_; } + +    bool eof() const override { return eof_; } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +    std::size_t buffer_available() const { return buffer_.available(); } + +private: +    ProducerCallback producer_; +    std::size_t width_ = 0; +    std::size_t height_ = 0; +    PixelFormat format_ = PixelFormat::UNKNOWN; + +    bool eof_ = false; + +    ImageBufferGenesysUsb buffer_; +}; + +// A pipeline node that produces data from the given array. +class ImagePipelineNodeArraySource : public ImagePipelineNodeBytesSource +{ +public: +    ImagePipelineNodeArraySource(std::size_t width, std::size_t height, PixelFormat format, +                                 std::vector<std::uint8_t> data); + +    std::size_t get_width() const override { return width_; } +    std::size_t get_height() const override { return height_; } +    PixelFormat get_format() const override { return format_; } + +    bool eof() const override { return eof_; } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    std::size_t width_ = 0; +    std::size_t height_ = 0; +    PixelFormat format_ = PixelFormat::UNKNOWN; + +    bool eof_ = false; + +    std::vector<std::uint8_t> data_; +    std::size_t next_row_ = 0; +}; + + +/// A pipeline node that produces data from the given image +class ImagePipelineNodeImageSource : public ImagePipelineNode +{ +public: +    ImagePipelineNodeImageSource(const Image& source); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height(); } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return next_row_ >= get_height(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    const Image& source_; +    std::size_t next_row_ = 0; +}; + +// A pipeline node that converts between pixel formats +class ImagePipelineNodeFormatConvert : public ImagePipelineNode +{ +public: +    ImagePipelineNodeFormatConvert(ImagePipelineNode& source, PixelFormat dst_format) : +        source_(source), +        dst_format_{dst_format} +    {} + +    ~ImagePipelineNodeFormatConvert() override = default; + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height(); } +    PixelFormat get_format() const override { return dst_format_; } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    PixelFormat dst_format_; +    std::vector<std::uint8_t> buffer_; +}; + +// A pipeline node that handles data that comes out of segmented sensors. Note that the width of +// the output data does not necessarily match the input data width, because in many cases almost +// all width of the image needs to be read in order to desegment it. +class ImagePipelineNodeDesegment : public ImagePipelineNode +{ +public: +    ImagePipelineNodeDesegment(ImagePipelineNode& source, +                               std::size_t output_width, +                               const std::vector<unsigned>& segment_order, +                               std::size_t segment_pixels, +                               std::size_t interleaved_lines, +                               std::size_t pixels_per_chunk); + +    ImagePipelineNodeDesegment(ImagePipelineNode& source, +                               std::size_t output_width, +                               std::size_t segment_count, +                               std::size_t segment_pixels, +                               std::size_t interleaved_lines, +                               std::size_t pixels_per_chunk); + +    ~ImagePipelineNodeDesegment() override = default; + +    std::size_t get_width() const override { return output_width_; } +    std::size_t get_height() const override { return source_.get_height() / interleaved_lines_; } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    std::size_t output_width_; +    std::vector<unsigned> segment_order_; +    std::size_t segment_pixels_ = 0; +    std::size_t interleaved_lines_ = 0; +    std::size_t pixels_per_chunk_ = 0; + +    RowBuffer buffer_; +}; + +// A pipeline node that deinterleaves data on multiple lines +class ImagePipelineNodeDeinterleaveLines : public ImagePipelineNodeDesegment +{ +public: +    ImagePipelineNodeDeinterleaveLines(ImagePipelineNode& source, +                                       std::size_t interleaved_lines, +                                       std::size_t pixels_per_chunk); +}; + +// A pipeline that swaps bytes in 16-bit components on big-endian systems +class ImagePipelineNodeSwap16BitEndian : public ImagePipelineNode +{ +public: +    ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height(); } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    bool needs_swapping_ = false; +}; + +// A pipeline node that merges 3 mono lines into a color channel +class ImagePipelineNodeMergeMonoLines : public ImagePipelineNode +{ +public: +    ImagePipelineNodeMergeMonoLines(ImagePipelineNode& source, +                                    ColorOrder color_order); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height() / 3; } +    PixelFormat get_format() const override { return output_format_; } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    static PixelFormat get_output_format(PixelFormat input_format, ColorOrder order); + +    ImagePipelineNode& source_; +    PixelFormat output_format_ = PixelFormat::UNKNOWN; + +    RowBuffer buffer_; +}; + +// A pipeline node that splits a color channel into 3 mono lines +class ImagePipelineNodeSplitMonoLines : public ImagePipelineNode +{ +public: +    ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height() * 3; } +    PixelFormat get_format() const override { return output_format_; } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    static PixelFormat get_output_format(PixelFormat input_format); + +    ImagePipelineNode& source_; +    PixelFormat output_format_ = PixelFormat::UNKNOWN; + +    std::vector<std::uint8_t> buffer_; +    unsigned next_channel_ = 0; +}; + +// A pipeline node that shifts colors across lines by the given offsets +class ImagePipelineNodeComponentShiftLines : public ImagePipelineNode +{ +public: +    ImagePipelineNodeComponentShiftLines(ImagePipelineNode& source, +                                         unsigned shift_r, unsigned shift_g, unsigned shift_b); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height() - extra_height_; } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    std::size_t extra_height_ = 0; + +    std::array<unsigned, 3> channel_shifts_; + +    RowBuffer buffer_; +}; + +// A pipeline node that shifts pixels across lines by the given offsets (performs unstaggering) +class ImagePipelineNodePixelShiftLines : public ImagePipelineNode +{ +public: +    constexpr static std::size_t MAX_SHIFTS = 2; + +    ImagePipelineNodePixelShiftLines(ImagePipelineNode& source, +                                     const std::vector<std::size_t>& shifts); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height() - extra_height_; } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    std::size_t extra_height_ = 0; + +    std::vector<std::size_t> pixel_shifts_; + +    RowBuffer buffer_; +}; + +// A pipeline node that extracts a sub-image from the image. Padding and cropping is done as needed. +// The class can't pad to the left of the image currently, as only positive offsets are accepted. +class ImagePipelineNodeExtract : public ImagePipelineNode +{ +public: +    ImagePipelineNodeExtract(ImagePipelineNode& source, +                             std::size_t offset_x, std::size_t offset_y, +                             std::size_t width, std::size_t height); + +    ~ImagePipelineNodeExtract() override; + +    std::size_t get_width() const override { return width_; } +    std::size_t get_height() const override { return height_; } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    std::size_t offset_x_ = 0; +    std::size_t offset_y_ = 0; +    std::size_t width_ = 0; +    std::size_t height_ = 0; + +    std::size_t current_line_ = 0; +    std::vector<std::uint8_t> cached_line_; +}; + +// A pipeline node that scales rows to the specified width by using a point filter +class ImagePipelineNodeScaleRows : public ImagePipelineNode +{ +public: +    ImagePipelineNodeScaleRows(ImagePipelineNode& source, std::size_t width); + +    std::size_t get_width() const override { return width_; } +    std::size_t get_height() const override { return source_.get_height(); } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    std::size_t width_ = 0; + +    std::vector<std::uint8_t> cached_line_; +}; + +// A pipeline node that mimics the calibration behavior on Genesys chips +class ImagePipelineNodeCalibrate : public ImagePipelineNode +{ +public: + +    ImagePipelineNodeCalibrate(ImagePipelineNode& source, const std::vector<std::uint16_t>& bottom, +                               const std::vector<std::uint16_t>& top); + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height(); } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; + +    std::vector<float> offset_; +    std::vector<float> multiplier_; +}; + +class ImagePipelineNodeDebug : public ImagePipelineNode +{ +public: +    ImagePipelineNodeDebug(ImagePipelineNode& source, const std::string& path); +    ~ImagePipelineNodeDebug() override; + +    std::size_t get_width() const override { return source_.get_width(); } +    std::size_t get_height() const override { return source_.get_height(); } +    PixelFormat get_format() const override { return source_.get_format(); } + +    bool eof() const override { return source_.eof(); } + +    bool get_next_row_data(std::uint8_t* out_data) override; + +private: +    ImagePipelineNode& source_; +    std::string path_; +    RowBuffer buffer_; +}; + +class ImagePipelineStack +{ +public: +    ImagePipelineStack() {} +    ~ImagePipelineStack() { clear(); } + +    std::size_t get_input_width() const; +    std::size_t get_input_height() const; +    PixelFormat get_input_format() const; +    std::size_t get_input_row_bytes() const; + +    std::size_t get_output_width() const; +    std::size_t get_output_height() const; +    PixelFormat get_output_format() const; +    std::size_t get_output_row_bytes() const; + +    ImagePipelineNode& front() { return *(nodes_.front().get()); } + +    bool eof() const { return nodes_.back()->eof(); } + +    void clear(); + +    template<class Node, class... Args> +    void push_first_node(Args&&... args) +    { +        if (!nodes_.empty()) { +            throw SaneException("Trying to append first node when there are existing nodes"); +        } +        nodes_.emplace_back(std::unique_ptr<Node>(new Node(std::forward<Args>(args)...))); +    } + +    template<class Node, class... Args> +    void push_node(Args&&... args) +    { +        ensure_node_exists(); +        nodes_.emplace_back(std::unique_ptr<Node>(new Node(*nodes_.back(), +                                                           std::forward<Args>(args)...))); +    } + +    bool get_next_row_data(std::uint8_t* out_data) +    { +        return nodes_.back()->get_next_row_data(out_data); +    } + +    std::vector<std::uint8_t> get_all_data(); + +    Image get_image(); + +private: +    void ensure_node_exists() const; + +    std::vector<std::unique_ptr<ImagePipelineNode>> nodes_; +}; + +} // namespace genesys + +#endif // ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H diff --git a/backend/genesys/image_pixel.cpp b/backend/genesys/image_pixel.cpp new file mode 100644 index 0000000..1b83e12 --- /dev/null +++ b/backend/genesys/image_pixel.cpp @@ -0,0 +1,509 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "image.h" + +#include <array> + +namespace genesys { + +struct PixelFormatDesc +{ +    PixelFormat format; +    unsigned depth; +    unsigned channels; +    ColorOrder order; +}; + +const PixelFormatDesc s_known_pixel_formats[] = { +    { PixelFormat::I1, 1, 1, ColorOrder::RGB }, +    { PixelFormat::I8, 8, 1, ColorOrder::RGB }, +    { PixelFormat::I16, 16, 1, ColorOrder::RGB }, +    { PixelFormat::RGB111, 1, 3, ColorOrder::RGB }, +    { PixelFormat::RGB888, 8, 3, ColorOrder::RGB }, +    { PixelFormat::RGB161616, 16, 3, ColorOrder::RGB }, +    { PixelFormat::BGR888, 8, 3, ColorOrder::BGR }, +    { PixelFormat::BGR161616, 16, 3, ColorOrder::BGR }, +}; + + +ColorOrder get_pixel_format_color_order(PixelFormat format) +{ +    for (const auto& desc : s_known_pixel_formats) { +        if (desc.format == format) +            return desc.order; +    } +    throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +} + + +unsigned get_pixel_format_depth(PixelFormat format) +{ +    for (const auto& desc : s_known_pixel_formats) { +        if (desc.format == format) +            return desc.depth; +    } +    throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +} + +unsigned get_pixel_channels(PixelFormat format) +{ +    for (const auto& desc : s_known_pixel_formats) { +        if (desc.format == format) +            return desc.channels; +    } +    throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +} + +std::size_t get_pixel_row_bytes(PixelFormat format, std::size_t width) +{ +    std::size_t depth = get_pixel_format_depth(format) * get_pixel_channels(format); +    std::size_t total_bits = depth * width; +    return total_bits / 8 + ((total_bits % 8 > 0) ? 1 : 0); +} + +std::size_t get_pixels_from_row_bytes(PixelFormat format, std::size_t row_bytes) +{ +    std::size_t depth = get_pixel_format_depth(format) * get_pixel_channels(format); +    return (row_bytes * 8) / depth; +} + +PixelFormat create_pixel_format(unsigned depth, unsigned channels, ColorOrder order) +{ +    for (const auto& desc : s_known_pixel_formats) { +        if (desc.depth == depth && desc.channels == channels && desc.order == order) { +            return desc.format; +        } +    } +   throw SaneException("Unknown pixel format %d %d %d", depth, channels, +                       static_cast<unsigned>(order)); +} + +static inline unsigned read_bit(const std::uint8_t* data, std::size_t x) +{ +    return (data[x / 8] >> (7 - (x % 8))) & 0x1; +} + +static inline void write_bit(std::uint8_t* data, std::size_t x, unsigned value) +{ +    value = (value & 0x1) << (7 - (x % 8)); +    std::uint8_t mask = 0x1 << (7 - (x % 8)); + +    data[x / 8] = (data[x / 8] & ~mask) | (value & mask); +} + +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: { +            std::uint16_t val = read_bit(data, x) ? 0xffff : 0x0000; +            return Pixel(val, val, val); +        } +        case PixelFormat::RGB111: { +            x *= 3; +            std::uint16_t r = read_bit(data, x) ? 0xffff : 0x0000; +            std::uint16_t g = read_bit(data, x + 1) ? 0xffff : 0x0000; +            std::uint16_t b = read_bit(data, x + 2) ? 0xffff : 0x0000; +            return Pixel(r, g, b); +        } +        case PixelFormat::I8: { +            std::uint16_t val = std::uint16_t(data[x]) | (data[x] << 8); +            return Pixel(val, val, val); +        } +        case PixelFormat::I16: { +            x *= 2; +            std::uint16_t val = std::uint16_t(data[x]) | (data[x + 1] << 8); +            return Pixel(val, val, val); +        } +        case PixelFormat::RGB888: { +            x *= 3; +            std::uint16_t r = std::uint16_t(data[x]) | (data[x] << 8); +            std::uint16_t g = std::uint16_t(data[x + 1]) | (data[x + 1] << 8); +            std::uint16_t b = std::uint16_t(data[x + 2]) | (data[x + 2] << 8); +            return Pixel(r, g, b); +        } +        case PixelFormat::BGR888: { +            x *= 3; +            std::uint16_t b = std::uint16_t(data[x]) | (data[x] << 8); +            std::uint16_t g = std::uint16_t(data[x + 1]) | (data[x + 1] << 8); +            std::uint16_t r = std::uint16_t(data[x + 2]) | (data[x + 2] << 8); +            return Pixel(r, g, b); +        } +        case PixelFormat::RGB161616: { +            x *= 6; +            std::uint16_t r = std::uint16_t(data[x]) | (data[x + 1] << 8); +            std::uint16_t g = std::uint16_t(data[x + 2]) | (data[x + 3] << 8); +            std::uint16_t b = std::uint16_t(data[x + 4]) | (data[x + 5] << 8); +            return Pixel(r, g, b); +        } +        case PixelFormat::BGR161616: { +            x *= 6; +            std::uint16_t b = std::uint16_t(data[x]) | (data[x + 1] << 8); +            std::uint16_t g = std::uint16_t(data[x + 2]) | (data[x + 3] << 8); +            std::uint16_t r = std::uint16_t(data[x + 4]) | (data[x + 5] << 8); +            return Pixel(r, g, b); +        } +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +    } +} + +void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel, PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: +            write_bit(data, x, pixel.r & 0x8000 ? 1 : 0); +            return; +        case PixelFormat::RGB111: { +            x *= 3; +            write_bit(data, x, pixel.r & 0x8000 ? 1 : 0); +            write_bit(data, x + 1,pixel.g & 0x8000 ? 1 : 0); +            write_bit(data, x + 2, pixel.b & 0x8000 ? 1 : 0); +            return; +        } +        case PixelFormat::I8: { +            float val = (pixel.r >> 8) * 0.3f; +            val += (pixel.g >> 8) * 0.59f; +            val += (pixel.b >> 8) * 0.11f; +            data[x] = static_cast<std::uint16_t>(val); +            return; +        } +        case PixelFormat::I16: { +            x *= 2; +            float val = pixel.r * 0.3f; +            val += pixel.g * 0.59f; +            val += pixel.b * 0.11f; +            auto val16 = static_cast<std::uint16_t>(val); +            data[x] = val16 & 0xff; +            data[x + 1] = (val16 >> 8) & 0xff; +            return; +        } +        case PixelFormat::RGB888: { +            x *= 3; +            data[x] = pixel.r >> 8; +            data[x + 1] = pixel.g >> 8; +            data[x + 2] = pixel.b >> 8; +            return; +        } +        case PixelFormat::BGR888: { +            x *= 3; +            data[x] = pixel.b >> 8; +            data[x + 1] = pixel.g >> 8; +            data[x + 2] = pixel.r >> 8; +            return; +        } +        case PixelFormat::RGB161616: { +            x *= 6; +            data[x] = pixel.r & 0xff; +            data[x + 1] = (pixel.r >> 8) & 0xff; +            data[x + 2] = pixel.g & 0xff; +            data[x + 3] = (pixel.g >> 8) & 0xff; +            data[x + 4] = pixel.b & 0xff; +            data[x + 5] = (pixel.b >> 8) & 0xff; +            return; +        } +        case PixelFormat::BGR161616: +            x *= 6; +            data[x] = pixel.b & 0xff; +            data[x + 1] = (pixel.b >> 8) & 0xff; +            data[x + 2] = pixel.g & 0xff; +            data[x + 3] = (pixel.g >> 8) & 0xff; +            data[x + 4] = pixel.r & 0xff; +            data[x + 5] = (pixel.r >> 8) & 0xff; +            return; +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +    } +} + +RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: +            return RawPixel(read_bit(data, x)); +        case PixelFormat::RGB111: { +            x *= 3; +            return RawPixel(read_bit(data, x) << 2 | +                            (read_bit(data, x + 1) << 1) | +                            (read_bit(data, x + 2))); +        } +        case PixelFormat::I8: +            return RawPixel(data[x]); +        case PixelFormat::I16: { +            x *= 2; +            return RawPixel(data[x], data[x + 1]); +        } +        case PixelFormat::RGB888: +        case PixelFormat::BGR888: { +            x *= 3; +            return RawPixel(data[x], data[x + 1], data[x + 2]); +        } +        case PixelFormat::RGB161616: +        case PixelFormat::BGR161616: { +            x *= 6; +            return RawPixel(data[x], data[x + 1], data[x + 2], +                            data[x + 3], data[x + 4], data[x + 5]); +        } +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +    } +} + +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel, PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: +            write_bit(data, x, pixel.data[0] & 0x1); +            return; +        case PixelFormat::RGB111: { +            x *= 3; +            write_bit(data, x, (pixel.data[0] >> 2) & 0x1); +            write_bit(data, x + 1, (pixel.data[0] >> 1) & 0x1); +            write_bit(data, x + 2, (pixel.data[0]) & 0x1); +            return; +        } +        case PixelFormat::I8: +            data[x] = pixel.data[0]; +            return; +        case PixelFormat::I16: { +            x *= 2; +            data[x] = pixel.data[0]; +            data[x + 1] = pixel.data[1]; +            return; +        } +        case PixelFormat::RGB888: +        case PixelFormat::BGR888: { +            x *= 3; +            data[x] = pixel.data[0]; +            data[x + 1] = pixel.data[1]; +            data[x + 2] = pixel.data[2]; +            return; +        } +        case PixelFormat::RGB161616: +        case PixelFormat::BGR161616: { +            x *= 6; +            data[x] = pixel.data[0]; +            data[x + 1] = pixel.data[1]; +            data[x + 2] = pixel.data[2]; +            data[x + 3] = pixel.data[3]; +            data[x + 4] = pixel.data[4]; +            data[x + 5] = pixel.data[5]; +            return; +        } +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +    } +} + +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel, +                                       PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: +            return read_bit(data, x); +        case PixelFormat::RGB111: +            return read_bit(data, x * 3 + channel); +        case PixelFormat::I8: +            return data[x]; +        case PixelFormat::I16: { +            x *= 2; +            return data[x] | (data[x + 1] << 8); +        } +        case PixelFormat::RGB888: +        case PixelFormat::BGR888: +            return data[x * 3 + channel]; +        case PixelFormat::RGB161616: +        case PixelFormat::BGR161616: +            return data[x * 6 + channel * 2] | (data[x * 6 + channel * 2 + 1]) << 8; +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +    } +} + +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, +                            std::uint16_t pixel, PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: +            write_bit(data, x, pixel & 0x1); +            return; +        case PixelFormat::RGB111: { +            write_bit(data, x * 3 + channel, pixel & 0x1); +            return; +        } +        case PixelFormat::I8: +            data[x] = pixel; +            return; +        case PixelFormat::I16: { +            x *= 2; +            data[x] = pixel; +            data[x + 1] = pixel >> 8; +            return; +        } +        case PixelFormat::RGB888: +        case PixelFormat::BGR888: { +            x *= 3; +            data[x + channel] = pixel; +            return; +        } +        case PixelFormat::RGB161616: +        case PixelFormat::BGR161616: { +            x *= 6; +            data[x + channel * 2] = pixel; +            data[x + channel * 2 + 1] = pixel >> 8; +            return; +        } +        default: +            throw SaneException("Unknown pixel format %d", static_cast<unsigned>(format)); +    } +} + +template<PixelFormat Format> +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x) +{ +    return get_pixel_from_row(data, x, Format); +} + +template<PixelFormat Format> +void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel) +{ +    set_pixel_to_row(data, x, pixel, Format); +} + +template<PixelFormat Format> +RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x) +{ +    return get_raw_pixel_from_row(data, x, Format); +} + +template<PixelFormat Format> +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel) +{ +    set_raw_pixel_to_row(data, x, pixel, Format); +} + +template<PixelFormat Format> +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel) +{ +    return get_raw_channel_from_row(data, x, channel, Format); +} + +template<PixelFormat Format> +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel) +{ +    set_raw_channel_to_row(data, x, channel, pixel, Format); +} + +template Pixel get_pixel_from_row<PixelFormat::I1>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::RGB111>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::I8>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::RGB888>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::BGR888>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::I16>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::RGB161616>(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row<PixelFormat::BGR161616>(const std::uint8_t* data, std::size_t x); + +template RawPixel get_raw_pixel_from_row<PixelFormat::I1>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::RGB111>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::I8>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::RGB888>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::BGR888>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::I16>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::RGB161616>(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row<PixelFormat::BGR161616>(const std::uint8_t* data, std::size_t x); + +template std::uint16_t get_raw_channel_from_row<PixelFormat::I1>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::RGB111>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::I8>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::RGB888>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::BGR888>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::I16>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::RGB161616>( +        const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row<PixelFormat::BGR161616> +        (const std::uint8_t* data, std::size_t x, unsigned channel); + +template void set_pixel_to_row<PixelFormat::I1>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::RGB111>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::I8>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::RGB888>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::BGR888>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::I16>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::RGB161616>(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row<PixelFormat::BGR161616>(std::uint8_t* data, std::size_t x, Pixel pixel); + +template void set_raw_pixel_to_row<PixelFormat::I1>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::RGB111>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::I8>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::RGB888>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::BGR888>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::I16>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::RGB161616>(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row<PixelFormat::BGR161616>(std::uint8_t* data, std::size_t x, RawPixel pixel); + +template void set_raw_channel_to_row<PixelFormat::I1>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::RGB111>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::I8>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::RGB888>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::BGR888>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::I16>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::RGB161616>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row<PixelFormat::BGR161616>( +        std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); + +} // namespace genesys diff --git a/backend/genesys/image_pixel.h b/backend/genesys/image_pixel.h new file mode 100644 index 0000000..2dda271 --- /dev/null +++ b/backend/genesys/image_pixel.h @@ -0,0 +1,144 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_PIXEL_H +#define BACKEND_GENESYS_IMAGE_PIXEL_H + +#include "enums.h" +#include <algorithm> +#include <cstdint> +#include <cstddef> + +namespace genesys { + +enum class PixelFormat +{ +    UNKNOWN, +    I1, +    RGB111, +    I8, +    RGB888, +    BGR888, +    I16, +    RGB161616, +    BGR161616, +}; + +struct Pixel +{ +    Pixel() = default; +    Pixel(std::uint16_t red, std::uint16_t green, std::uint16_t blue) : +        r{red}, g{green}, b{blue} {} + +    std::uint16_t r = 0; +    std::uint16_t g = 0; +    std::uint16_t b = 0; + +    bool operator==(const Pixel& other) const +    { +        return r == other.r && g == other.g && b == other.b; +    } +}; + +struct RawPixel +{ +    RawPixel() = default; +    RawPixel(std::uint8_t d0) : data{d0, 0, 0, 0, 0, 0} {} +    RawPixel(std::uint8_t d0, std::uint8_t d1) : data{d0, d1, 0, 0, 0, 0} {} +    RawPixel(std::uint8_t d0, std::uint8_t d1, std::uint8_t d2) : data{d0, d1, d2, 0, 0, 0} {} +    RawPixel(std::uint8_t d0, std::uint8_t d1, std::uint8_t d2, +             std::uint8_t d3, std::uint8_t d4, std::uint8_t d5) : data{d0, d1, d2, d3, d4, d5} {} +    std::uint8_t data[6] = {}; + +    bool operator==(const RawPixel& other) const +    { +        return std::equal(std::begin(data), std::end(data), +                          std::begin(other.data)); +    } +}; + +ColorOrder get_pixel_format_color_order(PixelFormat format); +unsigned get_pixel_format_depth(PixelFormat format); +unsigned get_pixel_channels(PixelFormat format); +std::size_t get_pixel_row_bytes(PixelFormat format, std::size_t width); + +std::size_t get_pixels_from_row_bytes(PixelFormat format, std::size_t row_bytes); + +PixelFormat create_pixel_format(unsigned depth, unsigned channels, ColorOrder order); + +// retrieves or sets the logical pixel values in 16-bit range. +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format); +void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel, PixelFormat format); + +// retrieves or sets the physical pixel values. The low bytes of the RawPixel are interpreted as +// the retrieved values / values to set +RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format); +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel, PixelFormat format); + +// retrieves or sets the physical value of specific channel of the pixel. The channels are numbered +// in the same order as the pixel is laid out in memory, that is, whichever channel comes first +// has the index 0. E.g. 0-th channel in RGB888 is the red byte, but in BGR888 is the blue byte. +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel, +                                       PixelFormat format); +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel, +                            PixelFormat format); + +template<PixelFormat Format> +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template<PixelFormat Format> +void set_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); + +template<PixelFormat Format> +Pixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template<PixelFormat Format> +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); + +template<PixelFormat Format> +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel); +template<PixelFormat Format> +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, +                            std::uint16_t pixel); + +} // namespace genesys + +#endif // BACKEND_GENESYS_IMAGE_PIXEL_H diff --git a/backend/genesys/low.cpp b/backend/genesys/low.cpp new file mode 100644 index 0000000..7937fcc --- /dev/null +++ b/backend/genesys/low.cpp @@ -0,0 +1,1994 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr> + + +   This file is part of the SANE package. + +   This program is free software; you can redistribute it and/or +   modify it under the terms of the GNU General Public License as +   published by the Free Software Foundation; either version 2 of the +   License, or (at your option) any later version. + +   This program is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   General Public License for more details. + +   You should have received a copy of the GNU General Public License +   along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "low.h" +#include "assert.h" +#include "test_settings.h" + +#include "gl124_registers.h" +#include "gl646_registers.h" +#include "gl841_registers.h" +#include "gl843_registers.h" +#include "gl846_registers.h" +#include "gl847_registers.h" +#include "gl646_registers.h" + +#include <cstdio> +#include <cmath> +#include <vector> + +/* ------------------------------------------------------------------------ */ +/*                  functions calling ASIC specific functions               */ +/* ------------------------------------------------------------------------ */ + +namespace genesys { + +/** + * setup the hardware dependent functions + */ + +namespace gl124 { std::unique_ptr<CommandSet> create_gl124_cmd_set(); } +namespace gl646 { std::unique_ptr<CommandSet> create_gl646_cmd_set(); } +namespace gl841 { std::unique_ptr<CommandSet> create_gl841_cmd_set(); } +namespace gl843 { std::unique_ptr<CommandSet> create_gl843_cmd_set(); } +namespace gl846 { std::unique_ptr<CommandSet> create_gl846_cmd_set(); } +namespace gl847 { std::unique_ptr<CommandSet> create_gl847_cmd_set(); } + +void sanei_genesys_init_cmd_set(Genesys_Device* dev) +{ +  DBG_INIT (); +    DBG_HELPER(dbg); +    switch (dev->model->asic_type) { +        case AsicType::GL646: dev->cmd_set = gl646::create_gl646_cmd_set(); break; +        case AsicType::GL841: dev->cmd_set = gl841::create_gl841_cmd_set(); break; +        case AsicType::GL843: dev->cmd_set = gl843::create_gl843_cmd_set(); break; +        case AsicType::GL845: // since only a few reg bits differs we handle both together +        case AsicType::GL846: dev->cmd_set = gl846::create_gl846_cmd_set(); break; +        case AsicType::GL847: dev->cmd_set = gl847::create_gl847_cmd_set(); break; +        case AsicType::GL124: dev->cmd_set = gl124::create_gl124_cmd_set(); break; +        default: throw SaneException(SANE_STATUS_INVAL, "unknown ASIC type"); +    } +} + +/* ------------------------------------------------------------------------ */ +/*                  General IO and debugging functions                      */ +/* ------------------------------------------------------------------------ */ + +void sanei_genesys_write_file(const char* filename, const std::uint8_t* data, std::size_t length) +{ +    DBG_HELPER(dbg); +    std::FILE* out = std::fopen(filename, "w"); +    if (!out) { +        throw SaneException("could not open %s for writing: %s", filename, strerror(errno)); +    } +    std::fwrite(data, 1, length, out); +    std::fclose(out); +} + +// Write data to a pnm file (e.g. calibration). For debugging only +// data is RGB or grey, with little endian byte order +void sanei_genesys_write_pnm_file(const char* filename, const std::uint8_t* data, int depth, +                                  int channels, int pixels_per_line, int lines) +{ +    DBG_HELPER_ARGS(dbg, "depth=%d, channels=%d, ppl=%d, lines=%d", depth, channels, +                    pixels_per_line, lines); +  int count; + +    std::FILE* out = std::fopen(filename, "w"); +  if (!out) +    { +        throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno)); +    } +  if(depth==1) +    { +      fprintf (out, "P4\n%d\n%d\n", pixels_per_line, lines); +    } +  else +    { +        std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', pixels_per_line, lines, +                     static_cast<int>(std::pow(static_cast<double>(2), +                                               static_cast<double>(depth - 1)))); +    } +  if (channels == 3) +    { +      for (count = 0; count < (pixels_per_line * lines * 3); count++) +	{ +	  if (depth == 16) +	    fputc (*(data + 1), out); +	  fputc (*(data++), out); +	  if (depth == 16) +	    data++; +	} +    } +  else +    { +      if (depth==1) +        { +          pixels_per_line/=8; +        } +      for (count = 0; count < (pixels_per_line * lines); count++) +	{ +          switch (depth) +            { +              case 8: +	        fputc (*(data + count), out); +                break; +              case 16: +	        fputc (*(data + 1), out); +	        fputc (*(data), out); +	        data += 2; +                break; +              default: +                fputc(data[count], out); +                break; +            } +	} +    } +    std::fclose(out); +} + +void sanei_genesys_write_pnm_file16(const char* filename, const uint16_t* data, unsigned channels, +                                    unsigned pixels_per_line, unsigned lines) +{ +    DBG_HELPER_ARGS(dbg, "channels=%d, ppl=%d, lines=%d", channels, +                    pixels_per_line, lines); + +    std::FILE* out = std::fopen(filename, "w"); +    if (!out) { +        throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno)); +    } +    std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', +                 pixels_per_line, lines, 256 * 256 - 1); + +    for (unsigned count = 0; count < (pixels_per_line * lines * channels); count++) { +        fputc(*data >> 8, out); +        fputc(*data & 0xff, out); +        data++; +    } +    std::fclose(out); +} + +bool is_supported_write_pnm_file_image_format(PixelFormat format) +{ +    switch (format) { +        case PixelFormat::I1: +        case PixelFormat::RGB111: +        case PixelFormat::I8: +        case PixelFormat::RGB888: +        case PixelFormat::I16: +        case PixelFormat::RGB161616: +            return true; +        default: +            return false; +    } +} + +void sanei_genesys_write_pnm_file(const char* filename, const Image& image) +{ +    if (!is_supported_write_pnm_file_image_format(image.get_format())) { +        throw SaneException("Unsupported format %d", static_cast<unsigned>(image.get_format())); +    } + +    sanei_genesys_write_pnm_file(filename, image.get_row_ptr(0), +                                 get_pixel_format_depth(image.get_format()), +                                 get_pixel_channels(image.get_format()), +                                 image.get_width(), image.get_height()); +} + +/* ------------------------------------------------------------------------ */ +/*                  Read and write RAM, registers and AFE                   */ +/* ------------------------------------------------------------------------ */ + +unsigned sanei_genesys_get_bulk_max_size(AsicType asic_type) +{ +    /*  Genesys supports 0xFE00 maximum size in general, wheraus GL646 supports +        0xFFC0. We use 0xF000 because that's the packet limit in the Linux usbmon +        USB capture stack. By default it limits packet size to b_size / 5 where +        b_size is the size of the ring buffer. By default it's 300*1024, so the +        packet is limited 61440 without any visibility to acquiring software. +    */ +    if (asic_type == AsicType::GL124 || +        asic_type == AsicType::GL846 || +        asic_type == AsicType::GL847) +    { +        return 0xeff0; +    } +    return 0xf000; +} + +// Set address for writing data +void sanei_genesys_set_buffer_address(Genesys_Device* dev, uint32_t addr) +{ +    DBG_HELPER(dbg); + +    if (dev->model->asic_type==AsicType::GL847 || +        dev->model->asic_type==AsicType::GL845 || +        dev->model->asic_type==AsicType::GL846 || +        dev->model->asic_type==AsicType::GL124) +    { +      DBG(DBG_warn, "%s: shouldn't be used for GL846+ ASICs\n", __func__); +      return; +    } + +  DBG(DBG_io, "%s: setting address to 0x%05x\n", __func__, addr & 0xfffffff0); + +  addr = addr >> 4; + +    dev->interface->write_register(0x2b, (addr & 0xff)); + +  addr = addr >> 8; +    dev->interface->write_register(0x2a, (addr & 0xff)); +} + +/* ------------------------------------------------------------------------ */ +/*                       Medium level functions                             */ +/* ------------------------------------------------------------------------ */ + +Status scanner_read_status(Genesys_Device& dev) +{ +    DBG_HELPER(dbg); +    std::uint16_t address = 0; + +    switch (dev.model->asic_type) { +        case AsicType::GL124: address = 0x101; break; +        case AsicType::GL646: +        case AsicType::GL841: +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: address = 0x41; break; +        default: throw SaneException("Unsupported asic type"); +    } + +    // same for all chips +    constexpr std::uint8_t PWRBIT = 0x80; +    constexpr std::uint8_t BUFEMPTY	= 0x40; +    constexpr std::uint8_t FEEDFSH = 0x20; +    constexpr std::uint8_t SCANFSH = 0x10; +    constexpr std::uint8_t HOMESNR = 0x08; +    constexpr std::uint8_t LAMPSTS = 0x04; +    constexpr std::uint8_t FEBUSY = 0x02; +    constexpr std::uint8_t MOTORENB	= 0x01; + +    auto value = dev.interface->read_register(address); +    Status status; +    status.is_replugged = !(value & PWRBIT); +    status.is_buffer_empty = value & BUFEMPTY; +    status.is_feeding_finished = value & FEEDFSH; +    status.is_scanning_finished = value & SCANFSH; +    status.is_at_home = value & HOMESNR; +    status.is_lamp_on = value & LAMPSTS; +    status.is_front_end_busy = value & FEBUSY; +    status.is_motor_enabled = value & MOTORENB; + +    if (DBG_LEVEL >= DBG_io) { +        debug_print_status(dbg, status); +    } + +    return status; +} + +Status scanner_read_reliable_status(Genesys_Device& dev) +{ +    DBG_HELPER(dbg); + +    scanner_read_status(dev); +    dev.interface->sleep_ms(100); +    return scanner_read_status(dev); +} + +void scanner_read_print_status(Genesys_Device& dev) +{ +    scanner_read_status(dev); +} + +/** + * decodes and prints content of status register + * @param val value read from status register + */ +void debug_print_status(DebugMessageHelper& dbg, Status val) +{ +    std::stringstream str; +    str << val; +    dbg.vlog(DBG_info, "status=%s\n", str.str().c_str()); +} + +#if 0 +/* returns pixels per line from register set */ +/*candidate for moving into chip specific files?*/ +static int +genesys_pixels_per_line (Genesys_Register_Set * reg) +{ +    int pixels_per_line; + +    pixels_per_line = reg->get8(0x32) * 256 + reg->get8(0x33); +    pixels_per_line -= (reg->get8(0x30) * 256 + reg->get8(0x31)); + +    return pixels_per_line; +} + +/* returns dpiset from register set */ +/*candidate for moving into chip specific files?*/ +static int +genesys_dpiset (Genesys_Register_Set * reg) +{ +    return reg->get8(0x2c) * 256 + reg->get8(0x2d); +} +#endif + +/** read the number of valid words in scanner's RAM + * ie registers 42-43-44 + */ +// candidate for moving into chip specific files? +void sanei_genesys_read_valid_words(Genesys_Device* dev, unsigned int* words) +{ +    DBG_HELPER(dbg); + +  switch (dev->model->asic_type) +    { +    case AsicType::GL124: +            *words = dev->interface->read_register(0x102) & 0x03; +            *words = *words * 256 + dev->interface->read_register(0x103); +            *words = *words * 256 + dev->interface->read_register(0x104); +            *words = *words * 256 + dev->interface->read_register(0x105); +            break; + +    case AsicType::GL845: +    case AsicType::GL846: +            *words = dev->interface->read_register(0x42) & 0x02; +            *words = *words * 256 + dev->interface->read_register(0x43); +            *words = *words * 256 + dev->interface->read_register(0x44); +            *words = *words * 256 + dev->interface->read_register(0x45); +            break; + +    case AsicType::GL847: +            *words = dev->interface->read_register(0x42) & 0x03; +            *words = *words * 256 + dev->interface->read_register(0x43); +            *words = *words * 256 + dev->interface->read_register(0x44); +            *words = *words * 256 + dev->interface->read_register(0x45); +            break; + +    default: +            *words = dev->interface->read_register(0x44); +            *words += dev->interface->read_register(0x43) * 256; +            if (dev->model->asic_type == AsicType::GL646) { +                *words += ((dev->interface->read_register(0x42) & 0x03) * 256 * 256); +            } else { +                *words += ((dev->interface->read_register(0x42) & 0x0f) * 256 * 256); +            } +    } + +  DBG(DBG_proc, "%s: %d words\n", __func__, *words); +} + +/** read the number of lines scanned + * ie registers 4b-4c-4d + */ +void sanei_genesys_read_scancnt(Genesys_Device* dev, unsigned int* words) +{ +    DBG_HELPER(dbg); + +    if (dev->model->asic_type == AsicType::GL124) { +        *words = (dev->interface->read_register(0x10b) & 0x0f) << 16; +        *words += (dev->interface->read_register(0x10c) << 8); +        *words += dev->interface->read_register(0x10d); +    } +  else +    { +        *words = dev->interface->read_register(0x4d); +        *words += dev->interface->read_register(0x4c) * 256; +        if (dev->model->asic_type == AsicType::GL646) { +            *words += ((dev->interface->read_register(0x4b) & 0x03) * 256 * 256); +        } else { +            *words += ((dev->interface->read_register(0x4b) & 0x0f) * 256 * 256); +        } +    } + +  DBG(DBG_proc, "%s: %d lines\n", __func__, *words); +} + +/** @brief Check if the scanner's internal data buffer is empty + * @param *dev device to test for data + * @param *empty return value + * @return empty will be set to true if there is no scanned data. + **/ +bool sanei_genesys_is_buffer_empty(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +    dev->interface->sleep_ms(1); + +    auto status = scanner_read_status(*dev); + +    if (status.is_buffer_empty) { +      /* fix timing issue on USB3 (or just may be too fast) hardware +       * spotted by John S. Weber <jweber53@gmail.com> +       */ +        dev->interface->sleep_ms(1); +      DBG(DBG_io2, "%s: buffer is empty\n", __func__); +        return true; +    } + + +  DBG(DBG_io, "%s: buffer is filled\n", __func__); +    return false; +} + +void wait_until_buffer_non_empty(Genesys_Device* dev, bool check_status_twice) +{ +    // FIXME: reduce MAX_RETRIES once tests are updated +    const unsigned MAX_RETRIES = 100000; +    for (unsigned i = 0; i < MAX_RETRIES; ++i) { + +        if (check_status_twice) { +            // FIXME: this only to preserve previous behavior, can be removed +            scanner_read_status(*dev); +        } + +        bool empty = sanei_genesys_is_buffer_empty(dev); +        dev->interface->sleep_ms(10); +        if (!empty) +            return; +    } +    throw SaneException(SANE_STATUS_IO_ERROR, "failed to read data"); +} + +void wait_until_has_valid_words(Genesys_Device* dev) +{ +    unsigned words = 0; +    unsigned sleep_time_ms = 10; + +    for (unsigned wait_ms = 0; wait_ms < 50000; wait_ms += sleep_time_ms) { +        sanei_genesys_read_valid_words(dev, &words); +        if (words != 0) +            break; +        dev->interface->sleep_ms(sleep_time_ms); +    } + +    if (words == 0) { +        throw SaneException(SANE_STATUS_IO_ERROR, "timeout, buffer does not get filled"); +    } +} + +// Read data (e.g scanned image) from scan buffer +void sanei_genesys_read_data_from_scanner(Genesys_Device* dev, uint8_t* data, size_t size) +{ +    DBG_HELPER_ARGS(dbg, "size = %zu bytes", size); + +  if (size & 1) +    DBG(DBG_info, "WARNING %s: odd number of bytes\n", __func__); + +    wait_until_has_valid_words(dev); + +    dev->interface->bulk_read_data(0x45, data, size); +} + +Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& session, +                                         std::size_t total_bytes) +{ +    DBG_HELPER(dbg); + +    auto format = create_pixel_format(session.params.depth, +                                      dev->model->is_cis ? 1 : session.params.channels, +                                      dev->model->line_mode_color_order); + +    auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw); +    auto height = session.output_line_count * (dev->model->is_cis ? session.params.channels : 1); + +    Image image(width, height, format); + +    auto max_bytes = image.get_row_bytes() * height; +    if (total_bytes > max_bytes) { +        throw SaneException("Trying to read too much data %zu (max %zu)", total_bytes, max_bytes); +    } +    if (total_bytes != max_bytes) { +        DBG(DBG_info, "WARNING %s: trying to read not enough data (%zu, full fill %zu\n", __func__, +            total_bytes, max_bytes); +    } + +    sanei_genesys_read_data_from_scanner(dev, image.get_row_ptr(0), total_bytes); + +    ImagePipelineStack pipeline; +    pipeline.push_first_node<ImagePipelineNodeImageSource>(image); + +    if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && session.params.depth == 16) { +        dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); +    } + +#ifdef WORDS_BIGENDIAN +    if (depth == 16) { +        dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); +    } +#endif + +    if (dev->model->is_cis && session.params.channels == 3) { +        dev->pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order); +    } + +    if (dev->pipeline.get_output_format() == PixelFormat::BGR888) { +        dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); +    } + +    if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) { +        dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); +    } + +    return pipeline.get_image(); +} + +void sanei_genesys_read_feed_steps(Genesys_Device* dev, unsigned int* steps) +{ +    DBG_HELPER(dbg); + +    if (dev->model->asic_type == AsicType::GL124) { +        *steps = (dev->interface->read_register(0x108) & 0x1f) << 16; +        *steps += (dev->interface->read_register(0x109) << 8); +        *steps += dev->interface->read_register(0x10a); +    } +  else +    { +        *steps = dev->interface->read_register(0x4a); +        *steps += dev->interface->read_register(0x49) * 256; +        if (dev->model->asic_type == AsicType::GL646) { +            *steps += ((dev->interface->read_register(0x48) & 0x03) * 256 * 256); +        } else if (dev->model->asic_type == AsicType::GL841) { +            *steps += ((dev->interface->read_register(0x48) & 0x0f) * 256 * 256); +        } else { +            *steps += ((dev->interface->read_register(0x48) & 0x1f) * 256 * 256); +        } +    } + +  DBG(DBG_proc, "%s: %d steps\n", __func__, *steps); +} + +void sanei_genesys_set_lamp_power(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                  Genesys_Register_Set& regs, bool set) +{ +    static const uint8_t REG_0x03_LAMPPWR = 0x10; + +    if (set) { +        regs.find_reg(0x03).value |= REG_0x03_LAMPPWR; + +        if (dev->model->asic_type == AsicType::GL841) { +            regs_set_exposure(dev->model->asic_type, regs, +                              sanei_genesys_fixup_exposure(sensor.exposure)); +            regs.set8(0x19, 0x50); +        } + +        if (dev->model->asic_type == AsicType::GL843) { +            regs_set_exposure(dev->model->asic_type, regs, sensor.exposure); + +            // we don't actually turn on lamp on infrared scan +            if ((dev->model->model_id == ModelId::CANON_8400F || +                 dev->model->model_id == ModelId::CANON_8600F || +                 dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +                 dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) && +                dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +            { +                regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR; +            } +        } +    } else { +        regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR; + +        if (dev->model->asic_type == AsicType::GL841) { +            regs_set_exposure(dev->model->asic_type, regs, {0x0101, 0x0101, 0x0101}); +            regs.set8(0x19, 0xff); +        } + +        if (dev->model->asic_type == AsicType::GL843) { +            if (dev->model->model_id == ModelId::PANASONIC_KV_SS080 || +                dev->model->model_id == ModelId::HP_SCANJET_4850C || +                dev->model->model_id == ModelId::HP_SCANJET_G4010 || +                dev->model->model_id == ModelId::HP_SCANJET_G4050) +            { +                // BUG: datasheet says we shouldn't set exposure to zero +                regs_set_exposure(dev->model->asic_type, regs, {0, 0, 0}); +            } +        } +    } +    regs.state.is_lamp_on = set; +} + +void sanei_genesys_set_motor_power(Genesys_Register_Set& regs, bool set) +{ +    static const uint8_t REG_0x02_MTRPWR = 0x10; + +    if (set) { +        regs.find_reg(0x02).value |= REG_0x02_MTRPWR; +    } else { +        regs.find_reg(0x02).value &= ~REG_0x02_MTRPWR; +    } +    regs.state.is_motor_on = set; +} + +bool should_enable_gamma(const ScanSession& session, const Genesys_Sensor& sensor) +{ +    if ((session.params.flags & ScanFlag::DISABLE_GAMMA) != ScanFlag::NONE) { +        return false; +    } +    if (sensor.gamma[0] == 1.0f || sensor.gamma[1] == 1.0f || sensor.gamma[2] == 1.0f) { +        return false; +    } +    if (session.params.depth == 16) +        return false; + +    return true; +} + +std::vector<uint16_t> get_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                      int color) +{ +    if (!dev->gamma_override_tables[color].empty()) { +        return dev->gamma_override_tables[color]; +    } else { +        std::vector<uint16_t> ret; +        sanei_genesys_create_default_gamma_table(dev, ret, sensor.gamma[color]); +        return ret; +    } +} + +/** @brief generates gamma buffer to transfer + * Generates gamma table buffer to send to ASIC. Applies + * contrast and brightness if set. + * @param dev device to set up + * @param bits number of bits used by gamma + * @param max value for gamma + * @param size of the gamma table + * @param gamma allocated gamma buffer to fill + */ +void sanei_genesys_generate_gamma_buffer(Genesys_Device* dev, +                                                const Genesys_Sensor& sensor, +                                                int bits, +                                                int max, +                                                int size, +                                                uint8_t* gamma) +{ +    DBG_HELPER(dbg); +    std::vector<uint16_t> rgamma = get_gamma_table(dev, sensor, GENESYS_RED); +    std::vector<uint16_t> ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN); +    std::vector<uint16_t> bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE); + +  if(dev->settings.contrast!=0 || dev->settings.brightness!=0) +    { +      std::vector<uint16_t> lut(65536); +      sanei_genesys_load_lut(reinterpret_cast<unsigned char *>(lut.data()), +                             bits, +                             bits, +                             0, +                             max, +                             dev->settings.contrast, +                             dev->settings.brightness); +      for (int i = 0; i < size; i++) +        { +          uint16_t value=rgamma[i]; +          value=lut[value]; +          gamma[i * 2 + size * 0 + 0] = value & 0xff; +          gamma[i * 2 + size * 0 + 1] = (value >> 8) & 0xff; + +          value=ggamma[i]; +          value=lut[value]; +          gamma[i * 2 + size * 2 + 0] = value & 0xff; +          gamma[i * 2 + size * 2 + 1] = (value >> 8) & 0xff; + +          value=bgamma[i]; +          value=lut[value]; +          gamma[i * 2 + size * 4 + 0] = value & 0xff; +          gamma[i * 2 + size * 4 + 1] = (value >> 8) & 0xff; +        } +    } +  else +    { +      for (int i = 0; i < size; i++) +        { +          uint16_t value=rgamma[i]; +          gamma[i * 2 + size * 0 + 0] = value & 0xff; +          gamma[i * 2 + size * 0 + 1] = (value >> 8) & 0xff; + +          value=ggamma[i]; +          gamma[i * 2 + size * 2 + 0] = value & 0xff; +          gamma[i * 2 + size * 2 + 1] = (value >> 8) & 0xff; + +          value=bgamma[i]; +          gamma[i * 2 + size * 4 + 0] = value & 0xff; +          gamma[i * 2 + size * 4 + 1] = (value >> 8) & 0xff; +        } +    } +} + + +/** @brief send gamma table to scanner + * This function sends generic gamma table (ie ones built with + * provided gamma) or the user defined one if provided by + * fontend. Used by gl846+ ASICs + * @param dev device to write to + */ +void sanei_genesys_send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +  int size; +  int i; + +  size = 256 + 1; + +  /* allocate temporary gamma tables: 16 bits words, 3 channels */ +  std::vector<uint8_t> gamma(size * 2 * 3, 255); + +    sanei_genesys_generate_gamma_buffer(dev, sensor, 16, 65535, size, gamma.data()); + +    // loop sending gamma tables NOTE: 0x01000000 not 0x10000000 +    for (i = 0; i < 3; i++) { +        // clear corresponding GMM_N bit +        uint8_t val = dev->interface->read_register(0xbd); +        val &= ~(0x01 << i); +        dev->interface->write_register(0xbd, val); + +        // clear corresponding GMM_F bit +        val = dev->interface->read_register(0xbe); +      val &= ~(0x01 << i); +        dev->interface->write_register(0xbe, val); + +      // FIXME: currently the last word of each gamma table is not initialied, so to work around +      // unstable data, just set it to 0 which is the most likely value of uninitialized memory +      // (proper value is probably 0xff) +      gamma[size * 2 * i + size * 2 - 2] = 0; +      gamma[size * 2 * i + size * 2 - 1] = 0; + +      /* set GMM_Z */ +        dev->interface->write_register(0xc5+2*i, gamma[size*2*i+1]); +        dev->interface->write_register(0xc6+2*i, gamma[size*2*i]); + +        dev->interface->write_ahb(0x01000000 + 0x200 * i, (size-1) * 2, +                                  gamma.data() + i * size * 2+2); +    } +} + +static unsigned align_int_up(unsigned num, unsigned alignment) +{ +    unsigned mask = alignment - 1; +    if (num & mask) +        num = (num & ~mask) + alignment; +    return num; +} + +void compute_session_buffer_sizes(AsicType asic, ScanSession& s) +{ +    size_t line_bytes = s.output_line_bytes; +    size_t line_bytes_stagger = s.output_line_bytes; + +    if (asic != AsicType::GL646) { +        // BUG: this is historical artifact and should be removed. Note that buffer sizes affect +        // how often we request the scanner for data and thus change the USB traffic. +        line_bytes_stagger = +                multiply_by_depth_ceil(s.optical_pixels, s.params.depth) * s.params.channels; +    } + +    struct BufferConfig { +        size_t* result_size = nullptr; +        size_t lines = 0; +        size_t lines_mult = 0; +        size_t max_size = 0; // does not apply if 0 +        size_t stagger_lines = 0; + +        BufferConfig() = default; +        BufferConfig(std::size_t* rs, std::size_t l, std::size_t lm, std::size_t ms, +                     std::size_t sl) : +            result_size{rs}, +            lines{l}, +            lines_mult{lm}, +            max_size{ms}, +            stagger_lines{sl} +        {} +    }; + +    std::array<BufferConfig, 4> configs; +    if (asic == AsicType::GL124 || asic == AsicType::GL843) { +        configs = { { +            { &s.buffer_size_read, 32, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, +            { &s.buffer_size_lines, 32, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, +            { &s.buffer_size_shrink, 16, 1, 0, 0 }, +            { &s.buffer_size_out, 8, 1, 0, 0 }, +        } }; +    } else if (asic == AsicType::GL841) { +        size_t max_buf = sanei_genesys_get_bulk_max_size(asic); +        configs = { { +            { &s.buffer_size_read, 8, 2, max_buf, s.max_color_shift_lines + s.num_staggered_lines }, +            { &s.buffer_size_lines, 8, 2, max_buf, s.max_color_shift_lines + s.num_staggered_lines }, +            { &s.buffer_size_shrink, 8, 1, max_buf, 0 }, +            { &s.buffer_size_out, 8, 1, 0, 0 }, +        } }; +    } else { +        configs = { { +            { &s.buffer_size_read, 16, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, +            { &s.buffer_size_lines, 16, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, +            { &s.buffer_size_shrink, 8, 1, 0, 0 }, +            { &s.buffer_size_out, 8, 1, 0, 0 }, +        } }; +    } + +    for (BufferConfig& config : configs) { +        size_t buf_size = line_bytes * config.lines; +        if (config.max_size > 0 && buf_size > config.max_size) { +            buf_size = (config.max_size / line_bytes) * line_bytes; +        } +        buf_size *= config.lines_mult; +        buf_size += line_bytes_stagger * config.stagger_lines; +        *config.result_size = buf_size; +    } +} + +void compute_session_pipeline(const Genesys_Device* dev, ScanSession& s) +{ +    auto channels = s.params.channels; +    auto depth = s.params.depth; + +    s.pipeline_needs_reorder = true; +    if (channels != 3 && depth != 16) { +        s.pipeline_needs_reorder = false; +    } +#ifndef WORDS_BIGENDIAN +    if (channels != 3 && depth == 16) { +        s.pipeline_needs_reorder = false; +    } +    if (channels == 3 && depth == 16 && !dev->model->is_cis && +        dev->model->line_mode_color_order == ColorOrder::RGB) +    { +        s.pipeline_needs_reorder = false; +    } +#endif +    if (channels == 3 && depth == 8 && !dev->model->is_cis && +        dev->model->line_mode_color_order == ColorOrder::RGB) +    { +        s.pipeline_needs_reorder = false; +    } +    s.pipeline_needs_ccd = s.max_color_shift_lines + s.num_staggered_lines > 0; +    s.pipeline_needs_shrink = dev->settings.requested_pixels != s.output_pixels; +} + +void compute_session_pixel_offsets(const Genesys_Device* dev, ScanSession& s, +                                   const Genesys_Sensor& sensor) +{ +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); + +    if (dev->model->asic_type == AsicType::GL646) { + +        // startx cannot be below dummy pixel value +        s.pixel_startx = sensor.dummy_pixel; +        if (has_flag(s.params.flags, ScanFlag::USE_XCORRECTION) && sensor.ccd_start_xoffset > 0) { +            s.pixel_startx = sensor.ccd_start_xoffset; +        } +        s.pixel_startx += s.params.startx; + +        if (sensor.stagger_config.stagger_at_resolution(s.params.xres, s.params.yres) > 0) { +            s.pixel_startx |= 1; +        } + +        s.pixel_endx = s.pixel_startx + s.optical_pixels; + +        s.pixel_startx /= sensor.ccd_pixels_per_system_pixel() * s.ccd_size_divisor; +        s.pixel_endx /= sensor.ccd_pixels_per_system_pixel() * s.ccd_size_divisor; + +    } else if (dev->model->asic_type == AsicType::GL841) { +        s.pixel_startx = ((sensor.ccd_start_xoffset + s.params.startx) * s.optical_resolution) +                                / sensor.optical_res; + +        s.pixel_startx += sensor.dummy_pixel + 1; + +        if (s.num_staggered_lines > 0 && (s.pixel_startx & 1) == 0) { +            s.pixel_startx++; +        } + +        /*  In case of SHDAREA, we need to align start on pixel average factor, startx is +            different than 0 only when calling for function to setup for scan, where shading data +            needs to be align. + +            NOTE: we can check the value of the register here, because we don't set this bit +            anywhere except in initialization. +        */ +        const uint8_t REG_0x01_SHDAREA = 0x02; +        if ((dev->reg.find_reg(0x01).value & REG_0x01_SHDAREA) != 0) { +            unsigned average_factor = s.optical_resolution / s.params.xres; +            s.pixel_startx = align_multiple_floor(s.pixel_startx, average_factor); +        } + +        s.pixel_endx = s.pixel_startx + s.optical_pixels; + +    } else if (dev->model->asic_type == AsicType::GL843) { + +        s.pixel_startx = (s.params.startx + sensor.dummy_pixel) / ccd_pixels_per_system_pixel; +        s.pixel_endx = s.pixel_startx + s.optical_pixels / ccd_pixels_per_system_pixel; + +        s.pixel_startx /= s.hwdpi_divisor; +        s.pixel_endx /= s.hwdpi_divisor; + +        // in case of stagger we have to start at an odd coordinate +        bool stagger_starts_even = dev->model->model_id == ModelId::CANON_8400F; +        if (s.num_staggered_lines > 0) { +            if (!stagger_starts_even && (s.pixel_startx & 1) == 0) { +                s.pixel_startx++; +                s.pixel_endx++; +            } else if (stagger_starts_even && (s.pixel_startx & 1) != 0) { +                s.pixel_startx++; +                s.pixel_endx++; +            } +        } + +    } else if (dev->model->asic_type == AsicType::GL845 || +               dev->model->asic_type == AsicType::GL846 || +               dev->model->asic_type == AsicType::GL847) +    { +        s.pixel_startx = s.params.startx; + +        if (s.num_staggered_lines > 0) { +            s.pixel_startx |= 1; +        } + +        s.pixel_startx += sensor.ccd_start_xoffset * ccd_pixels_per_system_pixel; +        s.pixel_endx = s.pixel_startx + s.optical_pixels_raw; + +        s.pixel_startx /= s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel; +        s.pixel_endx /= s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel; + +    } else if (dev->model->asic_type == AsicType::GL124) { +        s.pixel_startx = s.params.startx; + +        if (s.num_staggered_lines > 0) { +            s.pixel_startx |= 1; +        } + +        s.pixel_startx /= ccd_pixels_per_system_pixel; +        // FIXME: should we add sensor.dummy_pxel to pixel_startx at this point? +        s.pixel_endx = s.pixel_startx + s.optical_pixels / ccd_pixels_per_system_pixel; + +        s.pixel_startx /= s.hwdpi_divisor * s.segment_count; +        s.pixel_endx /= s.hwdpi_divisor * s.segment_count; + +        std::uint32_t segcnt = (sensor.custom_regs.get_value(gl124::REG_SEGCNT) << 16) + +                               (sensor.custom_regs.get_value(gl124::REG_SEGCNT + 1) << 8) + +                                sensor.custom_regs.get_value(gl124::REG_SEGCNT + 2); +        if (s.pixel_endx == segcnt) { +            s.pixel_endx = 0; +        } +    } + +    s.pixel_count_multiplier = sensor.pixel_count_multiplier; + +    s.pixel_startx *= sensor.pixel_count_multiplier; +    s.pixel_endx *= sensor.pixel_count_multiplier; +} + +void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); + +    (void) dev; +    s.params.assert_valid(); + +    if (s.params.depth != 8 && s.params.depth != 16) { +        throw SaneException("Unsupported depth setting %d", s.params.depth); +    } + +    unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); + +    // compute optical and output resolutions + +    if (dev->model->asic_type == AsicType::GL843) { +        // FIXME: this may be incorrect, but need more scanners to test +        s.hwdpi_divisor = sensor.get_hwdpi_divisor_for_dpi(s.params.xres); +    } else { +        s.hwdpi_divisor = sensor.get_hwdpi_divisor_for_dpi(s.params.xres * ccd_pixels_per_system_pixel); +    } + +    s.ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(s.params.xres); + +    if (dev->model->asic_type == AsicType::GL646) { +        s.optical_resolution = sensor.optical_res; +    } else { +        s.optical_resolution = sensor.optical_res / s.ccd_size_divisor; +    } +    s.output_resolution = s.params.xres; + +    if (s.output_resolution > s.optical_resolution) { +        throw std::runtime_error("output resolution higher than optical resolution"); +    } + +    // compute the number of optical pixels that will be acquired by the chip +    s.optical_pixels = (s.params.pixels * s.optical_resolution) / s.output_resolution; +    if (s.optical_pixels * s.output_resolution < s.params.pixels * s.optical_resolution) { +        s.optical_pixels++; +    } + +    if (dev->model->asic_type == AsicType::GL841) { +        if (s.optical_pixels & 1) +            s.optical_pixels++; +    } + +    if (dev->model->asic_type == AsicType::GL646 && s.params.xres == 400) { +        s.optical_pixels = (s.optical_pixels / 6) * 6; +    } + +    if (dev->model->asic_type == AsicType::GL843) { +        // ensure the number of optical pixels is divisible by 2. +        // In quarter-CCD mode optical_pixels is 4x larger than the actual physical number +        s.optical_pixels = align_int_up(s.optical_pixels, 2 * s.ccd_size_divisor); + +        if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || +            dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || +            dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) +        { +            s.optical_pixels = align_int_up(s.optical_pixels, 16); +        } +    } + +    // after all adjustments on the optical pixels have been made, compute the number of pixels +    // to retrieve from the chip +    s.output_pixels = (s.optical_pixels * s.output_resolution) / s.optical_resolution; + +    // Note: staggering is not applied for calibration. Staggering starts at 2400 dpi +    s.num_staggered_lines = 0; +    if (!has_flag(s.params.flags, ScanFlag::IGNORE_LINE_DISTANCE)) +    { +        s.num_staggered_lines = sensor.stagger_config.stagger_at_resolution(s.params.xres, +                                                                            s.params.yres); +    } + +    s.color_shift_lines_r = dev->model->ld_shift_r; +    s.color_shift_lines_g = dev->model->ld_shift_g; +    s.color_shift_lines_b = dev->model->ld_shift_b; + +    if (dev->model->motor_id == MotorId::G4050 && s.params.yres > 600) { +        // it seems base_dpi of the G4050 motor is changed above 600 dpi +        s.color_shift_lines_r = (s.color_shift_lines_r * 3800) / dev->motor.base_ydpi; +        s.color_shift_lines_g = (s.color_shift_lines_g * 3800) / dev->motor.base_ydpi; +        s.color_shift_lines_b = (s.color_shift_lines_b * 3800) / dev->motor.base_ydpi; +    } + +    s.color_shift_lines_r = (s.color_shift_lines_r * s.params.yres) / dev->motor.base_ydpi; +    s.color_shift_lines_g = (s.color_shift_lines_g * s.params.yres) / dev->motor.base_ydpi; +    s.color_shift_lines_b = (s.color_shift_lines_b * s.params.yres) / dev->motor.base_ydpi; + +    s.max_color_shift_lines = 0; +    if (s.params.channels > 1 && !has_flag(s.params.flags, ScanFlag::IGNORE_LINE_DISTANCE)) { +        s.max_color_shift_lines = std::max(s.color_shift_lines_r, std::max(s.color_shift_lines_g, +                                                                           s.color_shift_lines_b)); +    } + +    s.output_line_count = s.params.lines + s.max_color_shift_lines + s.num_staggered_lines; + +    s.output_channel_bytes = multiply_by_depth_ceil(s.output_pixels, s.params.depth); +    s.output_line_bytes = s.output_channel_bytes * s.params.channels; + +    s.segment_count = sensor.get_segment_count(); + +    s.optical_pixels_raw = s.optical_pixels; +    s.output_line_bytes_raw = s.output_line_bytes; +    s.conseq_pixel_dist = 0; + +    if (dev->model->asic_type == AsicType::GL845 || +        dev->model->asic_type == AsicType::GL846 || +        dev->model->asic_type == AsicType::GL847) +    { +        if (s.segment_count > 1) { +            s.conseq_pixel_dist = sensor.segment_size; + +            // in case of multi-segments sensor, we have to add the width of the sensor crossed by +            // the scan area +            unsigned extra_segment_scan_area = align_multiple_ceil(s.conseq_pixel_dist, 2); +            extra_segment_scan_area *= s.segment_count - 1; +            extra_segment_scan_area *= s.hwdpi_divisor * s.segment_count; +            extra_segment_scan_area *= ccd_pixels_per_system_pixel; + +            s.optical_pixels_raw += extra_segment_scan_area; +        } + +        s.output_line_bytes_raw = multiply_by_depth_ceil( +                    (s.optical_pixels_raw * s.output_resolution) / sensor.optical_res / s.segment_count, +                    s.params.depth); +    } + +    if (dev->model->asic_type == AsicType::GL841) { +        if (dev->model->is_cis) { +            s.output_line_bytes_raw = s.output_channel_bytes; +        } +    } + +    if (dev->model->asic_type == AsicType::GL124) { +        if (dev->model->is_cis) { +            s.output_line_bytes_raw = s.output_channel_bytes; +        } +        s.conseq_pixel_dist = s.output_pixels / s.ccd_size_divisor / s.segment_count; +    } + +    if (dev->model->asic_type == AsicType::GL843) { +        s.conseq_pixel_dist = s.output_pixels / s.segment_count; +    } + +    s.output_segment_pixel_group_count = 0; +    if (dev->model->asic_type == AsicType::GL124 || +        dev->model->asic_type == AsicType::GL843) +    { +        s.output_segment_pixel_group_count = multiply_by_depth_ceil( +            s.output_pixels / s.ccd_size_divisor / s.segment_count, s.params.depth); +    } +    if (dev->model->asic_type == AsicType::GL845 || +        dev->model->asic_type == AsicType::GL846 || +        dev->model->asic_type == AsicType::GL847) +    { +        s.output_segment_pixel_group_count = multiply_by_depth_ceil( +            s.optical_pixels / (s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel), +            s.params.depth); +    } + +    s.output_line_bytes_requested = multiply_by_depth_ceil( +            s.params.get_requested_pixels() * s.params.channels, s.params.depth); + +    s.output_total_bytes_raw = s.output_line_bytes_raw * s.output_line_count; +    s.output_total_bytes = s.output_line_bytes * s.output_line_count; + +    compute_session_buffer_sizes(dev->model->asic_type, s); +    compute_session_pipeline(dev, s); +    compute_session_pixel_offsets(dev, s, sensor); + +    if (dev->model->asic_type == AsicType::GL124 || +        dev->model->asic_type == AsicType::GL845 || +        dev->model->asic_type == AsicType::GL846) +    { +        s.enable_ledadd = (s.params.channels == 1 && dev->model->is_cis && dev->settings.true_gray); +    } + +    if (dev->model->asic_type == AsicType::GL841 || +        dev->model->asic_type == AsicType::GL843) +    { +        // no 16 bit gamma for this ASIC +        if (s.params.depth == 16) { +            s.params.flags |= ScanFlag::DISABLE_GAMMA; +        } +    } + +    s.computed = true; + +    DBG(DBG_info, "%s ", __func__); +    debug_dump(DBG_info, s); +} + +static std::size_t get_usb_buffer_read_size(AsicType asic, const ScanSession& session) +{ +    switch (asic) { +        case AsicType::GL646: +            // buffer not used on this chip set +            return 1; + +        case AsicType::GL124: +            // BUG: we shouldn't multiply by channels here nor divide by ccd_size_divisor +            return session.output_line_bytes_raw / session.ccd_size_divisor * session.params.channels; + +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +            // BUG: we shouldn't multiply by channels here +            return session.output_line_bytes_raw * session.params.channels; + +        case AsicType::GL843: +            return session.output_line_bytes_raw * 2; + +        default: +            throw SaneException("Unknown asic type"); +    } +} + +static FakeBufferModel get_fake_usb_buffer_model(const ScanSession& session) +{ +    FakeBufferModel model; +    model.push_step(session.buffer_size_read, 1); + +    if (session.pipeline_needs_reorder) { +        model.push_step(session.buffer_size_lines, session.output_line_bytes); +    } +    if (session.pipeline_needs_ccd) { +        model.push_step(session.buffer_size_shrink, session.output_line_bytes); +    } +    if (session.pipeline_needs_shrink) { +        model.push_step(session.buffer_size_out, session.output_line_bytes); +    } + +    return model; +} + +void build_image_pipeline(Genesys_Device* dev, const ScanSession& session) +{ +    static unsigned s_pipeline_index = 0; + +    s_pipeline_index++; + +    auto format = create_pixel_format(session.params.depth, +                                      dev->model->is_cis ? 1 : session.params.channels, +                                      dev->model->line_mode_color_order); +    auto depth = get_pixel_format_depth(format); +    auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw); + +    auto read_data_from_usb = [dev](std::size_t size, std::uint8_t* data) +    { +        dev->interface->bulk_read_data(0x45, data, size); +        return true; +    }; + +    auto lines = session.output_line_count * (dev->model->is_cis ? session.params.channels : 1); + +    dev->pipeline.clear(); + +    // FIXME: here we are complicating things for the time being to preserve the existing behaviour +    // This allows to be sure that the changes to the image pipeline have not introduced +    // regressions. + +    if (session.segment_count > 1) { +        // BUG: we're reading one line too much +        dev->pipeline.push_first_node<ImagePipelineNodeBufferedCallableSource>( +                width, lines + 1, format, +                get_usb_buffer_read_size(dev->model->asic_type, session), read_data_from_usb); + +        auto output_width = session.output_segment_pixel_group_count * session.segment_count; +        dev->pipeline.push_node<ImagePipelineNodeDesegment>(output_width, dev->segment_order, +                                                            session.conseq_pixel_dist, +                                                            1, 1); +    } else { +        auto read_bytes_left_after_deseg = session.output_line_bytes * session.output_line_count; +        if (dev->model->asic_type == AsicType::GL646) { +            read_bytes_left_after_deseg *= dev->model->is_cis ? session.params.channels : 1; +        } + +        dev->pipeline.push_first_node<ImagePipelineNodeBufferedGenesysUsb>( +                width, lines, format, read_bytes_left_after_deseg, +                get_fake_usb_buffer_model(session), read_data_from_usb); +    } + +    if (DBG_LEVEL >= DBG_io2) { +        dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + +                                                        std::to_string(s_pipeline_index) + +                                                        "_0_before_swap.pnm"); +    } + +    if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && depth == 16) { +        dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); +    } + +#ifdef WORDS_BIGENDIAN +    if (depth == 16) { +        dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); +    } +#endif + +    if (DBG_LEVEL >= DBG_io2) { +        dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + +                                                        std::to_string(s_pipeline_index) + +                                                        "_1_after_swap.pnm"); +    } + +    if (dev->model->is_cis && session.params.channels == 3) { +        dev->pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order); +    } + +    if (dev->pipeline.get_output_format() == PixelFormat::BGR888) { +        dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); +    } + +    if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) { +        dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); +    } + +    if (session.max_color_shift_lines > 0 && session.params.channels == 3) { +        dev->pipeline.push_node<ImagePipelineNodeComponentShiftLines>( +                    session.color_shift_lines_r, +                    session.color_shift_lines_g, +                    session.color_shift_lines_b); +    } + +    if (DBG_LEVEL >= DBG_io2) { +        dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + +                                                        std::to_string(s_pipeline_index) + +                                                        "_2_after_shift.pnm"); +    } + +    if (session.num_staggered_lines > 0) { +        std::vector<std::size_t> shifts{0, session.num_staggered_lines}; +        dev->pipeline.push_node<ImagePipelineNodePixelShiftLines>(shifts); +    } + +    if (DBG_LEVEL >= DBG_io2) { +        dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + +                                                        std::to_string(s_pipeline_index) + +                                                        "_3_after_stagger.pnm"); +    } + +    if ((dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) && +        !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) +    { +        dev->pipeline.push_node<ImagePipelineNodeCalibrate>(dev->dark_average_data, +                                                            dev->white_average_data); + +        if (DBG_LEVEL >= DBG_io2) { +            dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + +                                                            std::to_string(s_pipeline_index) + +                                                            "_4_after_calibrate.pnm"); +        } +    } + +    if (session.output_pixels != session.params.get_requested_pixels()) { +        dev->pipeline.push_node<ImagePipelineNodeScaleRows>(session.params.get_requested_pixels()); +    } + +    auto read_from_pipeline = [dev](std::size_t size, std::uint8_t* out_data) +    { +        (void) size; // will be always equal to dev->pipeline.get_output_row_bytes() +        return dev->pipeline.get_next_row_data(out_data); +    }; +    dev->pipeline_buffer = ImageBuffer{dev->pipeline.get_output_row_bytes(), +                                       read_from_pipeline}; +} + +std::uint8_t compute_frontend_gain_wolfson(float value, float target_value) +{ +    /*  the flow of data through the frontend ADC is as follows (see e.g. WM8192 datasheet) +        input +        -> apply offset (o = i + 260mV * (DAC[7:0]-127.5)/127.5) -> +        -> apply gain (o = i * 208/(283-PGA[7:0]) +        -> ADC + +        Here we have some input data that was acquired with zero gain (PGA==0). +        We want to compute gain such that the output would approach full ADC range (controlled by +        target_value). + +        We want to solve the following for {PGA}: + +        {value}         = {input} * 208 / (283 - 0) +        {target_value}  = {input} * 208 / (283 - {PGA}) + +        The solution is the following equation: + +        {PGA} = 283 * (1 - {value} / {target_value}) +    */ +    float gain = value / target_value; +    int code = static_cast<int>(283 * (1 - gain)); +    return clamp(code, 0, 255); +} + +std::uint8_t compute_frontend_gain_analog_devices(float value, float target_value) +{ +    /*  The flow of data through the frontend ADC is as follows (see e.g. AD9826 datasheet) +        input +        -> apply offset (o = i + 300mV * (OFFSET[8] ? 1 : -1) * (OFFSET[7:0] / 127) +        -> apply gain (o = i * 6 / (1 + 5 * ( 63 - PGA[5:0] ) / 63 ) ) +        -> ADC + +        We want to solve the following for {PGA}: + +        {value}         = {input} * 6 / (1 + 5 * ( 63 - 0) / 63 ) ) +        {target_value}  = {input} * 6 / (1 + 5 * ( 63 - {PGA}) / 63 ) ) + +        The solution is the following equation: + +        {PGA} = (378 / 5) * ({target_value} - {value} / {target_value}) +    */ +    int code = static_cast<int>((378.0f / 5.0f) * ((target_value - value) / target_value)); +    return clamp(code, 0, 63); +} + +std::uint8_t compute_frontend_gain(float value, float target_value, +                                   FrontendType frontend_type) +{ +    if (frontend_type == FrontendType::WOLFSON) { +        return compute_frontend_gain_wolfson(value, target_value); +    } +    if (frontend_type == FrontendType::ANALOG_DEVICES) { +        return compute_frontend_gain_analog_devices(value, target_value); +    } +    throw SaneException("Unknown frontend to compute gain for"); +} + +/** @brief initialize device + * Initialize backend and ASIC : registers, motor tables, and gamma tables + * then ensure scanner's head is at home. Designed for gl846+ ASICs. + * Detects cold boot (ie first boot since device plugged) in this case + * an extensice setup up is done at hardware level. + * + * @param dev device to initialize + * @param max_regs umber of maximum used registers + */ +void sanei_genesys_asic_init(Genesys_Device* dev, bool /*max_regs*/) +{ +    DBG_HELPER(dbg); + +  uint8_t val; +    bool cold = true; + +    // URB    16  control  0xc0 0x0c 0x8e 0x0b len     1 read  0x00 */ +    dev->interface->get_usb_device().control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, +                                                 VALUE_GET_REGISTER, 0x00, 1, &val); + +  DBG (DBG_io2, "%s: value=0x%02x\n", __func__, val); +  DBG (DBG_info, "%s: device is %s\n", __func__, (val & 0x08) ? "USB 1.0" : "USB2.0"); +  if (val & 0x08) +    { +      dev->usb_mode = 1; +    } +  else +    { +      dev->usb_mode = 2; +    } + +    /*  Check if the device has already been initialized and powered up. We read register 0x06 and +        check PWRBIT, if reset scanner has been freshly powered up. This bit will be set to later +        so that following reads can detect power down/up cycle +    */ +    if (!is_testing_mode()) { +        if (dev->interface->read_register(0x06) & 0x10) { +            cold = false; +        } +    } +  DBG (DBG_info, "%s: device is %s\n", __func__, cold ? "cold" : "warm"); + +  /* don't do anything if backend is initialized and hardware hasn't been +   * replug */ +  if (dev->already_initialized && !cold) +    { +      DBG (DBG_info, "%s: already initialized, nothing to do\n", __func__); +        return; +    } + +    // set up hardware and registers +    dev->cmd_set->asic_boot(dev, cold); + +  /* now hardware part is OK, set up device struct */ +  dev->white_average_data.clear(); +  dev->dark_average_data.clear(); + +  dev->settings.color_filter = ColorFilter::RED; + +  /* duplicate initial values into calibration registers */ +  dev->calib_reg = dev->reg; + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +    // Set analog frontend +    dev->cmd_set->set_fe(dev, sensor, AFE_INIT); + +    dev->already_initialized = true; + +    // Move to home if needed +    dev->cmd_set->move_back_home(dev, true); +    dev->set_head_pos_zero(ScanHeadId::PRIMARY); + +    // Set powersaving (default = 15 minutes) +    dev->cmd_set->set_powersaving(dev, 15); +} + +void scanner_start_action(Genesys_Device& dev, bool start_motor) +{ +    DBG_HELPER(dbg); +    switch (dev.model->asic_type) { +        case AsicType::GL646: +        case AsicType::GL841: +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported chip"); +    } + +    if (start_motor) { +        dev.interface->write_register(0x0f, 0x01); +    } else { +        dev.interface->write_register(0x0f, 0); +    } +} + +void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& sensor, +                             unsigned dpihw) +{ +    // same across GL646, GL841, GL843, GL846, GL847, GL124 +    const uint8_t REG_0x05_DPIHW_MASK = 0xc0; +    const uint8_t REG_0x05_DPIHW_600 = 0x00; +    const uint8_t REG_0x05_DPIHW_1200 = 0x40; +    const uint8_t REG_0x05_DPIHW_2400 = 0x80; +    const uint8_t REG_0x05_DPIHW_4800 = 0xc0; + +    if (sensor.register_dpihw_override != 0) { +        dpihw = sensor.register_dpihw_override; +    } + +    uint8_t dpihw_setting; +    switch (dpihw) { +        case 600: +            dpihw_setting = REG_0x05_DPIHW_600; +            break; +        case 1200: +            dpihw_setting = REG_0x05_DPIHW_1200; +            break; +        case 2400: +            dpihw_setting = REG_0x05_DPIHW_2400; +            break; +        case 4800: +            dpihw_setting = REG_0x05_DPIHW_4800; +            break; +        default: +            throw SaneException("Unknown dpihw value: %d", dpihw); +    } +    regs.set8_mask(0x05, dpihw_setting, REG_0x05_DPIHW_MASK); +} + +void regs_set_exposure(AsicType asic_type, Genesys_Register_Set& regs, +                       const SensorExposure& exposure) +{ +    switch (asic_type) { +        case AsicType::GL124: { +            regs.set24(gl124::REG_EXPR, exposure.red); +            regs.set24(gl124::REG_EXPG, exposure.green); +            regs.set24(gl124::REG_EXPB, exposure.blue); +            break; +        } +        case AsicType::GL646: { +            regs.set16(gl646::REG_EXPR, exposure.red); +            regs.set16(gl646::REG_EXPG, exposure.green); +            regs.set16(gl646::REG_EXPB, exposure.blue); +            break; +        } +        case AsicType::GL841: { +            regs.set16(gl841::REG_EXPR, exposure.red); +            regs.set16(gl841::REG_EXPG, exposure.green); +            regs.set16(gl841::REG_EXPB, exposure.blue); +            break; +        } +        case AsicType::GL843: { +            regs.set16(gl843::REG_EXPR, exposure.red); +            regs.set16(gl843::REG_EXPG, exposure.green); +            regs.set16(gl843::REG_EXPB, exposure.blue); +            break; +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            regs.set16(gl846::REG_EXPR, exposure.red); +            regs.set16(gl846::REG_EXPG, exposure.green); +            regs.set16(gl846::REG_EXPB, exposure.blue); +            break; +        } +        case AsicType::GL847: { +            regs.set16(gl847::REG_EXPR, exposure.red); +            regs.set16(gl847::REG_EXPG, exposure.green); +            regs.set16(gl847::REG_EXPB, exposure.blue); +            break; +        } +        default: +            throw SaneException("Unsupported asic"); +    } +} + +void regs_set_optical_off(AsicType asic_type, Genesys_Register_Set& regs) +{ +    DBG_HELPER(dbg); +    switch (asic_type) { +        case AsicType::GL646: { +            regs.find_reg(gl646::REG_0x01).value &= ~gl646::REG_0x01_SCAN; +            break; +        } +        case AsicType::GL841: { +            regs.find_reg(gl841::REG_0x01).value &= ~gl841::REG_0x01_SCAN; +            break; +        } +        case AsicType::GL843: { +            regs.find_reg(gl843::REG_0x01).value &= ~gl843::REG_0x01_SCAN; +            break; +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            regs.find_reg(gl846::REG_0x01).value &= ~gl846::REG_0x01_SCAN; +            break; +        } +        case AsicType::GL847: { +            regs.find_reg(gl847::REG_0x01).value &= ~gl847::REG_0x01_SCAN; +            break; +        } +        case AsicType::GL124: { +            regs.find_reg(gl124::REG_0x01).value &= ~gl124::REG_0x01_SCAN; +            break; +        } +        default: +            throw SaneException("Unsupported asic"); +    } +} + +bool get_registers_gain4_bit(AsicType asic_type, const Genesys_Register_Set& regs) +{ +    switch (asic_type) { +        case AsicType::GL646: +            return static_cast<bool>(regs.get8(gl646::REG_0x06) & gl646::REG_0x06_GAIN4); +        case AsicType::GL841: +            return static_cast<bool>(regs.get8(gl841::REG_0x06) & gl841::REG_0x06_GAIN4); +        case AsicType::GL843: +            return static_cast<bool>(regs.get8(gl843::REG_0x06) & gl843::REG_0x06_GAIN4); +        case AsicType::GL845: +        case AsicType::GL846: +            return static_cast<bool>(regs.get8(gl846::REG_0x06) & gl846::REG_0x06_GAIN4); +        case AsicType::GL847: +            return static_cast<bool>(regs.get8(gl847::REG_0x06) & gl847::REG_0x06_GAIN4); +        case AsicType::GL124: +            return static_cast<bool>(regs.get8(gl124::REG_0x06) & gl124::REG_0x06_GAIN4); +        default: +            throw SaneException("Unsupported chipset"); +    } +} + +/** + * Wait for the scanning head to park + */ +void sanei_genesys_wait_for_home(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +  /* clear the parking status whatever the outcome of the function */ +    dev->parking = false; + +    if (is_testing_mode()) { +        return; +    } + +    // read initial status, if head isn't at home and motor is on we are parking, so we wait. +    // gl847/gl124 need 2 reads for reliable results +    auto status = scanner_read_status(*dev); +    dev->interface->sleep_ms(10); +    status = scanner_read_status(*dev); + +    if (status.is_at_home) { +	  DBG (DBG_info, +	       "%s: already at home\n", __func__); +        return; +    } + +    unsigned timeout_ms = 200000; +    unsigned elapsed_ms = 0; +  do +    { +      dev->interface->sleep_ms(100); +        elapsed_ms += 100; + +        status = scanner_read_status(*dev); +    } while (elapsed_ms < timeout_ms && !status.is_at_home); + +  /* if after the timeout, head is still not parked, error out */ +    if (elapsed_ms >= timeout_ms && !status.is_at_home) { +        DBG (DBG_error, "%s: failed to reach park position in %dseconds\n", __func__, +             timeout_ms / 1000); +        throw SaneException(SANE_STATUS_IO_ERROR, "failed to reach park position"); +    } +} + +/** @brief motor profile + * search for the database of motor profiles and get the best one. Each + * profile is at full step and at a reference exposure. Use first entry + * by default. + * @param motors motor profile database + * @param motor_type motor id + * @param exposure exposure time + * @return a pointer to a Motor_Profile struct + */ +const Motor_Profile& sanei_genesys_get_motor_profile(const std::vector<Motor_Profile>& motors, +                                                     MotorId motor_id, int exposure) +{ +  int idx; + +  idx=-1; +    for (std::size_t i = 0; i < motors.size(); ++i) { +        // exact match +        if (motors[i].motor_id == motor_id && motors[i].exposure==exposure) { +            return motors[i]; +        } + +        // closest match +        if (motors[i].motor_id == motor_id) { +          /* if profile exposure is higher than the required one, +           * the entry is a candidate for the closest match */ +            if (motors[i].exposure == 0 || motors[i].exposure >= exposure) +            { +              if(idx<0) +                { +                  /* no match found yet */ +                  idx=i; +                } +              else +                { +                  /* test for better match */ +                  if(motors[i].exposure<motors[idx].exposure) +                    { +                      idx=i; +                    } +                } +            } +        } +    } + +  /* default fallback */ +  if(idx<0) +    { +      DBG (DBG_warn,"%s: using default motor profile\n",__func__); +      idx=0; +    } + +    return motors[idx]; +} + +MotorSlopeTable sanei_genesys_slope_table(AsicType asic_type, int dpi, int exposure, int base_dpi, +                                          unsigned step_multiplier, +                                          const Motor_Profile& motor_profile) +{ +    unsigned target_speed_w = ((exposure * dpi) / base_dpi); + +    auto table = create_slope_table(motor_profile.slope, target_speed_w, motor_profile.step_type, +                                    step_multiplier, 2 * step_multiplier, +                                    get_slope_table_max_size(asic_type)); +    return table; +} + +MotorSlopeTable create_slope_table_fastest(AsicType asic_type, unsigned step_multiplier, +                                           const Motor_Profile& motor_profile) +{ +    return create_slope_table(motor_profile.slope, motor_profile.slope.max_speed_w, +                              motor_profile.step_type, +                              step_multiplier, 2 * step_multiplier, +                              get_slope_table_max_size(asic_type)); +} + +/** @brief returns the lowest possible ydpi for the device + * Parses device entry to find lowest motor dpi. + * @param dev device description + * @return lowest motor resolution + */ +int sanei_genesys_get_lowest_ydpi(Genesys_Device *dev) +{ +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method); +    return resolution_settings.get_min_resolution_y(); +} + +/** @brief returns the lowest possible dpi for the device + * Parses device entry to find lowest motor or sensor dpi. + * @param dev device description + * @return lowest motor resolution + */ +int sanei_genesys_get_lowest_dpi(Genesys_Device *dev) +{ +    const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method); +    return std::min(resolution_settings.get_min_resolution_x(), +                    resolution_settings.get_min_resolution_y()); +} + +/** @brief check is a cache entry may be used + * Compares current settings with the cache entry and return + * true if they are compatible. + * A calibration cache is compatible if color mode and x dpi match the user + * requested scan. In the case of CIS scanners, dpi isn't a criteria. + * flatbed cache entries are considred too old and then expires if they + * are older than the expiration time option, forcing calibration at least once + * then given time. */ +bool sanei_genesys_is_compatible_calibration(Genesys_Device* dev, +                                             const ScanSession& session, +                                             const Genesys_Calibration_Cache* cache, +                                             bool for_overwrite) +{ +    DBG_HELPER(dbg); +#ifdef HAVE_SYS_TIME_H +  struct timeval time; +#endif + +    bool compatible = true; + +    const auto& dev_params = session.params; + +    if (dev_params.scan_method != cache->params.scan_method) { +        dbg.vlog(DBG_io, "incompatible: scan_method %d vs. %d\n", +                 static_cast<unsigned>(dev_params.scan_method), +                 static_cast<unsigned>(cache->params.scan_method)); +        compatible = false; +    } + +    if (dev_params.xres != cache->params.xres) { +        dbg.vlog(DBG_io, "incompatible: params.xres %d vs. %d\n", +                 dev_params.xres, cache->params.xres); +        compatible = false; +    } + +    if (dev_params.yres != cache->params.yres) { +        // exposure depends on selected sensor and we select the sensor according to yres +        dbg.vlog(DBG_io, "incompatible: params.yres %d vs. %d\n", +                 dev_params.yres, cache->params.yres); +        compatible = false; +    } + +    if (dev_params.channels != cache->params.channels) { +        // exposure depends on total number of pixels at least on gl841 +        dbg.vlog(DBG_io, "incompatible: params.channels %d vs. %d\n", +                 dev_params.channels, cache->params.channels); +        compatible = false; +    } + +    if (dev_params.startx != cache->params.startx) { +        // exposure depends on total number of pixels at least on gl841 +        dbg.vlog(DBG_io, "incompatible: params.startx %d vs. %d\n", +                 dev_params.startx, cache->params.startx); +        compatible = false; +    } + +    if (dev_params.pixels != cache->params.pixels) { +        // exposure depends on total number of pixels at least on gl841 +        dbg.vlog(DBG_io, "incompatible: params.pixels %d vs. %d\n", +                 dev_params.pixels, cache->params.pixels); +        compatible = false; +    } + +  if (!compatible) +    { +      DBG (DBG_proc, "%s: completed, non compatible cache\n", __func__); +      return false; +    } + +  /* a cache entry expires after afetr expiration time for non sheetfed scanners */ +  /* this is not taken into account when overwriting cache entries    */ +#ifdef HAVE_SYS_TIME_H +    if (!for_overwrite && dev->settings.expiration_time >=0) +    { +        gettimeofday(&time, nullptr); +      if ((time.tv_sec - cache->last_calibration > dev->settings.expiration_time*60) +          && !dev->model->is_sheetfed +          && (dev->settings.scan_method == ScanMethod::FLATBED)) +        { +          DBG (DBG_proc, "%s: expired entry, non compatible cache\n", __func__); +          return false; +        } +    } +#endif + +  return true; +} + +/** @brief build lookup table for digital enhancements + * Function to build a lookup table (LUT), often +   used by scanners to implement brightness/contrast/gamma +   or by backends to speed binarization/thresholding + +   offset and slope inputs are -127 to +127 + +   slope rotates line around central input/output val, +   0 makes horizontal line + +       pos           zero          neg +       .       x     .             .  x +       .      x      .             .   x +   out .     x       .xxxxxxxxxxx  .    x +       .    x        .             .     x +       ....x.......  ............  .......x.... +            in            in            in + +   offset moves line vertically, and clamps to output range +   0 keeps the line crossing the center of the table + +       high           low +       .   xxxxxxxx   . +       . x            . +   out x              .          x +       .              .        x +       ............   xxxxxxxx.... +            in             in + +   out_min/max provide bounds on output values, +   useful when building thresholding lut. +   0 and 255 are good defaults otherwise. +  * @param lut pointer where to store the generated lut +  * @param in_bits number of bits for in values +  * @param out_bits number of bits of out values +  * @param out_min minimal out value +  * @param out_max maximal out value +  * @param slope slope of the generated data +  * @param offset offset of the generated data +  */ +void sanei_genesys_load_lut(unsigned char* lut, +                            int in_bits, int out_bits, +                            int out_min, int out_max, +                            int slope, int offset) +{ +    DBG_HELPER(dbg); +  int i, j; +  double shift, rise; +  int max_in_val = (1 << in_bits) - 1; +  int max_out_val = (1 << out_bits) - 1; +  uint8_t *lut_p8 = lut; +    uint16_t* lut_p16 = reinterpret_cast<std::uint16_t*>(lut); + +  /* slope is converted to rise per unit run: +   * first [-127,127] to [-.999,.999] +   * then to [-PI/4,PI/4] then [0,PI/2] +   * then take the tangent (T.O.A) +   * then multiply by the normal linear slope +   * because the table may not be square, i.e. 1024x256*/ +    auto pi_4 = M_PI / 4.0; +    rise = std::tan(static_cast<double>(slope) / 128 * pi_4 + pi_4) * max_out_val / max_in_val; + +  /* line must stay vertically centered, so figure +   * out vertical offset at central input value */ +    shift = static_cast<double>(max_out_val) / 2 - (rise * max_in_val / 2); + +  /* convert the user offset setting to scale of output +   * first [-127,127] to [-1,1] +   * then to [-max_out_val/2,max_out_val/2]*/ +    shift += static_cast<double>(offset) / 127 * max_out_val / 2; + +  for (i = 0; i <= max_in_val; i++) +    { +        j = static_cast<int>(rise * i + shift); + +      /* cap data to required range */ +      if (j < out_min) +	{ +	  j = out_min; +	} +      else if (j > out_max) +	{ +	  j = out_max; +	} + +      /* copy result according to bit depth */ +      if (out_bits <= 8) +	{ +	  *lut_p8 = j; +	  lut_p8++; +	} +      else +	{ +	  *lut_p16 = j; +	  lut_p16++; +	} +    } +} + +} // namespace genesys diff --git a/backend/genesys/low.h b/backend/genesys/low.h new file mode 100644 index 0000000..d7f5dd2 --- /dev/null +++ b/backend/genesys/low.h @@ -0,0 +1,525 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003 Oliver Rauch +   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004, 2005 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) 2006 Laurent Charpentier <laurent_pubs@yahoo.com> +   Parts of the structs have been taken from the gt68xx backend by +   Sergey Vlasov <vsu@altlinux.ru> et al. + +   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. +*/ + +#ifndef GENESYS_LOW_H +#define GENESYS_LOW_H + + +#include "../include/sane/config.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <math.h> +#include <stddef.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_MKDIR +#include <sys/stat.h> +#include <sys/types.h> +#endif + +#include "../include/sane/sane.h" +#include "../include/sane/sanei.h" +#include "../include/sane/saneopts.h" + +#include "../include/sane/sanei_backend.h" +#include "../include/sane/sanei_usb.h" + +#include "../include/_stdint.h" + +#include "device.h" +#include "enums.h" +#include "error.h" +#include "fwd.h" +#include "usb_device.h" +#include "sensor.h" +#include "serialize.h" +#include "settings.h" +#include "static_init.h" +#include "status.h" +#include "register.h" + +#include <algorithm> +#include <array> +#include <cstring> +#include <functional> +#include <iostream> +#include <sstream> +#include <limits> +#include <memory> +#include <stdexcept> +#include <string> +#include <vector> + +#define GENESYS_RED   0 +#define GENESYS_GREEN 1 +#define GENESYS_BLUE  2 + +/* Flags */ +#define GENESYS_FLAG_UNTESTED     (1 << 0)	/**< Print a warning for these scanners */ +#define GENESYS_FLAG_14BIT_GAMMA  (1 << 1)	/**< use 14bit Gamma table instead of 12 */ +#define GENESYS_FLAG_XPA          (1 << 3) +#define GENESYS_FLAG_SKIP_WARMUP  (1 << 4)	/**< skip genesys_warmup()              */ +/** @brief offset calibration flag + * signals that the scanner does offset calibration. In this case off_calibration() and + * coarse_gain_calibration() functions must be implemented + */ +#define GENESYS_FLAG_OFFSET_CALIBRATION   (1 << 5) +#define GENESYS_FLAG_SEARCH_START (1 << 6)	/**< do start search before scanning    */ +#define GENESYS_FLAG_REPARK       (1 << 7)	/**< repark head (and check for lock) by +						   moving without scanning */ +#define GENESYS_FLAG_DARK_CALIBRATION (1 << 8)	/**< do dark calibration */ + +#define GENESYS_FLAG_MUST_WAIT        (1 << 10)	/**< tells wether the scanner must wait for the head when parking */ + + +#define GENESYS_FLAG_HAS_UTA          (1 << 11)	/**< scanner has a transparency adapter */ + +#define GENESYS_FLAG_DARK_WHITE_CALIBRATION (1 << 12) /**< yet another calibration method. does white and dark shading in one run, depending on a black and a white strip*/ +#define GENESYS_FLAG_CUSTOM_GAMMA     (1 << 13)       /**< allow custom gamma tables */ +#define GENESYS_FLAG_NO_CALIBRATION   (1 << 14)       /**< allow scanners to use skip the calibration, needed for sheetfed scanners */ +#define GENESYS_FLAG_SIS_SENSOR       (1 << 16)       /**< handling of multi-segments sensors in software */ +#define GENESYS_FLAG_SHADING_NO_MOVE  (1 << 17)       /**< scanner doesn't move sensor during shading calibration */ +#define GENESYS_FLAG_SHADING_REPARK   (1 << 18)       /**< repark head between shading scans */ +#define GENESYS_FLAG_FULL_HWDPI_MODE  (1 << 19)       /**< scanner always use maximum hw dpi to setup the sensor */ +// scanner has infrared transparency scanning capability +#define GENESYS_FLAG_HAS_UTA_INFRARED (1 << 20) +// scanner calibration is handled on the host side +#define GENESYS_FLAG_CALIBRATION_HOST_SIDE (1 << 21) +#define GENESYS_FLAG_16BIT_DATA_INVERTED (1 << 22) + +#define GENESYS_HAS_NO_BUTTONS       0              /**< scanner has no supported button */ +#define GENESYS_HAS_SCAN_SW          (1 << 0)       /**< scanner has SCAN button */ +#define GENESYS_HAS_FILE_SW          (1 << 1)       /**< scanner has FILE button */ +#define GENESYS_HAS_COPY_SW          (1 << 2)       /**< scanner has COPY button */ +#define GENESYS_HAS_EMAIL_SW         (1 << 3)       /**< scanner has EMAIL button */ +#define GENESYS_HAS_PAGE_LOADED_SW   (1 << 4)       /**< scanner has paper in detection */ +#define GENESYS_HAS_OCR_SW           (1 << 5)       /**< scanner has OCR button */ +#define GENESYS_HAS_POWER_SW         (1 << 6)       /**< scanner has power button */ +#define GENESYS_HAS_CALIBRATE        (1 << 7)       /**< scanner has 'calibrate' software button to start calibration */ +#define GENESYS_HAS_EXTRA_SW         (1 << 8)       /**< scanner has extra function button */ + +/* USB control message values */ +#define REQUEST_TYPE_IN		(USB_TYPE_VENDOR | USB_DIR_IN) +#define REQUEST_TYPE_OUT	(USB_TYPE_VENDOR | USB_DIR_OUT) +#define REQUEST_REGISTER	0x0c +#define REQUEST_BUFFER		0x04 +#define VALUE_BUFFER		0x82 +#define VALUE_SET_REGISTER	0x83 +#define VALUE_READ_REGISTER	0x84 +#define VALUE_WRITE_REGISTER	0x85 +#define VALUE_INIT		0x87 +#define GPIO_OUTPUT_ENABLE	0x89 +#define GPIO_READ		0x8a +#define GPIO_WRITE		0x8b +#define VALUE_BUF_ENDACCESS	0x8c +#define VALUE_GET_REGISTER	0x8e +#define INDEX			0x00 + +/* todo: used? +#define VALUE_READ_STATUS	0x86 +*/ + +/* Read/write bulk data/registers */ +#define BULK_OUT		0x01 +#define BULK_IN			0x00 +#define BULK_RAM		0x00 +#define BULK_REGISTER		0x11 + +#define BULKOUT_MAXSIZE         0xF000 + +/* AFE values */ +#define AFE_INIT       1 +#define AFE_SET        2 +#define AFE_POWER_SAVE 4 + +#define LOWORD(x)  ((uint16_t)((x) & 0xffff)) +#define HIWORD(x)  ((uint16_t)((x) >> 16)) +#define LOBYTE(x)  ((uint8_t)((x) & 0xFF)) +#define HIBYTE(x)  ((uint8_t)((x) >> 8)) + +/* Global constants */ +/* TODO: emove this leftover of early backend days */ +#define MOTOR_SPEED_MAX		350 +#define DARK_VALUE		0 + +#define MAX_RESOLUTIONS 13 +#define MAX_DPI 4 + +namespace genesys { + +struct Genesys_USB_Device_Entry { + +    Genesys_USB_Device_Entry(unsigned v, unsigned p, const Genesys_Model& m) : +        vendor(v), product(p), model(m) +    {} + +    // USB vendor identifier +    std::uint16_t vendor; +    // USB product identifier +    std::uint16_t product; +    // Scanner model information +    Genesys_Model model; +}; + +/** + * structure for motor database + */ +struct Motor_Profile +{ +    MotorId motor_id; +    int exposure;           // used only to select the wanted motor +    StepType step_type;   // default step type for given exposure +    MotorSlope slope; +}; + +extern StaticInit<std::vector<Motor_Profile>> gl843_motor_profiles; +extern StaticInit<std::vector<Motor_Profile>> gl846_motor_profiles; +extern StaticInit<std::vector<Motor_Profile>> gl847_motor_profiles; +extern StaticInit<std::vector<Motor_Profile>> gl124_motor_profiles; + +/*--------------------------------------------------------------------------*/ +/*       common functions needed by low level specific functions            */ +/*--------------------------------------------------------------------------*/ + +inline GenesysRegister* sanei_genesys_get_address(Genesys_Register_Set* regs, uint16_t addr) +{ +    auto* ret = regs->find_reg_address(addr); +    if (ret == nullptr) { +        DBG(DBG_error, "%s: failed to find address for register 0x%02x, crash expected !\n", +            __func__, addr); +    } +    return ret; +} + +extern void sanei_genesys_init_cmd_set(Genesys_Device* dev); + +// reads the status of the scanner +Status scanner_read_status(Genesys_Device& dev); + +// reads the status of the scanner reliably. This is done by reading the status twice. The first +// read sometimes returns the home sensor as engaged when this is not true. +Status scanner_read_reliable_status(Genesys_Device& dev); + +// reads and prints the scanner status +void scanner_read_print_status(Genesys_Device& dev); + +void debug_print_status(DebugMessageHelper& dbg, Status status); + +extern void sanei_genesys_write_ahb(Genesys_Device* dev, uint32_t addr, uint32_t size, +                                    uint8_t* data); + +extern void sanei_genesys_init_structs (Genesys_Device * dev); + +const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev); +const Genesys_Sensor& sanei_genesys_find_sensor(Genesys_Device* dev, unsigned dpi, +                                                unsigned channels, ScanMethod scan_method); +bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels, +                              ScanMethod scan_method); +Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigned dpi, +                                                    unsigned channels, ScanMethod scan_method); + +std::vector<std::reference_wrapper<const Genesys_Sensor>> +    sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method); +std::vector<std::reference_wrapper<Genesys_Sensor>> +    sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method); + +extern void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            int pixels_per_line); + +extern void sanei_genesys_read_valid_words(Genesys_Device* dev, unsigned int* steps); + +extern void sanei_genesys_read_scancnt(Genesys_Device* dev, unsigned int* steps); + +extern void sanei_genesys_read_feed_steps(Genesys_Device* dev, unsigned int* steps); + +void sanei_genesys_set_lamp_power(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                  Genesys_Register_Set& regs, bool set); + +void sanei_genesys_set_motor_power(Genesys_Register_Set& regs, bool set); + +bool should_enable_gamma(const ScanSession& session, const Genesys_Sensor& sensor); + +/** Calculates the values of the Z{1,2}MOD registers. They are a phase correction to synchronize +    with the line clock during acceleration and deceleration. + +    two_table is true if moving is done by two tables, false otherwise. + +    acceleration_steps is the number of steps for acceleration, i.e. the number written to +    REG_STEPNO. + +    move_steps number of steps to move, i.e. the number written to REG_FEEDL. + +    buffer_acceleration_steps, the number of steps for acceleration when buffer condition is met, +    i.e. the number written to REG_FWDSTEP. +*/ +void sanei_genesys_calculate_zmod(bool two_table, +                                  uint32_t exposure_time, +                                  const std::vector<uint16_t>& slope_table, +                                  unsigned acceleration_steps, +                                  unsigned move_steps, +                                  unsigned buffer_acceleration_steps, +                                  uint32_t* out_z1, uint32_t* out_z2); + +extern void sanei_genesys_set_buffer_address(Genesys_Device* dev, uint32_t addr); + +unsigned sanei_genesys_get_bulk_max_size(AsicType asic_type); + +SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, float ydpi, StepType step_type, +                                      int endpixel, int led_exposure); + +MotorSlopeTable sanei_genesys_create_slope_table3(AsicType asic_type, const Genesys_Motor& motor, +                                                  StepType step_type, int exposure_time, +                                                  unsigned yres); + +void sanei_genesys_create_default_gamma_table(Genesys_Device* dev, +                                              std::vector<uint16_t>& gamma_table, float gamma); + +std::vector<uint16_t> get_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                      int color); + +void sanei_genesys_send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor); + +extern void sanei_genesys_stop_motor(Genesys_Device* dev); + +extern void sanei_genesys_search_reference_point(Genesys_Device* dev, Genesys_Sensor& sensor, +                                                 const uint8_t* src_data, int start_pixel, int dpi, +                                                 int width, int height); + +// moves the scan head by the specified steps at the motor base dpi +void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, Direction direction); + +void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home); +void scanner_move_back_home_ta(Genesys_Device& dev); + +void scanner_clear_scan_and_feed_counts(Genesys_Device& dev); + +extern void sanei_genesys_write_file(const char* filename, const std::uint8_t* data, +                                     std::size_t length); + +extern void sanei_genesys_write_pnm_file(const char* filename, const std::uint8_t* data, int depth, +                                         int channels, int pixels_per_line, int lines); + +void sanei_genesys_write_pnm_file(const char* filename, const Image& image); + +extern void sanei_genesys_write_pnm_file16(const char* filename, const uint16_t *data, unsigned channels, +                                           unsigned pixels_per_line, unsigned lines); + +void wait_until_buffer_non_empty(Genesys_Device* dev, bool check_status_twice = false); + +extern void sanei_genesys_read_data_from_scanner(Genesys_Device* dev, uint8_t* data, size_t size); + +Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& session, +                                         std::size_t total_bytes); + +void regs_set_exposure(AsicType asic_type, Genesys_Register_Set& regs, +                       const SensorExposure& exposure); + +void regs_set_optical_off(AsicType asic_type, Genesys_Register_Set& regs); + +void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& sensor, +                             unsigned dpihw); + +inline uint16_t sanei_genesys_fixup_exposure_value(uint16_t value) +{ +    if ((value & 0xff00) == 0) { +        value |= 0x100; +    } +    if ((value & 0x00ff) == 0) { +        value |= 0x1; +    } +    return value; +} + +inline SensorExposure sanei_genesys_fixup_exposure(SensorExposure exposure) +{ +    exposure.red = sanei_genesys_fixup_exposure_value(exposure.red); +    exposure.green = sanei_genesys_fixup_exposure_value(exposure.green); +    exposure.blue = sanei_genesys_fixup_exposure_value(exposure.blue); +    return exposure; +} + +bool get_registers_gain4_bit(AsicType asic_type, const Genesys_Register_Set& regs); + +extern void sanei_genesys_wait_for_home(Genesys_Device* dev); + +extern void sanei_genesys_asic_init(Genesys_Device* dev, bool cold); + +void scanner_start_action(Genesys_Device& dev, bool start_motor); +void scanner_stop_action(Genesys_Device& dev); +void scanner_stop_action_no_move(Genesys_Device& dev, Genesys_Register_Set& regs); + +bool scanner_is_motor_stopped(Genesys_Device& dev); + +const Motor_Profile& sanei_genesys_get_motor_profile(const std::vector<Motor_Profile>& motors, +                                                     MotorId motor_id, int exposure); + +MotorSlopeTable sanei_genesys_slope_table(AsicType asic_type, int dpi, int exposure, int base_dpi, +                                          unsigned step_multiplier, +                                          const Motor_Profile& motor_profile); + +MotorSlopeTable create_slope_table_fastest(AsicType asic_type, unsigned step_multiplier, +                                           const Motor_Profile& motor_profile); + +/** @brief find lowest motor resolution for the device. + * Parses the resolution list for motor and + * returns the lowest value. + * @param dev for which to find the lowest motor resolution + * @return the lowest available motor resolution for the device + */ +extern +int sanei_genesys_get_lowest_ydpi(Genesys_Device *dev); + +/** @brief find lowest resolution for the device. + * Parses the resolution list for motor and sensor and + * returns the lowest value. + * @param dev for which to find the lowest resolution + * @return the lowest available resolution for the device + */ +extern +int sanei_genesys_get_lowest_dpi(Genesys_Device *dev); + +bool sanei_genesys_is_compatible_calibration(Genesys_Device* dev, +                                             const ScanSession& session, +                                             const Genesys_Calibration_Cache* cache, +                                             bool for_overwrite); + +extern void sanei_genesys_load_lut(unsigned char* lut, +                                   int in_bits, int out_bits, +                                   int out_min, int out_max, +                                   int slope, int offset); + +extern void sanei_genesys_generate_gamma_buffer(Genesys_Device* dev, +                                    const Genesys_Sensor& sensor, +                                    int bits, +                                    int max, +                                    int size, +                                    uint8_t* gamma); + +void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Sensor& sensor); + +void build_image_pipeline(Genesys_Device* dev, const ScanSession& session); + +std::uint8_t compute_frontend_gain(float value, float target_value, +                                   FrontendType frontend_type); + +template<class T> +inline T abs_diff(T a, T b) +{ +    if (a < b) { +        return b - a; +    } else { +        return a - b; +    } +} + +inline uint64_t align_multiple_floor(uint64_t x, uint64_t multiple) +{ +    return (x / multiple) * multiple; +} + +inline uint64_t align_multiple_ceil(uint64_t x, uint64_t multiple) +{ +    return ((x + multiple - 1) / multiple) * multiple; +} + +inline uint64_t multiply_by_depth_ceil(uint64_t pixels, uint64_t depth) +{ +    if (depth == 1) { +        return (pixels / 8) + ((pixels % 8) ? 1 : 0); +    } else { +        return pixels * (depth / 8); +    } +} + +template<class T> +inline T clamp(const T& value, const T& lo, const T& hi) +{ +    if (value < lo) +        return lo; +    if (value > hi) +        return hi; +    return value; +} + +/*---------------------------------------------------------------------------*/ +/*                ASIC specific functions declarations                       */ +/*---------------------------------------------------------------------------*/ + +extern StaticInit<std::vector<Genesys_Sensor>> s_sensors; +extern StaticInit<std::vector<Genesys_Frontend>> s_frontends; +extern StaticInit<std::vector<Genesys_Gpo>> s_gpo; +extern StaticInit<std::vector<Genesys_Motor>> s_motors; +extern StaticInit<std::vector<Genesys_USB_Device_Entry>> s_usb_devices; + +void genesys_init_sensor_tables(); +void genesys_init_frontend_tables(); +void genesys_init_gpo_tables(); +void genesys_init_motor_tables(); +void genesys_init_motor_profile_tables(); +void genesys_init_usb_device_tables(); + +template<class T> +void debug_dump(unsigned level, const T& value) +{ +    std::stringstream out; +    out << value; +    DBG(level, "%s\n", out.str().c_str()); +} + +} // namespace genesys + +#endif /* not GENESYS_LOW_H */ diff --git a/backend/genesys/motor.cpp b/backend/genesys/motor.cpp new file mode 100644 index 0000000..910266a --- /dev/null +++ b/backend/genesys/motor.cpp @@ -0,0 +1,180 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "motor.h" +#include "utilities.h" +#include <cmath> + +namespace genesys { + +unsigned MotorSlope::get_table_step_shifted(unsigned step, StepType step_type) const +{ +    // first two steps are always equal to the initial speed +    if (step < 2) { +        return initial_speed_w >> static_cast<unsigned>(step_type); +    } +    step--; + +    float initial_speed_v = 1.0f / initial_speed_w; +    float speed_v = std::sqrt(initial_speed_v * initial_speed_v + 2 * acceleration * step); +    return static_cast<unsigned>(1.0f / speed_v) >> static_cast<unsigned>(step_type); +} + +float compute_acceleration_for_steps(unsigned initial_w, unsigned max_w, unsigned steps) +{ +    float initial_speed_v = 1.0f / static_cast<float>(initial_w); +    float max_speed_v = 1.0f / static_cast<float>(max_w); +    return (max_speed_v * max_speed_v - initial_speed_v * initial_speed_v) / (2 * steps); +} + + +MotorSlope MotorSlope::create_from_steps(unsigned initial_w, unsigned max_w, +                                         unsigned steps) +{ +    MotorSlope slope; +    slope.initial_speed_w = initial_w; +    slope.max_speed_w = max_w; +    slope.acceleration = compute_acceleration_for_steps(initial_w, max_w, steps); +    return slope; +} + +void MotorSlopeTable::slice_steps(unsigned count) +{ +    if (count >= table.size() || count > steps_count) { +        throw SaneException("Excepssive steps count"); +    } +    steps_count = count; +} + +unsigned get_slope_table_max_size(AsicType asic_type) +{ +    switch (asic_type) { +        case AsicType::GL646: +        case AsicType::GL841: return 255; +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: return 1024; +        default: +            throw SaneException("Unknown asic type"); +    } +} + +MotorSlopeTable create_slope_table(const MotorSlope& slope, unsigned target_speed_w, +                                   StepType step_type, unsigned steps_alignment, +                                   unsigned min_size, unsigned max_size) +{ +    DBG_HELPER_ARGS(dbg, "target_speed_w: %d, step_type: %d, steps_alignment: %d, min_size: %d", +                    target_speed_w, static_cast<unsigned>(step_type), steps_alignment, min_size); +    MotorSlopeTable table; + +    unsigned step_shift = static_cast<unsigned>(step_type); + +    unsigned target_speed_shifted_w = target_speed_w >> step_shift; +    unsigned max_speed_shifted_w = slope.max_speed_w >> step_shift; + +    if (target_speed_shifted_w < max_speed_shifted_w) { +        dbg.log(DBG_warn, "failed to reach target speed"); +    } + +    unsigned final_speed = std::max(target_speed_shifted_w, max_speed_shifted_w); + +    table.table.reserve(max_size); + +    while (table.table.size() < max_size - 1) { +        unsigned current = slope.get_table_step_shifted(table.table.size(), step_type); +        if (current <= final_speed) { +            break; +        } +        table.table.push_back(current); +        table.pixeltime_sum += current; +    } + +    // make sure the target speed (or the max speed if target speed is too high) is present in +    // the table +    table.table.push_back(final_speed); +    table.pixeltime_sum += table.table.back(); + +    // fill the table up to the specified size +    while (table.table.size() < max_size - 1 && +           (table.table.size() % steps_alignment != 0 || table.table.size() < min_size)) +    { +        table.table.push_back(table.table.back()); +        table.pixeltime_sum += table.table.back(); +    } + +    table.steps_count = table.table.size(); + +    // fill the rest of the table with the final speed +    table.table.resize(max_size, final_speed); + +    return table; +} + +std::ostream& operator<<(std::ostream& out, const MotorSlope& slope) +{ +    out << "MotorSlope{\n" +        << "    initial_speed_w: " << slope.initial_speed_w << '\n' +        << "    max_speed_w: " << slope.max_speed_w << '\n' +        << "    a: " << slope.acceleration << '\n' +        << '}'; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const Genesys_Motor& motor) +{ +    out << "Genesys_Motor{\n" +        << "    id: " << static_cast<unsigned>(motor.id) << '\n' +        << "    base_ydpi: " << motor.base_ydpi << '\n' +        << "    optical_ydpi: " << motor.optical_ydpi << '\n' +        << "    slopes: " +        << format_indent_braced_list(4, format_vector_indent_braced(4, "MotorSlope", +                                                                    motor.slopes)) +        << '}'; +    return out; +} + +} // namespace genesys diff --git a/backend/genesys/motor.h b/backend/genesys/motor.h new file mode 100644 index 0000000..d80da6d --- /dev/null +++ b/backend/genesys/motor.h @@ -0,0 +1,177 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_MOTOR_H +#define BACKEND_GENESYS_MOTOR_H + +#include <cstdint> +#include <vector> +#include "enums.h" + +namespace genesys { + +/*  Describes a motor acceleration curve. + +    Definitions: +        v - speed in steps per pixeltime +        w - speed in pixel times per step. w = 1 / v +        a - acceleration in steps per pixeltime squared +        s - distance travelled in steps +        t - time in pixeltime + +    The physical mode defines the curve in physical quantities. We asssume that the scanner head +    accelerates from standstill to the target speed uniformly. Then: + +    v(t) = v(0) + a * t                                                                         (2) + +    Where `a` is acceleration, `t` is time. Also we can calculate the travelled distance `s`: + +    s(t) = v(0) * t + a * t^2 / 2                                                               (3) + +    The actual motor slope is defined as the duration of each motor step. That means we need to +    define speed in terms of travelled distance. + +    Solving (3) for `t` gives: + +           sqrt( v(0)^2 + 2 * a * s ) - v(0) +    t(s) = ---------------------------------                                                    (4) +                          a + +    Combining (4) and (2) will yield: + +    v(s) = sqrt( v(0)^2 + 2 * a * s )                                                           (5) + +    The data in the slope struct MotorSlope corresponds to the above in the following way: + +    maximum_start_speed is `w(0) = 1/v(0)` + +    maximum_speed is defines maximum speed which should not be exceeded + +    minimum_steps is not used + +    g is `a` + +    Given the start and target speeds on a known motor curve, `a` can be computed as follows: + +        v(t1)^2 - v(t0)^2 +    a = -----------------                                                                       (6) +               2 * s + +    Here `v(t0)` and `v(t1)` are the start and target speeds and `s` is the number of step required +    to reach the target speeds. +*/ +struct MotorSlope +{ +    // initial speed in pixeltime per step +    unsigned initial_speed_w = 0; + +    // max speed in pixeltime per step +    unsigned max_speed_w = 0; + +    // maximum number of steps in the table +    unsigned max_step_count; + +    // acceleration in steps per pixeltime squared. +    float acceleration = 0; + +    unsigned get_table_step_shifted(unsigned step, StepType step_type) const; + +    static MotorSlope create_from_steps(unsigned initial_w, unsigned max_w, +                                        unsigned steps); +}; + +struct MotorSlopeTable +{ +    std::vector<std::uint16_t> table; +    unsigned steps_count = 0; +    unsigned pixeltime_sum = 0; + +    void slice_steps(unsigned count); +}; + +unsigned get_slope_table_max_size(AsicType asic_type); + +MotorSlopeTable create_slope_table(const MotorSlope& slope, unsigned target_speed_w, +                                   StepType step_type, unsigned steps_alignment, +                                   unsigned min_size, unsigned max_size); + +std::ostream& operator<<(std::ostream& out, const MotorSlope& slope); + + +struct Genesys_Motor +{ +    Genesys_Motor() = default; + +    // id of the motor description +    MotorId id = MotorId::UNKNOWN; +    // motor base steps. Unit: 1/inch +    int base_ydpi = 0; +    // maximum resolution in y-direction. Unit: 1/inch +    int optical_ydpi = 0; +    // slopes to derive individual slopes from +    std::vector<MotorSlope> slopes; + +    MotorSlope& get_slope(StepType step_type) +    { +        return slopes[static_cast<unsigned>(step_type)]; +    } + +    const MotorSlope& get_slope(StepType step_type) const +    { +        return slopes[static_cast<unsigned>(step_type)]; +    } + +    StepType max_step_type() const +    { +        if (slopes.empty()) { +            throw std::runtime_error("Slopes table is empty"); +        } +        return static_cast<StepType>(slopes.size() - 1); +    } +}; + +std::ostream& operator<<(std::ostream& out, const Genesys_Motor& motor); + +} // namespace genesys + +#endif // BACKEND_GENESYS_MOTOR_H diff --git a/backend/genesys/register.h b/backend/genesys/register.h new file mode 100644 index 0000000..bbc7ec8 --- /dev/null +++ b/backend/genesys/register.h @@ -0,0 +1,537 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_REGISTER_H +#define BACKEND_GENESYS_REGISTER_H + +#include "utilities.h" + +#include <algorithm> +#include <climits> +#include <cstdint> +#include <iostream> +#include <iomanip> +#include <stdexcept> +#include <vector> + +namespace genesys { + +template<class Value> +struct Register +{ +    std::uint16_t address = 0; +    Value value = 0; +}; + +using GenesysRegister = Register<std::uint8_t>; + +template<class Value> +inline bool operator<(const Register<Value>& lhs, const Register<Value>& rhs) +{ +    return lhs.address < rhs.address; +} + +struct GenesysRegisterSetState +{ +    bool is_lamp_on = false; +    bool is_xpa_on = false; +    bool is_motor_on = false; +    bool is_xpa_motor_on = false; +}; + +template<class Value> +class RegisterContainer +{ +public: + +    enum Options { +        SEQUENTIAL = 1 +    }; + +    using RegisterType = Register<Value>; +    using ContainerType = std::vector<RegisterType>; +    using iterator = typename ContainerType::iterator; +    using const_iterator = typename ContainerType::const_iterator; + +    RegisterContainer() = default; + +    RegisterContainer(Options opts) : RegisterContainer() +    { +        if ((opts & SEQUENTIAL) == SEQUENTIAL) { +            sorted_ = false; +        } +    } + +    void init_reg(std::uint16_t address, Value default_value) +    { +        if (find_reg_index(address) >= 0) { +            set(address, default_value); +            return; +        } +        RegisterType reg; +        reg.address = address; +        reg.value = default_value; +        registers_.push_back(reg); +        if (sorted_) +            std::sort(registers_.begin(), registers_.end()); +    } + +    bool has_reg(std::uint16_t address) const +    { +        return find_reg_index(address) >= 0; +    } + +    void remove_reg(std::uint16_t address) +    { +        int i = find_reg_index(address); +        if (i < 0) { +            throw std::runtime_error("the register does not exist"); +        } +        registers_.erase(registers_.begin() + i); +    } + +    RegisterType& find_reg(std::uint16_t address) +    { +        int i = find_reg_index(address); +        if (i < 0) { +            throw std::runtime_error("the register does not exist"); +        } +        return registers_[i]; +    } + +    const RegisterType& find_reg(std::uint16_t address) const +    { +        int i = find_reg_index(address); +        if (i < 0) { +            throw std::runtime_error("the register does not exist"); +        } +        return registers_[i]; +    } + +    void set(std::uint16_t address, Value value) +    { +        find_reg(address).value = value; +    } + +    Value get(std::uint16_t address) const +    { +        return find_reg(address).value; +    } + +    void reserve(std::size_t size) { registers_.reserve(size); } +    void clear() { registers_.clear(); } +    std::size_t size() const { return registers_.size(); } + +    iterator begin() { return registers_.begin(); } +    const_iterator begin() const { return registers_.begin(); } + +    iterator end() { return registers_.end(); } +    const_iterator end() const { return registers_.end(); } + +private: +    int find_reg_index(std::uint16_t address) const +    { +        if (!sorted_) { +            for (std::size_t i = 0; i < registers_.size(); i++) { +                if (registers_[i].address == address) { +                    return i; +                } +            } +            return -1; +        } + +        RegisterType search; +        search.address = address; +        auto it = std::lower_bound(registers_.begin(), registers_.end(), search); +        if (it == registers_.end()) +            return -1; +        if (it->address != address) +            return -1; +        return std::distance(registers_.begin(), it); +    } + +    // registers are stored in a sorted vector +    bool sorted_ = true; +    std::vector<RegisterType> registers_; +}; + +template<class Value> +std::ostream& operator<<(std::ostream& out, const RegisterContainer<Value>& container) +{ +    StreamStateSaver state_saver{out}; + +    out << "RegisterContainer{\n"; +    out << std::hex; +    out.fill('0'); + +    for (const auto& reg : container) { +        unsigned address_width = sizeof(reg.address) * 2; +        unsigned value_width = sizeof(reg.value) * 2; + +        out << "    0x" << std::setw(address_width) << static_cast<unsigned>(reg.address) +            << " = 0x" << std::setw(value_width) << static_cast<unsigned>(reg.value) << '\n'; +    } +    out << "}"; +    return out; +} + +class Genesys_Register_Set +{ +public: +    static constexpr unsigned MAX_REGS = 256; + +    using ContainerType = RegisterContainer<std::uint8_t>; +    using iterator = typename ContainerType::iterator; +    using const_iterator = typename ContainerType::const_iterator; + +    // FIXME: this shouldn't live here, but in a separate struct that contains Genesys_Register_Set +    GenesysRegisterSetState state; + +    enum Options { +        SEQUENTIAL = 1 +    }; + +    Genesys_Register_Set() +    { +        registers_.reserve(MAX_REGS); +    } + +    // by default the register set is sorted by address. In certain cases it's importand to send +    // the registers in certain order: use the SEQUENTIAL option for that +    Genesys_Register_Set(Options opts) : registers_{static_cast<ContainerType::Options>(opts)} +    { +        registers_.reserve(MAX_REGS); +    } + +    const ContainerType& registers() const +    { +        return registers_; +    } + +    void init_reg(std::uint16_t address, std::uint8_t default_value) +    { +        registers_.init_reg(address, default_value); +    } + +    bool has_reg(std::uint16_t address) const { return registers_.has_reg(address); } + +    void remove_reg(std::uint16_t address) { registers_.remove_reg(address); } + +    GenesysRegister& find_reg(std::uint16_t address) +    { +        return registers_.find_reg(address); +    } + +    const GenesysRegister& find_reg(std::uint16_t address) const +    { +        return registers_.find_reg(address); +    } + +    GenesysRegister* find_reg_address(std::uint16_t address) +    { +        return &find_reg(address); +    } + +    const GenesysRegister* find_reg_address(std::uint16_t address) const +    { +        return &find_reg(address); +    } + +    void set8(std::uint16_t address, std::uint8_t value) +    { +        find_reg(address).value = value; +    } + +    void set8_mask(std::uint16_t address, std::uint8_t value, std::uint8_t mask) +    { +        auto& reg = find_reg(address); +        reg.value = (reg.value & ~mask) | value; +    } + +    void set16(std::uint16_t address, std::uint16_t value) +    { +        find_reg(address).value = (value >> 8) & 0xff; +        find_reg(address + 1).value = value & 0xff; +    } + +    void set24(std::uint16_t address, std::uint32_t value) +    { +        find_reg(address).value = (value >> 16) & 0xff; +        find_reg(address + 1).value = (value >> 8) & 0xff; +        find_reg(address + 2).value = value & 0xff; +    } + +    std::uint8_t get8(std::uint16_t address) const +    { +        return find_reg(address).value; +    } + +    std::uint16_t get16(std::uint16_t address) const +    { +        return (find_reg(address).value << 8) | find_reg(address + 1).value; +    } + +    std::uint32_t get24(std::uint16_t address) const +    { +        return (find_reg(address).value << 16) | +               (find_reg(address + 1).value << 8) | +                find_reg(address + 2).value; +    } + +    void clear() { registers_.clear(); } +    std::size_t size() const { return registers_.size(); } + +    iterator begin() { return registers_.begin(); } +    const_iterator begin() const { return registers_.begin(); } + +    iterator end() { return registers_.end(); } +    const_iterator end() const { return registers_.end(); } + +private: + +    // registers are stored in a sorted vector +    ContainerType registers_; +}; + +inline std::ostream& operator<<(std::ostream& out, const Genesys_Register_Set& regs) +{ +    out << regs.registers(); +    return out; +} + +template<class Value> +struct RegisterSetting +{ +    using ValueType = Value; +    using AddressType = std::uint16_t; + +    RegisterSetting() = default; + +    RegisterSetting(AddressType p_address, ValueType p_value) : +        address(p_address), value(p_value) +    {} + +    RegisterSetting(AddressType p_address, ValueType p_value, ValueType p_mask) : +        address(p_address), value(p_value), mask(p_mask) +    {} + +    AddressType address = 0; +    ValueType value = 0; +    ValueType mask = 0xff; + +    bool operator==(const RegisterSetting& other) const +    { +        return address == other.address && value == other.value && mask == other.mask; +    } +}; + +using GenesysRegisterSetting = RegisterSetting<std::uint8_t>; +using GenesysRegisterSetting16 = RegisterSetting<std::uint16_t>; + +template<class Stream, class Value> +void serialize(Stream& str, RegisterSetting<Value>& reg) +{ +    serialize(str, reg.address); +    serialize(str, reg.value); +    serialize(str, reg.mask); +} + +template<class Value> +class RegisterSettingSet +{ +public: +    using ValueType = Value; +    using SettingType = RegisterSetting<ValueType>; +    using AddressType = typename SettingType::AddressType; + +    using container = std::vector<SettingType>; +    using iterator = typename container::iterator; +    using const_iterator = typename container::const_iterator; + +    RegisterSettingSet() = default; +    RegisterSettingSet(std::initializer_list<SettingType> ilist) : +        registers_(ilist) +    {} + +    iterator begin() { return registers_.begin(); } +    const_iterator begin() const { return registers_.begin(); } +    iterator end() { return registers_.end(); } +    const_iterator end() const { return registers_.end(); } + +    SettingType& operator[](std::size_t i) { return registers_[i]; } +    const SettingType& operator[](std::size_t i) const { return registers_[i]; } + +    std::size_t size() const { return registers_.size(); } +    bool empty() const { return registers_.empty(); } +    void clear() { registers_.clear(); } + +    void push_back(SettingType reg) { registers_.push_back(reg); } + +    void merge(const RegisterSettingSet& other) +    { +        for (const auto& reg : other) { +            set_value(reg.address, reg.value); +        } +    } + +    SettingType& find_reg(AddressType address) +    { +        int i = find_reg_index(address); +        if (i < 0) { +            throw std::runtime_error("the register does not exist"); +        } +        return registers_[i]; +    } + +    const SettingType& find_reg(AddressType address) const +    { +        int i = find_reg_index(address); +        if (i < 0) { +            throw std::runtime_error("the register does not exist"); +        } +        return registers_[i]; +    } + +    ValueType get_value(AddressType address) const +    { +        int index = find_reg_index(address); +        if (index >= 0) { +            return registers_[index].value; +        } +        throw std::out_of_range("Unknown register"); +    } + +    void set_value(AddressType address, ValueType value) +    { +        int index = find_reg_index(address); +        if (index >= 0) { +            registers_[index].value = value; +            return; +        } +        push_back(SettingType(address, value)); +    } + +    template<class V> +    friend void serialize(std::istream& str, RegisterSettingSet<V>& reg); +    template<class V> +    friend void serialize(std::ostream& str, RegisterSettingSet<V>& reg); + +    bool operator==(const RegisterSettingSet& other) const +    { +        return registers_ == other.registers_; +    } + +private: + +    int find_reg_index(AddressType address) const +    { +        for (std::size_t i = 0; i < registers_.size(); i++) { +            if (registers_[i].address == address) { +                return i; +            } +        } +        return -1; +    } + +    std::vector<SettingType> registers_; +}; + +using GenesysRegisterSettingSet = RegisterSettingSet<std::uint8_t>; +using GenesysRegisterSettingSet16 = RegisterSettingSet<std::uint16_t>; + +template<class Value> +std::ostream& operator<<(std::ostream& out, const RegisterSettingSet<Value>& container) +{ +    StreamStateSaver state_saver{out}; + +    out << "RegisterSettingSet{\n"; +    out << std::hex; +    out.fill('0'); + +    for (const auto& reg : container) { +        unsigned address_width = sizeof(reg.address) * 2; +        unsigned value_width = sizeof(reg.value) * 2; +        unsigned mask_width = sizeof(reg.mask) * 2; + +        out << "    0x" << std::setw(address_width) << static_cast<unsigned>(reg.address) +            << " = 0x" << std::setw(value_width) << static_cast<unsigned>(reg.value) +            << " & 0x" << std::setw(mask_width) << static_cast<unsigned>(reg.mask) << '\n'; +    } +    out << "}"; +    return out; +} + +template<class Value> +inline void serialize(std::istream& str, RegisterSettingSet<Value>& reg) +{ +    using AddressType = typename RegisterSetting<Value>::AddressType; + +    reg.clear(); +    const std::size_t max_register_address = 1 << (sizeof(AddressType) * CHAR_BIT); +    serialize(str, reg.registers_, max_register_address); +} + +template<class Value> +inline void serialize(std::ostream& str, RegisterSettingSet<Value>& reg) +{ +    serialize(str, reg.registers_); +} + +template<class F, class Value> +void apply_registers_ordered(const RegisterSettingSet<Value>& set, +                             std::initializer_list<std::uint16_t> order, F f) +{ +    for (std::uint16_t addr : order) { +        f(set.find_reg(addr)); +    } +    for (const auto& reg : set) { +        if (std::find(order.begin(), order.end(), reg.address) != order.end()) { +            continue; +        } +        f(reg); +    } +} + +} // namespace genesys + +#endif // BACKEND_GENESYS_REGISTER_H diff --git a/backend/genesys/register_cache.h b/backend/genesys/register_cache.h new file mode 100644 index 0000000..dce701a --- /dev/null +++ b/backend/genesys/register_cache.h @@ -0,0 +1,92 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_REGISTER_CACHE_H +#define BACKEND_GENESYS_REGISTER_CACHE_H + +#include "register.h" + +namespace genesys { + +template<class Value> +class RegisterCache +{ +public: +    void update(std::uint16_t address, Value value) +    { +        if (regs_.has_reg(address)) { +            regs_.set(address, value); +        } else { +            regs_.init_reg(address, value); +        } +    } + +    void update(const Genesys_Register_Set& regs) +    { +        for (const auto& reg : regs) { +            update(reg.address, reg.value); +        } +    } + +    Value get(std::uint16_t address) const +    { +        return regs_.get(address); +    } + +private: +    RegisterContainer<Value> regs_; + +    template<class V> +    friend std::ostream& operator<<(std::ostream& out, const RegisterCache<V>& cache); +}; + +template<class Value> +std::ostream& operator<<(std::ostream& out, const RegisterCache<Value>& cache) +{ +    out << cache.regs_; +    return out; +} + +} // namespace genesys + +#endif // BACKEND_GENESYS_LINE_BUFFER_H diff --git a/backend/genesys/row_buffer.h b/backend/genesys/row_buffer.h new file mode 100644 index 0000000..e1a0c82 --- /dev/null +++ b/backend/genesys/row_buffer.h @@ -0,0 +1,214 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_LINE_BUFFER_H +#define BACKEND_GENESYS_LINE_BUFFER_H + +#include "error.h" + +#include <algorithm> +#include <cstdint> +#include <cstddef> +#include <vector> + +namespace genesys { + +class RowBuffer +{ +public: +    RowBuffer(std::size_t line_bytes) : row_bytes_{line_bytes} {} +    RowBuffer(const RowBuffer&) = default; +    RowBuffer& operator=(const RowBuffer&) = default; +    ~RowBuffer() = default; + +    const std::uint8_t* get_row_ptr(std::size_t y) const +    { +        if (y >= height()) { +            throw SaneException("y %zu is out of range", y); +        } +        return data_.data() + row_bytes_ * get_row_index(y); +    } + +    std::uint8_t* get_row_ptr(std::size_t y) +    { +        if (y >= height()) { +            throw SaneException("y %zu is out of range", y); +        } +        return data_.data() + row_bytes_ * get_row_index(y); +    } + +    const std::uint8_t* get_front_row_ptr() const { return get_row_ptr(0); } +    std::uint8_t* get_front_row_ptr() { return get_row_ptr(0); } +    const std::uint8_t* get_back_row_ptr() const { return get_row_ptr(height() - 1); } +    std::uint8_t* get_back_row_ptr() { return get_row_ptr(height() - 1); } + +    bool empty() const { return is_linear_ && first_ == last_; } + +    bool full() +    { +        if (is_linear_) { +            return last_ == buffer_end_; +        } +        return first_ == last_; +    } + +    bool is_linear() const { return is_linear_; } + +    void linearize() +    { +        if (!is_linear_) { +            std::rotate(data_.begin(), data_.begin() + row_bytes_ * first_, data_.end()); +            last_ = height(); +            first_ = 0; +            is_linear_ = true; +        } +    } + +    void pop_front() +    { +        if (empty()) { +            throw SaneException("Trying to pop out of empty() line buffer"); +        } + +        first_++; +        if (first_ == last_) { +            first_ = 0; +            last_ = 0; +            is_linear_ = true; +        } else  if (first_ == buffer_end_) { +            first_ = 0; +            is_linear_ = true; +        } +    } + +    void push_front() +    { +        if (height() + 1 >= height_capacity()) { +            ensure_capacity(std::max<std::size_t>(1, height() * 2)); +        } + +        if (first_ == 0) { +            is_linear_ = false; +            first_ = buffer_end_; +        } +        first_--; +    } + +    void pop_back() +    { +        if (empty()) { +            throw SaneException("Trying to pop out of empty() line buffer"); +        } +        if (last_ == 0) { +            last_ = buffer_end_; +            is_linear_ = true; +        } +        last_--; +        if (first_ == last_) { +            first_ = 0; +            last_ = 0; +            is_linear_ = true; +        } +    } + +    void push_back() +    { +        if (height() + 1 >= height_capacity()) { +            ensure_capacity(std::max<std::size_t>(1, height() * 2)); +        } + +        if (last_ == buffer_end_) { +            is_linear_ = false; +            last_ = 0; +        } +        last_++; +    } + +    std::size_t row_bytes() const { return row_bytes_; } + +    std::size_t height() const +    { +        if (!is_linear_) { +            return last_ + buffer_end_ - first_; +        } +        return last_ - first_; +    } + +    std::size_t height_capacity() const { return buffer_end_; } + +    void clear() +    { +        first_ = 0; +        last_ = 0; +    } + +private: +    std::size_t get_row_index(std::size_t index) const +    { +        if (index >= buffer_end_ - first_) { +            return index - (buffer_end_ - first_); +        } +        return index + first_; +    } + +    void ensure_capacity(std::size_t capacity) +    { +        if (capacity < height_capacity()) +            return; +        linearize(); +        data_.resize(capacity * row_bytes_); +        buffer_end_ = capacity; +    } + +private: +    std::size_t row_bytes_ = 0; +    std::size_t first_ = 0; +    std::size_t last_ = 0; +    std::size_t buffer_end_ = 0; +    bool is_linear_ = true; +    std::vector<std::uint8_t> data_; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_LINE_BUFFER_H diff --git a/backend/genesys/scanner_interface.cpp b/backend/genesys/scanner_interface.cpp new file mode 100644 index 0000000..0b60b66 --- /dev/null +++ b/backend/genesys/scanner_interface.cpp @@ -0,0 +1,52 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "scanner_interface.h" + +namespace genesys { + +ScannerInterface::~ScannerInterface() = default; + +} // namespace genesys diff --git a/backend/genesys/scanner_interface.h b/backend/genesys/scanner_interface.h new file mode 100644 index 0000000..03c7132 --- /dev/null +++ b/backend/genesys/scanner_interface.h @@ -0,0 +1,112 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_SCANNER_INTERFACE_H +#define BACKEND_GENESYS_SCANNER_INTERFACE_H + +#include "fwd.h" +#include <cstddef> +#include <cstdint> +#include <string> +#include <vector> + +namespace genesys { + +// Represents an interface through which all low level operations are performed. +class ScannerInterface +{ +public: +    enum Flags { +        FLAG_NONE = 0, +        FLAG_SWAP_REGISTERS = 1 << 0, +        FLAG_SMALL_ADDRESS = 1 << 1 +    }; + +    virtual ~ScannerInterface(); + +    virtual bool is_mock() const = 0; + +    virtual std::uint8_t read_register(std::uint16_t address) = 0; +    virtual void write_register(std::uint16_t address, std::uint8_t value) = 0; +    virtual void write_registers(const Genesys_Register_Set& regs) = 0; + +    virtual void write_0x8c(std::uint8_t index, std::uint8_t value) = 0; +    virtual void bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) = 0; +    virtual void bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) = 0; + +    // GL646, GL841, GL843 have different ways to write to RAM and to gamma tables +    // FIXME: remove flags when updating tests +    virtual void write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                              std::size_t size, Flags flags = FLAG_NONE) = 0; + +    virtual void write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                             std::size_t size, Flags flags = FLAG_NONE) = 0; + +    // GL845, GL846, GL847 and GL124 have a uniform way to write to RAM tables +    virtual void write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) = 0; + +    virtual std::uint16_t read_fe_register(std::uint8_t address) = 0; +    virtual void write_fe_register(std::uint8_t address, std::uint16_t value) = 0; + +    virtual IUsbDevice& get_usb_device() = 0; + +    // sleeps the specified number of microseconds. Will not sleep if testing mode is enabled. +    virtual void sleep_us(unsigned microseconds) = 0; + +    void sleep_ms(unsigned milliseconds) +    { +        sleep_us(milliseconds * 1000); +    } + +    virtual void record_progress_message(const char* msg) = 0; + +    virtual void record_slope_table(unsigned table_nr, const std::vector<std::uint16_t>& steps) = 0; + +    virtual void record_key_value(const std::string& key, const std::string& value) = 0; + +    virtual void test_checkpoint(const std::string& name) = 0; +}; + +} // namespace genesys + +#endif diff --git a/backend/genesys/scanner_interface_usb.cpp b/backend/genesys/scanner_interface_usb.cpp new file mode 100644 index 0000000..d4d83dd --- /dev/null +++ b/backend/genesys/scanner_interface_usb.cpp @@ -0,0 +1,515 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "scanner_interface_usb.h" +#include "low.h" +#include <thread> + +namespace genesys { + +ScannerInterfaceUsb::~ScannerInterfaceUsb() = default; + +ScannerInterfaceUsb::ScannerInterfaceUsb(Genesys_Device* dev) : dev_{dev} {} + +bool ScannerInterfaceUsb::is_mock() const +{ +    return false; +} + +std::uint8_t ScannerInterfaceUsb::read_register(std::uint16_t address) +{ +    DBG_HELPER(dbg); + +    std::uint8_t value = 0; + +    if (dev_->model->asic_type == AsicType::GL847 || +        dev_->model->asic_type == AsicType::GL845 || +        dev_->model->asic_type == AsicType::GL846 || +        dev_->model->asic_type == AsicType::GL124) +    { +        std::uint8_t value2x8[2]; +        std::uint16_t address16 = 0x22 + (address << 8); + +        std::uint16_t usb_value = VALUE_GET_REGISTER; +        if (address > 0xff) { +            usb_value |= 0x100; +        } + +        usb_dev_.control_msg(REQUEST_TYPE_IN, REQUEST_BUFFER, usb_value, address16, 2, value2x8); + +        // check usb link status +        if (value2x8[1] != 0x55) { +            throw SaneException(SANE_STATUS_IO_ERROR, "invalid read, scanner unplugged?"); +        } + +        DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, address, value2x8[0]); + +        value = value2x8[0]; + +    } else { + +        if (address > 0xff) { +            throw SaneException("Invalid register address 0x%04x", address); +        } + +        std::uint8_t address8 = address & 0xff; + +        usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX, +                             1, &address8); +        usb_dev_.control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, VALUE_READ_REGISTER, INDEX, +                             1, &value); +    } + +    DBG(DBG_proc, "%s (0x%02x, 0x%02x) completed\n", __func__, address, value); +    return value; +} + +void ScannerInterfaceUsb::write_register(std::uint16_t address, std::uint8_t value) +{ +    DBG_HELPER_ARGS(dbg, "address: 0x%04x, value: 0x%02x", static_cast<unsigned>(address), +                    static_cast<unsigned>(value)); + +    if (dev_->model->asic_type == AsicType::GL847 || +        dev_->model->asic_type == AsicType::GL845 || +        dev_->model->asic_type == AsicType::GL846 || +        dev_->model->asic_type == AsicType::GL124) +    { +        std::uint8_t buffer[2]; + +        buffer[0] = address & 0xff; +        buffer[1] = value; + +        std::uint16_t usb_value = VALUE_SET_REGISTER; +        if (address > 0xff) { +            usb_value |= 0x100; +        } + +        usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, usb_value, INDEX, +                                  2, buffer); + +    } else { +        if (address > 0xff) { +            throw SaneException("Invalid register address 0x%04x", address); +        } + +        std::uint8_t address8 = address & 0xff; + +        usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX, +                             1, &address8); + +        usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_WRITE_REGISTER, INDEX, +                             1, &value); + +    } +    DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, address, value); +} + +void ScannerInterfaceUsb::write_registers(const Genesys_Register_Set& regs) +{ +    DBG_HELPER(dbg); +    if (dev_->model->asic_type == AsicType::GL646 || +        dev_->model->asic_type == AsicType::GL841) +    { +        uint8_t outdata[8]; +        std::vector<uint8_t> buffer; +        buffer.reserve(regs.size() * 2); + +        /* copy registers and values in data buffer */ +        for (const auto& r : regs) { +            buffer.push_back(r.address); +            buffer.push_back(r.value); +        } + +        DBG(DBG_io, "%s (elems= %zu, size = %zu)\n", __func__, regs.size(), buffer.size()); + +        if (dev_->model->asic_type == AsicType::GL646) { +            outdata[0] = BULK_OUT; +            outdata[1] = BULK_REGISTER; +            outdata[2] = 0x00; +            outdata[3] = 0x00; +            outdata[4] = (buffer.size() & 0xff); +            outdata[5] = ((buffer.size() >> 8) & 0xff); +            outdata[6] = ((buffer.size() >> 16) & 0xff); +            outdata[7] = ((buffer.size() >> 24) & 0xff); + +            usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, INDEX, +                                 sizeof(outdata), outdata); + +            size_t write_size = buffer.size(); + +            usb_dev_.bulk_write(buffer.data(), &write_size); +        } else { +            for (std::size_t i = 0; i < regs.size();) { +                std::size_t c = regs.size() - i; +                if (c > 32)  /*32 is max on GL841. checked that.*/ +                    c = 32; + +                usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_SET_REGISTER, +                                     INDEX, c * 2, buffer.data() + i * 2); + +                i += c; +            } +        } +    } else { +        for (const auto& r : regs) { +            write_register(r.address, r.value); +        } +    } + +    DBG(DBG_io, "%s: wrote %zu registers\n", __func__, regs.size()); +} + +void ScannerInterfaceUsb::write_0x8c(std::uint8_t index, std::uint8_t value) +{ +    DBG_HELPER_ARGS(dbg, "0x%02x,0x%02x", index, value); +    usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_BUF_ENDACCESS, index, 1, &value); +} + +static void bulk_read_data_send_header(UsbDevice& usb_dev, AsicType asic_type, size_t size) +{ +    DBG_HELPER(dbg); + +    uint8_t outdata[8]; +    if (asic_type == AsicType::GL124 || +        asic_type == AsicType::GL846 || +        asic_type == AsicType::GL847) +    { +        // hard coded 0x10000000 address +        outdata[0] = 0; +        outdata[1] = 0; +        outdata[2] = 0; +        outdata[3] = 0x10; +    } else if (asic_type == AsicType::GL841 || +               asic_type == AsicType::GL843) { +        outdata[0] = BULK_IN; +        outdata[1] = BULK_RAM; +        outdata[2] = 0x82; // +        outdata[3] = 0x00; +    } else { +        outdata[0] = BULK_IN; +        outdata[1] = BULK_RAM; +        outdata[2] = 0x00; +        outdata[3] = 0x00; +    } + +    /* data size to transfer */ +    outdata[4] = (size & 0xff); +    outdata[5] = ((size >> 8) & 0xff); +    outdata[6] = ((size >> 16) & 0xff); +    outdata[7] = ((size >> 24) & 0xff); + +   usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x00, +                       sizeof(outdata), outdata); +} + +void ScannerInterfaceUsb::bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) +{ +    // currently supported: GL646, GL841, GL843, GL846, GL847, GL124 +    DBG_HELPER(dbg); + +    unsigned is_addr_used = 1; +    unsigned has_header_before_each_chunk = 0; +    if (dev_->model->asic_type == AsicType::GL124 || +        dev_->model->asic_type == AsicType::GL846 || +        dev_->model->asic_type == AsicType::GL847) +    { +        is_addr_used = 0; +        has_header_before_each_chunk = 1; +    } + +    if (is_addr_used) { +        DBG(DBG_io, "%s: requesting %zu bytes from 0x%02x addr\n", __func__, size, addr); +    } else { +        DBG(DBG_io, "%s: requesting %zu bytes\n", __func__, size); +    } + +    if (size == 0) +        return; + +    if (is_addr_used) { +        usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, 0x00, +                             1, &addr); +    } + +    std::size_t target_size = size; + +    std::size_t max_in_size = sanei_genesys_get_bulk_max_size(dev_->model->asic_type); + +    if (!has_header_before_each_chunk) { +        bulk_read_data_send_header(usb_dev_, dev_->model->asic_type, size); +    } + +    // loop until computed data size is read +    while (target_size > 0) { +        std::size_t block_size = std::min(target_size, max_in_size); + +        if (has_header_before_each_chunk) { +            bulk_read_data_send_header(usb_dev_, dev_->model->asic_type, block_size); +        } + +        DBG(DBG_io2, "%s: trying to read %zu bytes of data\n", __func__, block_size); + +        usb_dev_.bulk_read(data, &block_size); + +        DBG(DBG_io2, "%s: read %zu bytes, %zu remaining\n", __func__, block_size, target_size - block_size); + +        target_size -= block_size; +        data += block_size; +    } +} + +void ScannerInterfaceUsb::bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t len) +{ +    DBG_HELPER_ARGS(dbg, "writing %zu bytes", len); + +    // supported: GL646, GL841, GL843 +    std::size_t size; +    std::uint8_t outdata[8]; + +    usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX, +                             1, &addr); + +    std::size_t max_out_size = sanei_genesys_get_bulk_max_size(dev_->model->asic_type); + +    while (len) { +        if (len > max_out_size) +            size = max_out_size; +        else +            size = len; + +        if (dev_->model->asic_type == AsicType::GL841) { +            outdata[0] = BULK_OUT; +            outdata[1] = BULK_RAM; +            // both 0x82 and 0x00 works on GL841. +            outdata[2] = 0x82; +            outdata[3] = 0x00; +        } else { +            outdata[0] = BULK_OUT; +            outdata[1] = BULK_RAM; +            // 8600F uses 0x82, but 0x00 works too. 8400F uses 0x02 for certain transactions. +            outdata[2] = 0x00; +            outdata[3] = 0x00; +        } + +        outdata[4] = (size & 0xff); +        outdata[5] = ((size >> 8) & 0xff); +        outdata[6] = ((size >> 16) & 0xff); +        outdata[7] = ((size >> 24) & 0xff); + +        usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x00, +                             sizeof(outdata), outdata); + +        usb_dev_.bulk_write(data, &size); + +        DBG(DBG_io2, "%s: wrote %zu bytes, %zu remaining\n", __func__, size, len - size); + +        len -= size; +        data += size; +    } +} + +void ScannerInterfaceUsb::write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                                       std::size_t size, Flags flags) +{ +    DBG_HELPER_ARGS(dbg, "type: 0x%02x, addr: 0x%08x, size: 0x%08zx", type, addr, size); +    if (dev_->model->asic_type != AsicType::GL646 && +        dev_->model->asic_type != AsicType::GL841 && +        dev_->model->asic_type != AsicType::GL843) +    { +        throw SaneException("Unsupported transfer mode"); +    } + +    if (dev_->model->asic_type == AsicType::GL843) { +        if (flags & FLAG_SWAP_REGISTERS) { +            if (!(flags & FLAG_SMALL_ADDRESS)) { +                write_register(0x29, ((addr >> 20) & 0xff)); +            } +            write_register(0x2a, ((addr >> 12) & 0xff)); +            write_register(0x2b, ((addr >> 4) & 0xff)); +        } else { +            write_register(0x2b, ((addr >> 4) & 0xff)); +            write_register(0x2a, ((addr >> 12) & 0xff)); +            if (!(flags & FLAG_SMALL_ADDRESS)) { +                write_register(0x29, ((addr >> 20) & 0xff)); +            } +        } +    } else { +        write_register(0x2b, ((addr >> 4) & 0xff)); +        write_register(0x2a, ((addr >> 12) & 0xff)); +    } +    bulk_write_data(type, data, size); +} + +void ScannerInterfaceUsb::write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                                      std::size_t size, Flags flags) +{ +    DBG_HELPER_ARGS(dbg, "type: 0x%02x, addr: 0x%08x, size: 0x%08zx", type, addr, size); +    if (dev_->model->asic_type != AsicType::GL646 && +        dev_->model->asic_type != AsicType::GL841 && +        dev_->model->asic_type != AsicType::GL843) +    { +        throw SaneException("Unsupported transfer mode"); +    } + +    if (flags & FLAG_SWAP_REGISTERS) { +        write_register(0x5b, ((addr >> 12) & 0xff)); +        write_register(0x5c, ((addr >> 4) & 0xff)); +    } else { +        write_register(0x5c, ((addr >> 4) & 0xff)); +        write_register(0x5b, ((addr >> 12) & 0xff)); +    } +    bulk_write_data(type, data, size); +} + +void ScannerInterfaceUsb::write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) +{ +    DBG_HELPER_ARGS(dbg, "address: 0x%08x, size: %d", static_cast<unsigned>(addr), +                    static_cast<unsigned>(size)); + +    if (dev_->model->asic_type != AsicType::GL845 && +        dev_->model->asic_type != AsicType::GL846 && +        dev_->model->asic_type != AsicType::GL847 && +        dev_->model->asic_type != AsicType::GL124) +    { +        throw SaneException("Unsupported transfer type"); +    } +    std::uint8_t outdata[8]; +    outdata[0] = addr & 0xff; +    outdata[1] = ((addr >> 8) & 0xff); +    outdata[2] = ((addr >> 16) & 0xff); +    outdata[3] = ((addr >> 24) & 0xff); +    outdata[4] = (size & 0xff); +    outdata[5] = ((size >> 8) & 0xff); +    outdata[6] = ((size >> 16) & 0xff); +    outdata[7] = ((size >> 24) & 0xff); + +    // write addr and size for AHB +    usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x01, 8, outdata); + +    std::size_t max_out_size = sanei_genesys_get_bulk_max_size(dev_->model->asic_type); + +    // write actual data +    std::size_t written = 0; +    do { +        std::size_t block_size = std::min(size - written, max_out_size); + +        usb_dev_.bulk_write(data + written, &block_size); + +        written += block_size; +    } while (written < size); +} + +std::uint16_t ScannerInterfaceUsb::read_fe_register(std::uint8_t address) +{ +    DBG_HELPER(dbg); +    Genesys_Register_Set reg; + +    reg.init_reg(0x50, address); + +    // set up read address +    write_registers(reg); + +    // read data +    std::uint16_t value = read_register(0x46) << 8; +    value |= read_register(0x47); + +    DBG(DBG_io, "%s (0x%02x, 0x%04x)\n", __func__, address, value); +    return value; +} + +void ScannerInterfaceUsb::write_fe_register(std::uint8_t address, std::uint16_t value) +{ +    DBG_HELPER_ARGS(dbg, "0x%02x, 0x%04x", address, value); +    Genesys_Register_Set reg(Genesys_Register_Set::SEQUENTIAL); + +    reg.init_reg(0x51, address); +    if (dev_->model->asic_type == AsicType::GL124) { +        reg.init_reg(0x5d, (value / 256) & 0xff); +        reg.init_reg(0x5e, value & 0xff); +    } else { +        reg.init_reg(0x3a, (value / 256) & 0xff); +        reg.init_reg(0x3b, value & 0xff); +    } + +    write_registers(reg); +} + +IUsbDevice& ScannerInterfaceUsb::get_usb_device() +{ +    return usb_dev_; +} + +void ScannerInterfaceUsb::sleep_us(unsigned microseconds) +{ +    if (sanei_usb_is_replay_mode_enabled()) { +        return; +    } +    std::this_thread::sleep_for(std::chrono::microseconds{microseconds}); +} + +void ScannerInterfaceUsb::record_progress_message(const char* msg) +{ +    sanei_usb_testing_record_message(msg); +} + +void ScannerInterfaceUsb::record_slope_table(unsigned table_nr, +                                             const std::vector<std::uint16_t>& steps) +{ +    (void) table_nr; +    (void) steps; +} + +void ScannerInterfaceUsb::record_key_value(const std::string& key, const std::string& value) +{ +    (void) key; +    (void) value; +} + +void ScannerInterfaceUsb::test_checkpoint(const std::string& name) +{ +    (void) name; +} + +} // namespace genesys diff --git a/backend/genesys/scanner_interface_usb.h b/backend/genesys/scanner_interface_usb.h new file mode 100644 index 0000000..06b51ff --- /dev/null +++ b/backend/genesys/scanner_interface_usb.h @@ -0,0 +1,98 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_SCANNER_INTERFACE_USB_H +#define BACKEND_GENESYS_SCANNER_INTERFACE_USB_H + +#include "scanner_interface.h" +#include "usb_device.h" + +namespace genesys { + +class ScannerInterfaceUsb : public ScannerInterface +{ +public: +    ScannerInterfaceUsb(Genesys_Device* dev); + +    ~ScannerInterfaceUsb() override; + +    bool is_mock() const override; + +    std::uint8_t read_register(std::uint16_t address) override; +    void write_register(std::uint16_t address, std::uint8_t value) override; +    void write_registers(const Genesys_Register_Set& regs) override; + +    void write_0x8c(std::uint8_t index, std::uint8_t value) override; +    void bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override; +    void bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override; + +    void write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                      std::size_t size, Flags flags) override; +    void write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                     std::size_t size, Flags flags) override; + +    void write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) override; + +    std::uint16_t read_fe_register(std::uint8_t address) override; +    void write_fe_register(std::uint8_t address, std::uint16_t value) override; + +    IUsbDevice& get_usb_device() override; + +    void sleep_us(unsigned microseconds) override; + +    void record_progress_message(const char* msg) override; + +    void record_slope_table(unsigned table_nr, const std::vector<std::uint16_t>& steps) override; + +    void record_key_value(const std::string& key, const std::string& value) override; + +    void test_checkpoint(const std::string& name) override; + +private: +    Genesys_Device* dev_; +    UsbDevice usb_dev_; +}; + +} // namespace genesys + +#endif diff --git a/backend/genesys/sensor.cpp b/backend/genesys/sensor.cpp new file mode 100644 index 0000000..e54af65 --- /dev/null +++ b/backend/genesys/sensor.cpp @@ -0,0 +1,160 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "sensor.h" +#include "utilities.h" +#include <iomanip> + +namespace genesys { + +std::ostream& operator<<(std::ostream& out, const StaggerConfig& config) +{ +    out << "StaggerConfig{\n" +        << "    min_resolution: " << config.min_resolution() << '\n' +        << "    lines_at_min: " << config.lines_at_min() << '\n' +        << "}"; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const FrontendType& type) +{ +    switch (type) { +        case FrontendType::UNKNOWN: out << "UNKNOWN"; break; +        case FrontendType::WOLFSON: out << "WOLFSON"; break; +        case FrontendType::ANALOG_DEVICES: out << "ANALOG_DEVICES"; break; +        default: out << "(unknown value)"; +    } +    return out; +} + +std::ostream& operator<<(std::ostream& out, const GenesysFrontendLayout& layout) +{ +    StreamStateSaver state_saver{out}; + +    out << "GenesysFrontendLayout{\n" +        << "    type: " << layout.type << '\n' +        << std::hex +        << "    offset_addr[0]: " << layout.offset_addr[0] << '\n' +        << "    offset_addr[1]: " << layout.offset_addr[1] << '\n' +        << "    offset_addr[2]: " << layout.offset_addr[2] << '\n' +        << "    gain_addr[0]: " << layout.gain_addr[0] << '\n' +        << "    gain_addr[1]: " << layout.gain_addr[1] << '\n' +        << "    gain_addr[2]: " << layout.gain_addr[2] << '\n' +        << '}'; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const Genesys_Frontend& frontend) +{ +    StreamStateSaver state_saver{out}; + +    out << "Genesys_Frontend{\n" +        << "    id: " << static_cast<unsigned>(frontend.id) << '\n' +        << "    regs: " << format_indent_braced_list(4, frontend.regs) << '\n' +        << std::hex +        << "    reg2[0]: " << frontend.reg2[0] << '\n' +        << "    reg2[1]: " << frontend.reg2[1] << '\n' +        << "    reg2[2]: " << frontend.reg2[2] << '\n' +        << "    layout: " << format_indent_braced_list(4, frontend.layout) << '\n' +        << '}'; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const SensorExposure& exposure) +{ +    out << "SensorExposure{\n" +        << "    red: " << exposure.red << '\n' +        << "    green: " << exposure.green << '\n' +        << "    blue: " << exposure.blue << '\n' +        << '}'; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const ResolutionFilter& resolutions) +{ +    if (resolutions.matches_any()) { +        out << "ANY"; +        return out; +    } +    out << format_vector_unsigned(4, resolutions.resolutions()); +    return out; +} + +std::ostream& operator<<(std::ostream& out, const Genesys_Sensor& sensor) +{ +    out << "Genesys_Sensor{\n" +        << "    sensor_id: " << static_cast<unsigned>(sensor.sensor_id) << '\n' +        << "    optical_res: " << sensor.optical_res << '\n' +        << "    resolutions: " << format_indent_braced_list(4, sensor.resolutions) << '\n' +        << "    channels: " << format_vector_unsigned(4, sensor.channels) << '\n' +        << "    method: " << sensor.method << '\n' +        << "    register_dpihw_override: " << sensor.register_dpihw_override << '\n' +        << "    logical_dpihw_override: " << sensor.logical_dpihw_override << '\n' +        << "    dpiset_override: " << sensor.dpiset_override << '\n' +        << "    ccd_size_divisor: " << sensor.ccd_size_divisor << '\n' +        << "    pixel_count_multiplier: " << sensor.pixel_count_multiplier << '\n' +        << "    black_pixels: " << sensor.black_pixels << '\n' +        << "    dummy_pixel: " << sensor.dummy_pixel << '\n' +        << "    ccd_start_xoffset: " << sensor.ccd_start_xoffset << '\n' +        << "    sensor_pixels: " << sensor.sensor_pixels << '\n' +        << "    fau_gain_white_ref: " << sensor.fau_gain_white_ref << '\n' +        << "    gain_white_ref: " << sensor.gain_white_ref << '\n' +        << "    exposure: " << format_indent_braced_list(4, sensor.exposure) << '\n' +        << "    exposure_lperiod: " << sensor.exposure_lperiod << '\n' +        << "    segment_size: " << sensor.segment_size << '\n' +        << "    segment_order: " +        << format_indent_braced_list(4, format_vector_unsigned(4, sensor.segment_order)) << '\n' +        << "    stagger_config: " << format_indent_braced_list(4, sensor.stagger_config) << '\n' +        << "    custom_base_regs: " << format_indent_braced_list(4, sensor.custom_base_regs) << '\n' +        << "    custom_regs: " << format_indent_braced_list(4, sensor.custom_regs) << '\n' +        << "    custom_fe_regs: " << format_indent_braced_list(4, sensor.custom_fe_regs) << '\n' +        << "    gamma.red: " << sensor.gamma[0] << '\n' +        << "    gamma.green: " << sensor.gamma[1] << '\n' +        << "    gamma.blue: " << sensor.gamma[2] << '\n' +        << "}"; +    return out; +} + +} // namespace genesys diff --git a/backend/genesys/sensor.h b/backend/genesys/sensor.h new file mode 100644 index 0000000..e70728e --- /dev/null +++ b/backend/genesys/sensor.h @@ -0,0 +1,470 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_SENSOR_H +#define BACKEND_GENESYS_SENSOR_H + +#include "enums.h" +#include "register.h" +#include "serialize.h" +#include <array> +#include <functional> + +namespace genesys { + +template<class T, size_t Size> +struct AssignableArray : public std::array<T, Size> { +    AssignableArray() = default; +    AssignableArray(const AssignableArray&) = default; +    AssignableArray& operator=(const AssignableArray&) = default; + +    AssignableArray& operator=(std::initializer_list<T> init) +    { +        if (init.size() != std::array<T, Size>::size()) +            throw std::runtime_error("An array of incorrect size assigned"); +        std::copy(init.begin(), init.end(), std::array<T, Size>::begin()); +        return *this; +    } +}; + + +class StaggerConfig +{ +public: +    StaggerConfig() = default; +    StaggerConfig(unsigned min_resolution, unsigned lines_at_min) : +        min_resolution_{min_resolution}, +        lines_at_min_{lines_at_min} +    { +    } + +    unsigned stagger_at_resolution(unsigned xresolution, unsigned yresolution) const +    { +        if (min_resolution_ == 0 || xresolution < min_resolution_) +            return 0; +        return yresolution / min_resolution_ * lines_at_min_; +    } + +    unsigned min_resolution() const { return min_resolution_; } +    unsigned lines_at_min() const { return lines_at_min_; } + +    bool operator==(const StaggerConfig& other) const +    { +        return min_resolution_ == other.min_resolution_ && +                lines_at_min_ == other.lines_at_min_; +    } + +private: +    unsigned min_resolution_ = 0; +    unsigned lines_at_min_ = 0; + +    template<class Stream> +    friend void serialize(Stream& str, StaggerConfig& x); +}; + +template<class Stream> +void serialize(Stream& str, StaggerConfig& x) +{ +    serialize(str, x.min_resolution_); +    serialize(str, x.lines_at_min_); +} + +std::ostream& operator<<(std::ostream& out, const StaggerConfig& config); + + +enum class FrontendType : unsigned +{ +    UNKNOWN, +    WOLFSON, +    ANALOG_DEVICES +}; + +inline void serialize(std::istream& str, FrontendType& x) +{ +    unsigned value; +    serialize(str, value); +    x = static_cast<FrontendType>(value); +} + +inline void serialize(std::ostream& str, FrontendType& x) +{ +    unsigned value = static_cast<unsigned>(x); +    serialize(str, value); +} + +std::ostream& operator<<(std::ostream& out, const FrontendType& type); + +struct GenesysFrontendLayout +{ +    FrontendType type = FrontendType::UNKNOWN; +    std::array<std::uint16_t, 3> offset_addr = {}; +    std::array<std::uint16_t, 3> gain_addr = {}; + +    bool operator==(const GenesysFrontendLayout& other) const +    { +        return type == other.type && +                offset_addr == other.offset_addr && +                gain_addr == other.gain_addr; +    } +}; + +template<class Stream> +void serialize(Stream& str, GenesysFrontendLayout& x) +{ +    serialize(str, x.type); +    serialize_newline(str); +    serialize(str, x.offset_addr); +    serialize_newline(str); +    serialize(str, x.gain_addr); +} + +std::ostream& operator<<(std::ostream& out, const GenesysFrontendLayout& layout); + +/** @brief Data structure to set up analog frontend. +    The analog frontend converts analog value from image sensor to digital value. It has its own +    control registers which are set up with this structure. The values are written using +    fe_write_data. + */ +struct Genesys_Frontend +{ +    Genesys_Frontend() = default; + +    // id of the frontend description +    AdcId id = AdcId::UNKNOWN; + +    // all registers of the frontend. Note that the registers can hold 9-bit values +    RegisterSettingSet<std::uint16_t> regs; + +    // extra control registers +    std::array<std::uint16_t, 3> reg2 = {}; + +    GenesysFrontendLayout layout; + +    void set_offset(unsigned which, std::uint16_t value) +    { +        regs.set_value(layout.offset_addr[which], value); +    } + +    void set_gain(unsigned which, std::uint16_t value) +    { +        regs.set_value(layout.gain_addr[which], value); +    } + +    std::uint16_t get_offset(unsigned which) const +    { +        return regs.get_value(layout.offset_addr[which]); +    } + +    std::uint16_t get_gain(unsigned which) const +    { +        return regs.get_value(layout.gain_addr[which]); +    } + +    bool operator==(const Genesys_Frontend& other) const +    { +        return id == other.id && +            regs == other.regs && +            reg2 == other.reg2 && +            layout == other.layout; +    } +}; + +std::ostream& operator<<(std::ostream& out, const Genesys_Frontend& frontend); + +template<class Stream> +void serialize(Stream& str, Genesys_Frontend& x) +{ +    serialize(str, x.id); +    serialize_newline(str); +    serialize(str, x.regs); +    serialize_newline(str); +    serialize(str, x.reg2); +    serialize_newline(str); +    serialize(str, x.layout); +} + +struct SensorExposure { +    std::uint16_t red = 0; +    std::uint16_t green = 0; +    std::uint16_t blue = 0; + +    SensorExposure() = default; +    SensorExposure(std::uint16_t r, std::uint16_t g, std::uint16_t b) : +        red{r}, green{g}, blue{b} +    {} + +    bool operator==(const SensorExposure& other) const +    { +        return red == other.red && green == other.green && blue == other.blue; +    } +}; + +std::ostream& operator<<(std::ostream& out, const SensorExposure& exposure); + + +class ResolutionFilter +{ +public: +    struct Any {}; +    static constexpr Any ANY{}; + +    ResolutionFilter() : matches_any_{false} {} +    ResolutionFilter(Any) : matches_any_{true} {} +    ResolutionFilter(std::initializer_list<unsigned> resolutions) : +        matches_any_{false}, +        resolutions_{resolutions} +    {} + +    bool matches(unsigned resolution) const +    { +        if (matches_any_) +            return true; +        auto it = std::find(resolutions_.begin(), resolutions_.end(), resolution); +        return it != resolutions_.end(); +    } + +    bool operator==(const ResolutionFilter& other) const +    { +        return  matches_any_ == other.matches_any_ && resolutions_ == other.resolutions_; +    } + +    bool matches_any() const { return matches_any_; } +    const std::vector<unsigned>& resolutions() const { return resolutions_; } + +private: +    bool matches_any_ = false; +    std::vector<unsigned> resolutions_; + +    template<class Stream> +    friend void serialize(Stream& str, ResolutionFilter& x); +}; + +std::ostream& operator<<(std::ostream& out, const ResolutionFilter& resolutions); + +template<class Stream> +void serialize(Stream& str, ResolutionFilter& x) +{ +    serialize(str, x.matches_any_); +    serialize_newline(str); +    serialize(str, x.resolutions_); +} + + +struct Genesys_Sensor { + +    Genesys_Sensor() = default; +    ~Genesys_Sensor() = default; + +    // id of the sensor description +    SensorId sensor_id = SensorId::UNKNOWN; + +    // sensor resolution in CCD pixels. Note that we may read more than one CCD pixel per logical +    // pixel, see ccd_pixels_per_system_pixel() +    unsigned optical_res = 0; + +    // the resolution list that the sensor is usable at. +    ResolutionFilter resolutions = ResolutionFilter::ANY; + +    // the channel list that the sensor is usable at +    std::vector<unsigned> channels = { 1, 3 }; + +    // the scan method used with the sensor +    ScanMethod method = ScanMethod::FLATBED; + +    // The scanner may be setup to use a custom dpihw that does not correspond to any actual +    // resolution. The value zero does not set the override. +    unsigned register_dpihw_override = 0; + +    // The scanner may be setup to use a custom logical dpihw that does not correspond to any actual +    // resolution. The value zero does not set the override. +    unsigned logical_dpihw_override = 0; + +    // The scanner may be setup to use a custom dpiset value that does not correspond to any actual +    // resolution. The value zero does not set the override. +    unsigned dpiset_override = 0; + +    // CCD may present itself as half or quarter-size CCD on certain resolutions +    int ccd_size_divisor = 1; + +    // Some scanners need an additional multiplier over the scan coordinates +    int pixel_count_multiplier = 1; + +    int black_pixels = 0; +    // value of the dummy register +    int dummy_pixel = 0; +    // last pixel of CCD margin at optical resolution +    int ccd_start_xoffset = 0; +    // total pixels used by the sensor +    int sensor_pixels = 0; +    // TA CCD target code (reference gain) +    int fau_gain_white_ref = 0; +    // CCD target code (reference gain) +    int gain_white_ref = 0; + +    // red, green and blue initial exposure values +    SensorExposure exposure; + +    int exposure_lperiod = -1; + +    // the number of pixels in a single segment. +    // only on gl843 +    unsigned segment_size = 0; + +    // the order of the segments, if any, for the sensor. If the sensor is not segmented or uses +    // only single segment, this array can be empty +    // only on gl843 +    std::vector<unsigned> segment_order; + +    // some CCDs use two arrays of pixels for double resolution. On such CCDs when scanning at +    // high-enough resolution, every other pixel column is shifted +    StaggerConfig stagger_config; + +    GenesysRegisterSettingSet custom_base_regs; // gl646-specific +    GenesysRegisterSettingSet custom_regs; +    GenesysRegisterSettingSet custom_fe_regs; + +    // red, green and blue gamma coefficient for default gamma tables +    AssignableArray<float, 3> gamma; + +    std::function<unsigned(const Genesys_Sensor&, unsigned)> get_logical_hwdpi_fun; +    std::function<unsigned(const Genesys_Sensor&, unsigned)> get_register_hwdpi_fun; +    std::function<unsigned(const Genesys_Sensor&, unsigned)> get_ccd_size_divisor_fun; +    std::function<unsigned(const Genesys_Sensor&, unsigned)> get_hwdpi_divisor_fun; + +    unsigned get_logical_hwdpi(unsigned xres) const { return get_logical_hwdpi_fun(*this, xres); } +    unsigned get_register_hwdpi(unsigned xres) const { return get_register_hwdpi_fun(*this, xres); } +    unsigned get_ccd_size_divisor_for_dpi(unsigned xres) const +    { +        return get_ccd_size_divisor_fun(*this, xres); +    } +    unsigned get_hwdpi_divisor_for_dpi(unsigned xres) const +    { +        return get_hwdpi_divisor_fun(*this, xres); +    } + +    // how many CCD pixels are processed per system pixel time. This corresponds to CKSEL + 1 +    unsigned ccd_pixels_per_system_pixel() const +    { +        // same on GL646, GL841, GL843, GL846, GL847, GL124 +        constexpr unsigned REG_CKSEL = 0x03; +        return (custom_regs.get_value(0x18) & REG_CKSEL) + 1; +    } + +    bool matches_channel_count(unsigned count) const +    { +        return std::find(channels.begin(), channels.end(), count) != channels.end(); +    } + +    unsigned get_segment_count() const +    { +        if (segment_order.size() < 2) +            return 1; +        return segment_order.size(); +    } + +    bool operator==(const Genesys_Sensor& other) const +    { +        return sensor_id == other.sensor_id && +            optical_res == other.optical_res && +            resolutions == other.resolutions && +            method == other.method && +            ccd_size_divisor == other.ccd_size_divisor && +            black_pixels == other.black_pixels && +            dummy_pixel == other.dummy_pixel && +            ccd_start_xoffset == other.ccd_start_xoffset && +            sensor_pixels == other.sensor_pixels && +            fau_gain_white_ref == other.fau_gain_white_ref && +            gain_white_ref == other.gain_white_ref && +            exposure == other.exposure && +            exposure_lperiod == other.exposure_lperiod && +            segment_size == other.segment_size && +            segment_order == other.segment_order && +            stagger_config == other.stagger_config && +            custom_base_regs == other.custom_base_regs && +            custom_regs == other.custom_regs && +            custom_fe_regs == other.custom_fe_regs && +            gamma == other.gamma; +    } +}; + +template<class Stream> +void serialize(Stream& str, Genesys_Sensor& x) +{ +    serialize(str, x.sensor_id); +    serialize(str, x.optical_res); +    serialize(str, x.resolutions); +    serialize(str, x.method); +    serialize(str, x.ccd_size_divisor); +    serialize(str, x.black_pixels); +    serialize(str, x.dummy_pixel); +    serialize(str, x.ccd_start_xoffset); +    serialize(str, x.sensor_pixels); +    serialize(str, x.fau_gain_white_ref); +    serialize(str, x.gain_white_ref); +    serialize_newline(str); +    serialize(str, x.exposure.blue); +    serialize(str, x.exposure.green); +    serialize(str, x.exposure.red); +    serialize(str, x.exposure_lperiod); +    serialize_newline(str); +    serialize(str, x.segment_size); +    serialize_newline(str); +    serialize(str, x.segment_order); +    serialize_newline(str); +    serialize(str, x.stagger_config); +    serialize_newline(str); +    serialize(str, x.custom_base_regs); +    serialize_newline(str); +    serialize(str, x.custom_regs); +    serialize_newline(str); +    serialize(str, x.custom_fe_regs); +    serialize_newline(str); +    serialize(str, x.gamma); +    serialize_newline(str); +} + +std::ostream& operator<<(std::ostream& out, const Genesys_Sensor& sensor); + +} // namespace genesys + +#endif // BACKEND_GENESYS_SENSOR_H diff --git a/backend/genesys/serialize.cpp b/backend/genesys/serialize.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/backend/genesys/serialize.cpp diff --git a/backend/genesys/serialize.h b/backend/genesys/serialize.h new file mode 100644 index 0000000..ed40a4e --- /dev/null +++ b/backend/genesys/serialize.h @@ -0,0 +1,150 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_SERIALIZE_H +#define BACKEND_GENESYS_SERIALIZE_H + +#include "error.h" +#include <array> +#include <iostream> +#include <limits> +#include <string> +#include <vector> + +namespace genesys { + +// it would be best to use something like boost.serialization + +inline void serialize_newline(std::ostream& str) { str << '\n'; } +inline void serialize_newline(std::istream& str) { (void) str; } + +inline void serialize(std::ostream& str, bool x) { str << static_cast<unsigned>(x) << " "; } +inline void serialize(std::istream& str, bool& x) { unsigned v; str >> v; x = v; } +inline void serialize(std::ostream& str, char x) { str << static_cast<int>(x) << " "; } +inline void serialize(std::istream& str, char& x) { int v; str >> v; x = v; } +inline void serialize(std::ostream& str, unsigned char x) { str << static_cast<unsigned>(x) << " "; } +inline void serialize(std::istream& str, unsigned char& x) { unsigned v; str >> v; x = v; } +inline void serialize(std::ostream& str, signed char x) { str << static_cast<int>(x) << " "; } +inline void serialize(std::istream& str, signed char& x) { int v; str >> v; x = v; } +inline void serialize(std::ostream& str, short x) { str << x << " "; } +inline void serialize(std::istream& str, short& x) { str >> x; } +inline void serialize(std::ostream& str, unsigned short x) { str << x << " "; } +inline void serialize(std::istream& str, unsigned short& x) { str >> x; } +inline void serialize(std::ostream& str, int x) { str << x << " "; } +inline void serialize(std::istream& str, int& x) { str >> x; } +inline void serialize(std::ostream& str, unsigned int x) { str << x << " "; } +inline void serialize(std::istream& str, unsigned int& x) { str >> x; } +inline void serialize(std::ostream& str, long x) { str << x << " "; } +inline void serialize(std::istream& str, long& x) { str >> x; } +inline void serialize(std::ostream& str, unsigned long x) { str << x << " "; } +inline void serialize(std::istream& str, unsigned long& x) { str >> x; } +inline void serialize(std::ostream& str, long long x) { str << x << " "; } +inline void serialize(std::istream& str, long long& x) { str >> x; } +inline void serialize(std::ostream& str, unsigned long long x) { str << x << " "; } +inline void serialize(std::istream& str, unsigned long long& x) { str >> x; } +inline void serialize(std::ostream& str, float x) { str << x << " "; } +inline void serialize(std::istream& str, float& x) { str >> x; } +inline void serialize(std::ostream& str, double x) { str << x << " "; } +inline void serialize(std::istream& str, double& x) { str >> x; } +inline void serialize(std::ostream& str, const std::string& x) { str << x << " "; } +inline void serialize(std::istream& str, std::string& x) { str >> x; } + +template<class T> +void serialize(std::ostream& str, std::vector<T>& x) +{ +    serialize(str, x.size()); +    serialize_newline(str); + +    for (auto& item : x) { +        serialize(str, item); +        serialize_newline(str); +    } +} + +template<class T> +void serialize(std::istream& str, std::vector<T>& x, +               size_t max_size = std::numeric_limits<size_t>::max()) +{ +    size_t new_size; +    serialize(str, new_size); + +    if (new_size > max_size) { +        throw SaneException("Too large std::vector to deserialize"); +    } +    x.reserve(new_size); +    for (size_t i = 0; i < new_size; ++i) { +        T item; +        serialize(str, item); +        x.push_back(item); +    } +} + +template<class T, size_t Size> +void serialize(std::ostream& str, std::array<T, Size>& x) +{ +    serialize(str, x.size()); +    serialize_newline(str); + +    for (auto& item : x) { +        serialize(str, item); +        serialize_newline(str); +    } +} + +template<class T, size_t Size> +void serialize(std::istream& str, std::array<T, Size>& x) +{ +    size_t new_size; +    serialize(str, new_size); + +    if (new_size > Size) { +        throw SaneException("Incorrect std::array size to deserialize"); +    } +    for (auto& item : x) { +        serialize(str, item); +    } +} + +} // namespace genesys + +#endif diff --git a/backend/genesys/settings.cpp b/backend/genesys/settings.cpp new file mode 100644 index 0000000..41c66de --- /dev/null +++ b/backend/genesys/settings.cpp @@ -0,0 +1,142 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "settings.h" +#include "utilities.h" +#include <iomanip> + +namespace genesys { + +std::ostream& operator<<(std::ostream& out, const Genesys_Settings& settings) +{ +    StreamStateSaver state_saver{out}; + +    out << "Genesys_Settings{\n" +        << "    xres: " << settings.xres << " yres: " << settings.yres << '\n' +        << "    lines: " << settings.lines << '\n' +        << "    pixels per line (actual): " << settings.pixels << '\n' +        << "    pixels per line (requested): " << settings.requested_pixels << '\n' +        << "    depth: " << settings.depth << '\n'; +    auto prec = out.precision(); +    out.precision(3); +    out << "    tl_x: " << settings.tl_x << " tl_y: " << settings.tl_y << '\n'; +    out.precision(prec); +    out << "    scan_mode: " << settings.scan_mode << '\n' +        << '}'; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const SetupParams& params) +{ +    StreamStateSaver state_saver{out}; + +    out << "SetupParams{\n" +        << "    xres: " << params.xres << " yres: " << params.yres << '\n' +        << "    lines: " << params.lines << '\n' +        << "    pixels per line (actual): " << params.pixels << '\n' +        << "    pixels per line (requested): " << params.requested_pixels << '\n' +        << "    depth: " << params.depth << '\n' +        << "    channels: " << params.channels << '\n' +        << "    startx: " << params.startx << " starty: " << params.starty << '\n' +        << "    scan_mode: " << params.scan_mode << '\n' +        << "    color_filter: " << params.color_filter << '\n' +        << "    flags: " << params.flags << '\n' +        << "}"; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const ScanSession& session) +{ +    out << "ScanSession{\n" +        << "    computed: " << session.computed << '\n' +        << "    hwdpi_divisor: " << session.hwdpi_divisor << '\n' +        << "    ccd_size_divisor: " << session.ccd_size_divisor << '\n' +        << "    optical_resolution: " << session.optical_resolution << '\n' +        << "    optical_pixels: " << session.optical_pixels << '\n' +        << "    optical_pixels_raw: " << session.optical_pixels_raw << '\n' +        << "    output_resolution: " << session.output_resolution << '\n' +        << "    output_pixels: " << session.output_pixels << '\n' +        << "    output_line_bytes: " << session.output_line_bytes << '\n' +        << "    output_line_bytes_raw: " << session.output_line_bytes_raw << '\n' +        << "    output_line_count: " << session.output_line_count << '\n' +        << "    num_staggered_lines: " << session.num_staggered_lines << '\n' +        << "    color_shift_lines_r: " << session.color_shift_lines_r << '\n' +        << "    color_shift_lines_g: " << session.color_shift_lines_g << '\n' +        << "    color_shift_lines_b: " << session.color_shift_lines_b << '\n' +        << "    max_color_shift_lines: " << session.max_color_shift_lines << '\n' +        << "    enable_ledadd: " << session.enable_ledadd << '\n' +        << "    segment_count: " << session.segment_count << '\n' +        << "    pixel_startx: " << session.pixel_startx << '\n' +        << "    pixel_endx: " << session.pixel_endx << '\n' +        << "    conseq_pixel_dist: " << session.conseq_pixel_dist << '\n' +        << "    output_segment_pixel_group_count: " +            << session.output_segment_pixel_group_count << '\n' +        << "    buffer_size_read: " << session.buffer_size_read << '\n' +        << "    buffer_size_read: " << session.buffer_size_lines << '\n' +        << "    buffer_size_shrink: " << session.buffer_size_shrink << '\n' +        << "    buffer_size_out: " << session.buffer_size_out << '\n' +        << "    filters: " +            << (session.pipeline_needs_reorder ? " reorder": "") +            << (session.pipeline_needs_ccd ? " ccd": "") +            << (session.pipeline_needs_shrink ? " shrink": "") << '\n' +        << "    params: " << format_indent_braced_list(4, session.params) << '\n' +        << "}"; +    return out; +} + +std::ostream& operator<<(std::ostream& out, const SANE_Parameters& params) +{ +    out << "SANE_Parameters{\n" +        << "    format: " << static_cast<unsigned>(params.format) << '\n' +        << "    last_frame: " << params.last_frame << '\n' +        << "    bytes_per_line: " << params.bytes_per_line << '\n' +        << "    pixels_per_line: " << params.pixels_per_line << '\n' +        << "    lines: " << params.lines << '\n' +        << "    depth: " << params.depth << '\n' +        << '}'; +    return out; +} + +} // namespace genesys diff --git a/backend/genesys/settings.h b/backend/genesys/settings.h new file mode 100644 index 0000000..a697e60 --- /dev/null +++ b/backend/genesys/settings.h @@ -0,0 +1,328 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_SETTINGS_H +#define BACKEND_GENESYS_SETTINGS_H + +#include "enums.h" +#include "serialize.h" + +namespace genesys { + +struct Genesys_Settings +{ +    ScanMethod scan_method = ScanMethod::FLATBED; +    ScanColorMode scan_mode = ScanColorMode::LINEART; + +    // horizontal dpi +    unsigned xres = 0; +    // vertical dpi +    unsigned yres = 0; + +    //x start on scan table in mm +    double tl_x = 0; +    // y start on scan table in mm +    double tl_y = 0; + +    // number of lines at scan resolution +    unsigned int lines = 0; +    // number of pixels expected from the scanner +    unsigned int pixels = 0; +    // number of pixels expected by the frontend +    unsigned requested_pixels = 0; + +    // bit depth of the scan +    unsigned int depth = 0; + +    ColorFilter color_filter = ColorFilter::NONE; + +    // true if scan is true gray, false if monochrome scan +    int true_gray = 0; + +    // lineart threshold +    int threshold = 0; + +    // lineart threshold curve for dynamic rasterization +    int threshold_curve = 0; + +    // Disable interpolation for xres<yres +    int disable_interpolation = 0; + +    // value for contrast enhancement in the [-100..100] range +    int contrast = 0; + +    // value for brightness enhancement in the [-100..100] range +    int brightness = 0; + +    // cache entries expiration time +    int expiration_time = 0; + +    unsigned get_channels() const +    { +        if (scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +            return 3; +        return 1; +    } +}; + +std::ostream& operator<<(std::ostream& out, const Genesys_Settings& settings); + + +struct SetupParams { + +    static constexpr unsigned NOT_SET = std::numeric_limits<unsigned>::max(); + +    // resolution in x direction +    unsigned xres = NOT_SET; +    // resolution in y direction +    unsigned yres = NOT_SET; +    // start pixel in X direction, from dummy_pixel + 1 +    unsigned startx = NOT_SET; +    // start pixel in Y direction, counted according to base_ydpi +    unsigned starty = NOT_SET; +    // the number of pixels in X direction. Note that each logical pixel may correspond to more +    // than one CCD pixel, see CKSEL and GenesysSensor::ccd_pixels_per_system_pixel() +    unsigned pixels = NOT_SET; + +    // the number of pixels in the X direction as requested by the frontend. This will be different +    // from `pixels` if the X resolution requested by the frontend is different than the actual +    // resolution. This is only needed to compute dev->total_bytes_to_read. If 0, then the value +    // is the same as pixels. +    // TODO: move the computation of total_bytes_to_read to a higher layer. +    unsigned requested_pixels = 0; + +    // the number of pixels in Y direction +    unsigned lines = NOT_SET; +    // the depth of the scan in bits. Allowed are 1, 8, 16 +    unsigned depth = NOT_SET; +    // the number of channels +    unsigned channels = NOT_SET; + +    ScanMethod scan_method = static_cast<ScanMethod>(NOT_SET); + +    ScanColorMode scan_mode = static_cast<ScanColorMode>(NOT_SET); + +    ColorFilter color_filter = static_cast<ColorFilter>(NOT_SET); + +    ScanFlag flags; + +    unsigned get_requested_pixels() const +    { +        if (requested_pixels != 0) { +            return requested_pixels; +        } +        return pixels; +    } + +    void assert_valid() const +    { +        if (xres == NOT_SET || yres == NOT_SET || startx == NOT_SET || starty == NOT_SET || +            pixels == NOT_SET || lines == NOT_SET ||depth == NOT_SET || channels == NOT_SET || +            scan_method == static_cast<ScanMethod>(NOT_SET) || +            scan_mode == static_cast<ScanColorMode>(NOT_SET) || +            color_filter == static_cast<ColorFilter>(NOT_SET)) +        { +            throw std::runtime_error("SetupParams are not valid"); +        } +    } + +    bool operator==(const SetupParams& other) const +    { +        return xres == other.xres && +            yres == other.yres && +            startx == other.startx && +            starty == other.starty && +            pixels == other.pixels && +            requested_pixels == other.requested_pixels && +            lines == other.lines && +            depth == other.depth && +            channels == other.channels && +            scan_method == other.scan_method && +            scan_mode == other.scan_mode && +            color_filter == other.color_filter && +            flags == other.flags; +    } +}; + +std::ostream& operator<<(std::ostream& out, const SetupParams& params); + +template<class Stream> +void serialize(Stream& str, SetupParams& x) +{ +    serialize(str, x.xres); +    serialize(str, x.yres); +    serialize(str, x.startx); +    serialize(str, x.starty); +    serialize(str, x.pixels); +    serialize(str, x.requested_pixels); +    serialize(str, x.lines); +    serialize(str, x.depth); +    serialize(str, x.channels); +    serialize(str, x.scan_method); +    serialize(str, x.scan_mode); +    serialize(str, x.color_filter); +    serialize(str, x.flags); +} + +struct ScanSession { +    SetupParams params; + +    // whether the session setup has been computed via compute_session() +    bool computed = false; + +    // specifies the reduction (if any) of hardware dpi on the Genesys chip side. +    // except gl646 +    unsigned hwdpi_divisor = 1; + +    // specifies the reduction (if any) of CCD effective dpi which is performed by latching the +    // data coming from CCD in such a way that 1/2 or 3/4 of pixel data is ignored. +    unsigned ccd_size_divisor = 1; + +    // the optical resolution of the scanner. +    unsigned optical_resolution = 0; + +    // the number of pixels at the optical resolution, not including segmentation overhead. +    unsigned optical_pixels = 0; + +    // the number of pixels at the optical resolution, including segmentation overhead. +    // only on gl846, g847 +    unsigned optical_pixels_raw = 0; + +    // the resolution of the output data. +    // gl843-only +    unsigned output_resolution = 0; + +    // the number of pixels in output data (after desegmentation) +    unsigned output_pixels = 0; + +    // the number of bytes in the output of a channel of a single line (after desegmentation) +    unsigned output_channel_bytes = 0; + +    // the number of bytes in the output of a single line (after desegmentation) +    unsigned output_line_bytes = 0; + +    // the number of bytes per line in the output data from the scanner (before desegmentation) +    // Equal to output_line_bytes if sensor does not have segments +    unsigned output_line_bytes_raw = 0; + +    // the number of bytes per line as requested by the frontend +    unsigned output_line_bytes_requested = 0; + +    // the number of lines in the output of the scanner. This must be larger than the user +    // requested number due to line staggering and color channel shifting. +    unsigned output_line_count = 0; + +    // the total number of bytes to read from the scanner (before desegmentation) +    unsigned output_total_bytes_raw = 0; + +    // the total number of bytes to read from the scanner (after desegmentation) +    unsigned output_total_bytes = 0; + +    // the number of staggered lines (i.e. lines that overlap during scanning due to line being +    // thinner than the CCD element) +    unsigned num_staggered_lines = 0; + +    // the number of lines that color channels shift due to different physical positions of +    // different color channels. +    unsigned max_color_shift_lines = 0; + +    // actual line shift of the red color +    unsigned color_shift_lines_r = 0; +    // actual line shift of the green color +    unsigned color_shift_lines_g = 0; +    // actual line shift of the blue color +    unsigned color_shift_lines_b = 0; + +    // the number of scanner segments used in the current scan +    unsigned segment_count = 1; + +    // the physical pixel positions that are sent to the registers +    unsigned pixel_startx = 0; +    unsigned pixel_endx = 0; + +    // certain scanners require the logical pixel count to be multiplied on certain resolutions +    unsigned pixel_count_multiplier = 1; + +    // Distance in pixels between consecutive pixels, e.g. between odd and even pixels. Note that +    // the number of segments can be large. +    // only on gl124, gl846, gl847 +    unsigned conseq_pixel_dist = 0; + +    // The number of "even" pixels to scan. This corresponds to the number of pixels that will be +    // scanned from a single segment +    // only on gl124, gl846, gl847 +    unsigned output_segment_pixel_group_count = 0; + +    // The number of bytes to skip at start of line during desegmentation. +    // Currently it's always zero. +    unsigned output_segment_start_offset = 0; + +    // the sizes of the corresponding buffers +    size_t buffer_size_read = 0; +    size_t buffer_size_lines = 0; +    size_t buffer_size_shrink = 0; +    size_t buffer_size_out = 0; + +    // whether to enable ledadd functionality +    bool enable_ledadd = false; + +    // what pipeline modifications are needed +    bool pipeline_needs_reorder = false; +    bool pipeline_needs_ccd = false; +    bool pipeline_needs_shrink = false; + +    void assert_computed() const +    { +        if (!computed) { +            throw std::runtime_error("ScanSession is not computed"); +        } +    } +}; + +std::ostream& operator<<(std::ostream& out, const ScanSession& session); + +std::ostream& operator<<(std::ostream& out, const SANE_Parameters& params); + +} // namespace genesys + +#endif // BACKEND_GENESYS_SETTINGS_H diff --git a/backend/genesys/static_init.cpp b/backend/genesys/static_init.cpp new file mode 100644 index 0000000..c0f3748 --- /dev/null +++ b/backend/genesys/static_init.cpp @@ -0,0 +1,70 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "static_init.h" +#include <vector> + +namespace genesys { + +static std::unique_ptr<std::vector<std::function<void()>>> s_functions_run_at_backend_exit; + +void add_function_to_run_at_backend_exit(const std::function<void()>& function) +{ +    if (!s_functions_run_at_backend_exit) +        s_functions_run_at_backend_exit.reset(new std::vector<std::function<void()>>()); +    s_functions_run_at_backend_exit->push_back(std::move(function)); +} + +void run_functions_at_backend_exit() +{ +    for (auto it = s_functions_run_at_backend_exit->rbegin(); +         it != s_functions_run_at_backend_exit->rend(); ++it) +    { +        (*it)(); +    } +    s_functions_run_at_backend_exit.reset(); +} + +} // namespace genesys diff --git a/backend/genesys/static_init.h b/backend/genesys/static_init.h new file mode 100644 index 0000000..3ffa62c --- /dev/null +++ b/backend/genesys/static_init.h @@ -0,0 +1,88 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_STATIC_INIT_H +#define BACKEND_GENESYS_STATIC_INIT_H + +#include <functional> +#include <memory> + +namespace genesys { + +void add_function_to_run_at_backend_exit(const std::function<void()>& function); + +// calls functions added via add_function_to_run_at_backend_exit() in reverse order of being +// added. +void run_functions_at_backend_exit(); + +template<class T> +class StaticInit { +public: +    StaticInit() = default; +    StaticInit(const StaticInit&) = delete; +    StaticInit& operator=(const StaticInit&) = delete; + +    template<class... Args> +    void init(Args&& ... args) +    { +        ptr_ = std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +        add_function_to_run_at_backend_exit([this](){ deinit(); }); +    } + +    void deinit() +    { +        ptr_.reset(); +    } + +    const T* operator->() const { return ptr_.get(); } +    T* operator->() { return ptr_.get(); } +    const T& operator*() const { return *ptr_.get(); } +    T& operator*() { return *ptr_.get(); } + +private: +    std::unique_ptr<T> ptr_; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_STATIC_INIT_H diff --git a/backend/genesys/status.cpp b/backend/genesys/status.cpp new file mode 100644 index 0000000..7f883b0 --- /dev/null +++ b/backend/genesys/status.cpp @@ -0,0 +1,66 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "status.h" +#include <iostream> + +namespace genesys { + +std::ostream& operator<<(std::ostream& out, Status status) +{ +    out << "Status{\n" +        << "    replugged: " << (status.is_replugged ? "yes" : "no") << '\n' +        << "    is_buffer_empty: " << (status.is_buffer_empty ? "yes" : "no") << '\n' +        << "    is_feeding_finished: " << (status.is_feeding_finished ? "yes" : "no") << '\n' +        << "    is_scanning_finished: " << (status.is_scanning_finished ? "yes" : "no") << '\n' +        << "    is_at_home: " << (status.is_at_home ? "yes" : "no") << '\n' +        << "    is_lamp_on: " << (status.is_lamp_on ? "yes" : "no") << '\n' +        << "    is_front_end_busy: " << (status.is_front_end_busy ? "yes" : "no") << '\n' +        << "    is_motor_enabled: " << (status.is_motor_enabled ? "yes" : "no") << '\n' +        << "}\n"; +    return out; +} + +} // namespace genesys diff --git a/backend/genesys/status.h b/backend/genesys/status.h new file mode 100644 index 0000000..91f4692 --- /dev/null +++ b/backend/genesys/status.h @@ -0,0 +1,68 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_STATUS_H +#define BACKEND_GENESYS_STATUS_H + +#include <iosfwd> + +namespace genesys { + +/// Represents the scanner status register +struct Status +{ +    bool is_replugged = false; +    bool is_buffer_empty = false; +    bool is_feeding_finished = false; +    bool is_scanning_finished = false; +    bool is_at_home = false; +    bool is_lamp_on = false; +    bool is_front_end_busy = false; +    bool is_motor_enabled = false; +}; + +std::ostream& operator<<(std::ostream& out, Status status); + +} // namespace genesys + +#endif // BACKEND_GENESYS_STATUS_H diff --git a/backend/genesys/tables_frontend.cpp b/backend/genesys/tables_frontend.cpp new file mode 100644 index 0000000..1edf32f --- /dev/null +++ b/backend/genesys/tables_frontend.cpp @@ -0,0 +1,653 @@ +/*  sane - Scanner Access Now Easy. + +    Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +    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 "low.h" + +namespace genesys { + +StaticInit<std::vector<Genesys_Frontend>> s_frontends; + +void genesys_init_frontend_tables() +{ +    s_frontends.init(); + +    GenesysFrontendLayout wolfson_layout; +    wolfson_layout.type = FrontendType::WOLFSON; +    wolfson_layout.offset_addr = { 0x20, 0x21, 0x22 }; +    wolfson_layout.gain_addr = { 0x28, 0x29, 0x2a }; + +    GenesysFrontendLayout analog_devices; +    analog_devices.type = FrontendType::ANALOG_DEVICES; + + +    Genesys_Frontend fe; +    fe.id = AdcId::WOLFSON_UMAX; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x05 }, +        { 0x03, 0x11 }, +        { 0x20, 0x80 }, +        { 0x21, 0x80 }, +        { 0x22, 0x80 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x02 }, +        { 0x29, 0x02 }, +        { 0x2a, 0x02 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_ST12; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x05 }, +        { 0x03, 0x03 }, +        { 0x20, 0xc8 }, +        { 0x21, 0xc8 }, +        { 0x22, 0xc8 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x04 }, +        { 0x29, 0x04 }, +        { 0x2a, 0x04 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_ST24; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x05 }, +        { 0x03, 0x21 }, +        { 0x20, 0xc8 }, +        { 0x21, 0xc8 }, +        { 0x22, 0xc8 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x06 }, +        { 0x29, 0x06 }, +        { 0x2a, 0x06 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_5345; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x05 }, +        { 0x03, 0x12 }, +        { 0x20, 0xb8 }, +        { 0x21, 0xb8 }, +        { 0x22, 0xb8 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x04 }, +        { 0x29, 0x04 }, +        { 0x2a, 0x04 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    // reg3=0x02 for 50-600 dpi, 0x32 (0x12 also works well) at 1200 +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_HP2400; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x05 }, +        { 0x03, 0x02 }, +        { 0x20, 0xb4 }, +        { 0x21, 0xb6 }, +        { 0x22, 0xbc }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x06 }, +        { 0x29, 0x09 }, +        { 0x2a, 0x08 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_HP2300; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x04 }, +        { 0x03, 0x02 }, +        { 0x20, 0xbe }, +        { 0x21, 0xbe }, +        { 0x22, 0xbe }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x04 }, +        { 0x29, 0x04 }, +        { 0x2a, 0x04 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_LIDE_35; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x3d }, +        { 0x02, 0x08 }, +        { 0x03, 0x00 }, +        { 0x20, 0xe1 }, +        { 0x21, 0xe1 }, +        { 0x22, 0xe1 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x93 }, +        { 0x29, 0x93 }, +        { 0x2a, 0x93 }, +    }; +    fe.reg2 = {0x00, 0x19, 0x06}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::AD_XP200; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x58 }, +        { 0x01, 0x80 }, +        { 0x02, 0x00 }, +        { 0x03, 0x00 }, +        { 0x20, 0x09 }, +        { 0x21, 0x09 }, +        { 0x22, 0x09 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x09 }, +        { 0x29, 0x09 }, +        { 0x2a, 0x09 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_XP300; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x35 }, +        { 0x02, 0x20 }, +        { 0x03, 0x14 }, +        { 0x20, 0xe1 }, +        { 0x21, 0xe1 }, +        { 0x22, 0xe1 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x93 }, +        { 0x29, 0x93 }, +        { 0x2a, 0x93 }, +    }; +    fe.reg2 = {0x07, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_HP3670; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x03 }, +        { 0x02, 0x05 }, +        { 0x03, 0x32 }, +        { 0x20, 0xba }, +        { 0x21, 0xb8 }, +        { 0x22, 0xb8 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x06 }, +        { 0x29, 0x05 }, +        { 0x2a, 0x04 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::WOLFSON_DSM600; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x35 }, +        { 0x02, 0x20 }, +        { 0x03, 0x14 }, +        { 0x20, 0x85 }, +        { 0x21, 0x85 }, +        { 0x22, 0x85 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0xa0 }, +        { 0x29, 0xa0 }, +        { 0x2a, 0xa0 }, +    }; +    fe.reg2 = {0x07, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_LIDE_200; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x9d }, +        { 0x01, 0x91 }, +        { 0x02, 0x00 }, +        { 0x03, 0x00 }, +        { 0x20, 0x00 }, +        { 0x21, 0x3f }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x32 }, +        { 0x29, 0x04 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_LIDE_700F; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x9d }, +        { 0x01, 0x9e }, +        { 0x02, 0x00 }, +        { 0x03, 0x00 }, +        { 0x20, 0x00 }, +        { 0x21, 0x3f }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x2f }, +        { 0x29, 0x04 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::KVSS080; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x23 }, +        { 0x02, 0x24 }, +        { 0x03, 0x0f }, +        { 0x20, 0x80 }, +        { 0x21, 0x80 }, +        { 0x22, 0x80 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x4b }, +        { 0x29, 0x4b }, +        { 0x2a, 0x4b }, +    }; +    fe.reg2 = {0x00,0x00,0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::G4050; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x23 }, +        { 0x02, 0x24 }, +        { 0x03, 0x1f }, +        { 0x20, 0x45 }, +        { 0x21, 0x45 }, +        { 0x22, 0x45 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x4b }, +        { 0x29, 0x4b }, +        { 0x2a, 0x4b }, +    }; +    fe.reg2 = {0x00,0x00,0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_LIDE_110; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x80 }, +        { 0x01, 0x8a }, +        { 0x02, 0x23 }, +        { 0x03, 0x4c }, +        { 0x20, 0x00 }, +        { 0x21, 0x00 }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0xca }, +        { 0x26, 0x94 }, +        { 0x28, 0x00 }, +        { 0x29, 0x00 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + +    /** @brief GL124 special case +    * for GL124 based scanners, this struct is "abused" +    * in fact the fields are map like below to AFE registers +    * (from Texas Instrument or alike ?) +    */ +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_LIDE_120; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x80 }, +        { 0x01, 0xa3 }, +        { 0x02, 0x2b }, +        { 0x03, 0x4c }, +        { 0x20, 0x00 }, +        { 0x21, 0x00 }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, // actual address 0x05 +        { 0x25, 0xca }, // actual address 0x06 +        { 0x26, 0x95 }, // actual address 0x07 +        { 0x28, 0x00 }, +        { 0x29, 0x00 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::PLUSTEK_OPTICPRO_3600; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x70 }, +        { 0x01, 0x80 }, +        { 0x02, 0x00 }, +        { 0x03, 0x00 }, +        { 0x20, 0x00 }, +        { 0x21, 0x00 }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x3f }, +        { 0x29, 0x3d }, +        { 0x2a, 0x3d }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::PLUSTEK_OPTICFILM_7200I; +    fe.layout = analog_devices; +    fe.regs = { +        { 0x00, 0xf8 }, +        { 0x01, 0x80 }, +        { 0x02, 0x0a }, +        { 0x03, 0x06 }, +        { 0x04, 0x0f }, +        { 0x05, 0x56 }, +        { 0x06, 0x64 }, +        { 0x07, 0x56 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::PLUSTEK_OPTICFILM_7300; +    fe.layout = analog_devices; +    fe.regs = { +        { 0x00, 0xf8 }, +        { 0x01, 0x80 }, +        { 0x02, 0x10 }, +        { 0x03, 0x06 }, +        { 0x04, 0x06 }, +        { 0x05, 0x09 }, +        { 0x06, 0x0a }, +        { 0x07, 0x0102 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::PLUSTEK_OPTICFILM_7500I; +    fe.layout = analog_devices; +    fe.regs = { +        { 0x00, 0xf8 }, +        { 0x01, 0x80 }, +        { 0x02, 0x1d }, +        { 0x03, 0x17 }, +        { 0x04, 0x13 }, +        { 0x05, 0x00 }, +        { 0x06, 0x00 }, +        { 0x07, 0x0111 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_4400F; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x23 }, +        { 0x02, 0x24 }, +        { 0x03, 0x2f }, +        { 0x20, 0x6d }, +        { 0x21, 0x67 }, +        { 0x22, 0x5b }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0xd8 }, +        { 0x29, 0xd1 }, +        { 0x2a, 0xb9 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_8400F; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x23 }, +        { 0x02, 0x24 }, +        { 0x03, 0x0f }, +        { 0x20, 0x60 }, +        { 0x21, 0x5c }, +        { 0x22, 0x6c }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x8a }, +        { 0x29, 0x9f }, +        { 0x2a, 0xc2 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_8600F; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x00 }, +        { 0x01, 0x23 }, +        { 0x02, 0x24 }, +        { 0x03, 0x2f }, +        { 0x20, 0x67 }, +        { 0x21, 0x69 }, +        { 0x22, 0x68 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0xdb }, +        { 0x29, 0xda }, +        { 0x2a, 0xd7 }, +    }; +    fe.reg2 = { 0x00, 0x00, 0x00 }; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::IMG101; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x78 }, +        { 0x01, 0xf0 }, +        { 0x02, 0x00 }, +        { 0x03, 0x00 }, +        { 0x20, 0x00 }, +        { 0x21, 0x00 }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x00 }, +        { 0x29, 0x00 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    fe = Genesys_Frontend(); +    fe.id = AdcId::PLUSTEK_OPTICBOOK_3800; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x78 }, +        { 0x01, 0xf0 }, +        { 0x02, 0x00 }, +        { 0x03, 0x00 }, +        { 0x20, 0x00 }, +        { 0x21, 0x00 }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x00 }, +        { 0x29, 0x00 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); + + +    /* reg0: control 74 data, 70 no data +    * reg3: offset +    * reg6: gain +    * reg0 , reg3, reg6 */ +    fe = Genesys_Frontend(); +    fe.id = AdcId::CANON_LIDE_80; +    fe.layout = wolfson_layout; +    fe.regs = { +        { 0x00, 0x70 }, +        { 0x01, 0x16 }, +        { 0x02, 0x60 }, +        { 0x03, 0x00 }, +        { 0x20, 0x00 }, +        { 0x21, 0x00 }, +        { 0x22, 0x00 }, +        { 0x24, 0x00 }, +        { 0x25, 0x00 }, +        { 0x26, 0x00 }, +        { 0x28, 0x00 }, +        { 0x29, 0x00 }, +        { 0x2a, 0x00 }, +    }; +    fe.reg2 = {0x00, 0x00, 0x00}; +    s_frontends->push_back(fe); +} + +} // namespace genesys diff --git a/backend/genesys/tables_gpo.cpp b/backend/genesys/tables_gpo.cpp new file mode 100644 index 0000000..2c9ad5e --- /dev/null +++ b/backend/genesys/tables_gpo.cpp @@ -0,0 +1,415 @@ +/*  sane - Scanner Access Now Easy. + +    Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +    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 "low.h" + +namespace genesys { + +StaticInit<std::vector<Genesys_Gpo>> s_gpo; + +void genesys_init_gpo_tables() +{ +    s_gpo.init(); + +    Genesys_Gpo gpo; +    gpo.id = GpioId::UMAX; +    gpo.regs = { +        { 0x66, 0x11 }, +        { 0x67, 0x00 }, +        { 0x68, 0x51 }, +        { 0x69, 0x20 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::ST12; +    gpo.regs = { +        { 0x66, 0x11 }, +        { 0x67, 0x00 }, +        { 0x68, 0x51 }, +        { 0x69, 0x20 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::ST24; +    gpo.regs = { +        { 0x66, 0x00 }, +        { 0x67, 0x00 }, +        { 0x68, 0x51 }, +        { 0x69, 0x20 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::MD_5345; // bits 11-12 are for bipolar V-ref input voltage +    gpo.regs = { +        { 0x66, 0x30 }, +        { 0x67, 0x18 }, +        { 0x68, 0xa0 }, +        { 0x69, 0x18 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::HP2400; +    gpo.regs = { +        { 0x66, 0x30 }, +        { 0x67, 0x00 }, +        { 0x68, 0x31 }, +        { 0x69, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::HP2300; +    gpo.regs = { +        { 0x66, 0x00 }, +        { 0x67, 0x00 }, +        { 0x68, 0x00 }, +        { 0x69, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_35; +    gpo.regs = { +        { 0x6c, 0x02 }, +        { 0x6d, 0x80 }, +        { 0x6e, 0xef }, +        { 0x6f, 0x80 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::XP200; +    gpo.regs = { +        { 0x66, 0x30 }, +        { 0x67, 0x00 }, +        { 0x68, 0xb0 }, +        { 0x69, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::HP3670; +    gpo.regs = { +        { 0x66, 0x00 }, +        { 0x67, 0x00 }, +        { 0x68, 0x00 }, +        { 0x69, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::XP300; +    gpo.regs = { +        { 0x6c, 0x09 }, +        { 0x6d, 0xc6 }, +        { 0x6e, 0xbb }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::DP665; +    gpo.regs = { +        { 0x6c, 0x18 }, +        { 0x6d, 0x00 }, +        { 0x6e, 0xbb }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::DP685; +    gpo.regs = { +        { 0x6c, 0x3f }, +        { 0x6d, 0x46 }, +        { 0x6e, 0xfb }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_200; +    gpo.regs = { +        { 0x6c, 0xfb }, // 0xfb when idle , 0xf9/0xe9 (1200) when scanning +        { 0x6d, 0x20 }, +        { 0x6e, 0xff }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_700F; +    gpo.regs = { +        { 0x6c, 0xdb }, +        { 0x6d, 0xff }, +        { 0x6e, 0xff }, +        { 0x6f, 0x80 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::KVSS080; +    gpo.regs = { +        { 0x6c, 0xf5 }, +        { 0x6d, 0x20 }, +        { 0x6e, 0x7e }, +        { 0x6f, 0xa1 }, +        { 0xa6, 0x06 }, +        { 0xa7, 0x0f }, +        { 0xa8, 0x00 }, +        { 0xa9, 0x08 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::G4050; +    gpo.regs = { +        { 0x6c, 0x20 }, +        { 0x6d, 0x00 }, +        { 0x6e, 0xfc }, +        { 0x6f, 0x00 }, +        { 0xa6, 0x08 }, +        { 0xa7, 0x1e }, +        { 0xa8, 0x3e }, +        { 0xa9, 0x06 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::HP_N6310; +    gpo.regs = { +        { 0x6c, 0xa3 }, +        { 0x6d, 0x00 }, +        { 0x6e, 0x7f }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_110; +    gpo.regs = { +        { 0x6c, 0xfb }, +        { 0x6d, 0x20 }, +        { 0x6e, 0xff }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_120; +    gpo.regs = { +        { 0x6c, 0xfb }, +        { 0x6d, 0x20 }, +        { 0x6e, 0xff }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_210; +    gpo.regs = { +        { 0x6c, 0xfb }, +        { 0x6d, 0x20 }, +        { 0x6e, 0xff }, +        { 0x6f, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::PLUSTEK_OPTICPRO_3600; +    gpo.regs = { +        { 0x6c, 0x02 }, +        { 0x6d, 0x00 }, +        { 0x6e, 0x1e }, +        { 0x6f, 0x80 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::PLUSTEK_OPTICFILM_7200I; +    gpo.regs = { +        { 0x6c, 0x4c }, +        { 0x6d, 0x80 }, +        { 0x6e, 0x4c }, +        { 0x6f, 0x80 }, +        { 0xa6, 0x00 }, +        { 0xa7, 0x07 }, +        { 0xa8, 0x20 }, +        { 0xa9, 0x01 }, +    }; +    s_gpo->push_back(gpo); + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::PLUSTEK_OPTICFILM_7300; +    gpo.regs = { +        { 0x6c, 0x4c }, +        { 0x6d, 0x00 }, +        { 0x6e, 0x4c }, +        { 0x6f, 0x80 }, +        { 0xa6, 0x00 }, +        { 0xa7, 0x07 }, +        { 0xa8, 0x20 }, +        { 0xa9, 0x01 }, +    }; +    s_gpo->push_back(gpo); + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::PLUSTEK_OPTICFILM_7500I; +    gpo.regs = { +        { 0x6c, 0x4c }, +        { 0x6d, 0x00 }, +        { 0x6e, 0x4c }, +        { 0x6f, 0x80 }, +        { 0xa6, 0x00 }, +        { 0xa7, 0x07 }, +        { 0xa8, 0x20 }, +        { 0xa9, 0x01 }, +    }; +    s_gpo->push_back(gpo); + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_4400F; +    gpo.regs = { +        { 0x6c, 0x01 }, +        { 0x6d, 0x7f }, +        { 0x6e, 0xff }, +        { 0x6f, 0x00 }, +        { 0xa6, 0x00 }, +        { 0xa7, 0xff }, +        { 0xa8, 0x07 }, +        { 0xa9, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_8400F; +    gpo.regs = { +        { 0x6c, 0x9a }, +        { 0x6d, 0xdf }, +        { 0x6e, 0xfe }, +        { 0x6f, 0x60 }, +        { 0xa6, 0x00 }, +        { 0xa7, 0x03 }, +        { 0xa8, 0x00 }, +        { 0xa9, 0x02 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_8600F; +    gpo.regs = { +        { 0x6c, 0x20 }, +        { 0x6d, 0x7c }, +        { 0x6e, 0xff }, +        { 0x6f, 0x00 }, +        { 0xa6, 0x00 }, +        { 0xa7, 0xff }, +        { 0xa8, 0x00 }, +        { 0xa9, 0x00 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::IMG101; +    gpo.regs = { +        { 0x6c, 0x41 }, +        { 0x6d, 0xa4 }, +        { 0x6e, 0x13 }, +        { 0x6f, 0xa7 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::PLUSTEK_OPTICBOOK_3800; +    gpo.regs = { +        { 0x6c, 0x41 }, +        { 0x6d, 0xa4 }, +        { 0x6e, 0x13 }, +        { 0x6f, 0xa7 }, +    }; +    s_gpo->push_back(gpo); + + +    gpo = Genesys_Gpo(); +    gpo.id = GpioId::CANON_LIDE_80; +    gpo.regs = { +        { 0x6c, 0x28 }, +        { 0x6d, 0x90 }, +        { 0x6e, 0x75 }, +        { 0x6f, 0x80 }, +    }; +    s_gpo->push_back(gpo); +} + +} // namespace genesys diff --git a/backend/genesys/tables_model.cpp b/backend/genesys/tables_model.cpp new file mode 100644 index 0000000..0b3a0af --- /dev/null +++ b/backend/genesys/tables_model.cpp @@ -0,0 +1,2958 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003 Oliver Rauch +   Copyright (C) 2003-2005 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004, 2005 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) 2010 Jack McGill <jmcgill85258@yahoo.com> +   Copyright (C) 2010 Andrey Loginov <avloginov@gmail.com>, +                   xerox travelscan device entry +   Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de> +                 for Plustek Opticbook 3600 support +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "low.h" + +namespace genesys { + +StaticInit<std::vector<Genesys_USB_Device_Entry>> s_usb_devices; + +void genesys_init_usb_device_tables() +{ +    s_usb_devices.init(); + +    Genesys_Model model; +    model.name = "umax-astra-4500"; +    model.vendor = "UMAX"; +    model.model = "Astra 4500"; +    model.model_id = ModelId::UMAX_ASTRA_4500; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 75 }, +            { 2400, 1200, 600, 300, 150, 75 } +        } +    }; +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 3.5; +    model.y_offset = 7.5; +    model.x_size = 218.0; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 1.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 8; +    model.ld_shift_b = 16; + +    model.line_mode_color_order = ColorOrder::BGR; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_UMAX; +    model.adc_id = AdcId::WOLFSON_UMAX; +    model.gpio_id = GpioId::UMAX; +    model.motor_id = MotorId::UMAX; +    model.flags = GENESYS_FLAG_UNTESTED; +    model.buttons = GENESYS_HAS_NO_BUTTONS; +    model.shading_lines = 20; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x0638, 0x0a10, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-50"; +    model.vendor = "Canon"; +    model.model = "LiDE 35/40/50"; +    model.model_id = ModelId::CANON_LIDE_50; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 200, 150, 75 }, +            { 2400, 1200, 600, 300, 200, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.42; +    model.y_offset = 7.9; +    model.x_size = 218.0; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 6.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_35; +    model.adc_id = AdcId::CANON_LIDE_35; +    model.gpio_id = GpioId::CANON_LIDE_35; +    model.motor_id = MotorId::CANON_LIDE_35; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_WHITE_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_FILE_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_COPY_SW; +    model.shading_lines = 280; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x2213, model); + + +    model = Genesys_Model(); +    model.name = "panasonic-kv-ss080"; +    model.vendor = "Panasonic"; +    model.model = "KV-SS080"; +    model.model_id = ModelId::PANASONIC_KV_SS080; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, /* 500, 400,*/ 300, 200, 150, 100, 75 }, +            { 1200, 600, /* 500, 400, */ 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 7.2; +    model.y_offset = 14.7; +    model.x_size = 217.7; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 9.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 0.0; +    model.y_size_ta = 0.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 8; +    model.ld_shift_b = 16; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_KVSS080; +    model.adc_id = AdcId::KVSS080; +    model.gpio_id = GpioId::KVSS080; +    model.motor_id = MotorId::KVSS080; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x04da, 0x100f, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-4850c"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet 4850C"; +    model.model_id = ModelId::HP_SCANJET_4850C; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 7.9; +    model.y_offset = 10.0; +    model.x_size = 219.6; +    model.y_size = 314.5; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_HP_4850C; +    model.adc_id = AdcId::G4050; +    model.gpio_id = GpioId::G4050; +    model.motor_id = MotorId::G4050; +    model.flags = GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; +    s_usb_devices->emplace_back(0x03f0, 0x1b05, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-g4010"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet G4010"; +    model.model_id = ModelId::HP_SCANJET_G4010; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 8.0; +    model.y_offset = 13.00; +    model.x_size = 217.9; +    model.y_size = 315.0; + +    model.y_offset_calib_white = 3.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_G4050; +    model.adc_id = AdcId::G4050; +    model.gpio_id = GpioId::G4050; +    model.motor_id = MotorId::G4050; +    model.flags = GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x03f0, 0x4505, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-g4050"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet G4050"; +    model.model_id = ModelId::HP_SCANJET_G4050; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 8.0; +    model.y_offset = 10.00; +    model.x_size = 217.9; +    model.y_size = 315.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 8.0; +    model.y_offset_ta = 13.00; +    model.x_size_ta = 217.9; +    model.y_size_ta = 250.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 40.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_G4050; +    model.adc_id = AdcId::G4050; +    model.gpio_id = GpioId::G4050; +    model.motor_id = MotorId::G4050; +    model.flags = GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x03f0, 0x4605, model); + + +    model = Genesys_Model(); +    model.name = "canon-canoscan-4400f"; +    model.vendor = "Canon"; +    model.model = "Canoscan 4400f"; +    model.model_id = ModelId::CANON_4400F; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300 }, +            { 1200, 600, 300 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 6.0; +    model.y_offset = 12.00; +    model.x_size = 215.9; +    model.y_size = 297.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 8.0; +    model.y_offset_ta = 13.00; +    model.x_size_ta = 217.9; +    model.y_size_ta = 250.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 40.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 96; +    model.ld_shift_g = 48; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_CANON_4400F; +    model.adc_id = AdcId::CANON_4400F; +    model.gpio_id = GpioId::CANON_4400F; +    model.motor_id = MotorId::CANON_4400F; +    model.flags = GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_FULL_HWDPI_MODE | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SHADING_REPARK; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x04a9, 0x2228, model); + + +    model = Genesys_Model(); +    model.name = "canon-canoscan-8400f"; +    model.vendor = "Canon"; +    model.model = "Canoscan 8400f"; +    model.model_id = ModelId::CANON_8400F; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 3200, 1600, 800, 400 }, +            { 3200, 1600, 800, 400 }, +        }, { +            { ScanMethod::TRANSPARENCY }, +            { 3200, 1600, 800, 400 }, +            { 3200, 1600, 800, 400 }, +        }, { +            { ScanMethod::TRANSPARENCY_INFRARED }, +            { 3200, 1600, 800, 400 }, +            { 3200, 1600, 800, 400 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 3.5; +    model.y_offset = 17.00; +    model.x_size = 219.9; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 10.0; + +    model.x_offset_ta = 75.0; +    model.y_offset_ta = 45.00; +    model.x_size_ta = 75.0; +    model.y_size_ta = 230.0; + +    model.y_offset_sensor_to_ta = 22.0; +    model.y_offset_calib_white_ta = 25.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_CANON_8400F; +    model.adc_id = AdcId::CANON_8400F; +    model.gpio_id = GpioId::CANON_8400F; +    model.motor_id = MotorId::CANON_8400F; +    model.flags = GENESYS_FLAG_HAS_UTA | +                  GENESYS_FLAG_HAS_UTA_INFRARED | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_FULL_HWDPI_MODE | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SHADING_REPARK; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 100; +    model.shading_ta_lines = 50; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x04a9, 0x221e, model); + + +    model = Genesys_Model(); +    model.name = "canon-canoscan-8600f"; +    model.vendor = "Canon"; +    model.model = "Canoscan 8600f"; +    model.model_id = ModelId::CANON_8600F; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300 }, +            { 1200, 600, 300 }, +        }, { +            { ScanMethod::TRANSPARENCY, ScanMethod::TRANSPARENCY_INFRARED }, +            { 4800, 2400, 1200, 600, 300 }, +            { 4800, 2400, 1200, 600, 300 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 24.0; +    model.y_offset = 10.0; +    model.x_size = 216.0; +    model.y_size = 297.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 8.0; + +    model.x_offset_ta = 85.0; +    model.y_offset_ta = 26.0; +    model.x_size_ta = 70.0; +    model.y_size_ta = 230.0; + +    model.y_offset_sensor_to_ta = 11.5; +    model.y_offset_calib_white_ta = 14.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 48; +    model.ld_shift_b = 96; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_CANON_8600F; +    model.adc_id = AdcId::CANON_8600F; +    model.gpio_id = GpioId::CANON_8600F; +    model.motor_id = MotorId::CANON_8600F; +    model.flags = GENESYS_FLAG_HAS_UTA | +                  GENESYS_FLAG_HAS_UTA_INFRARED | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_FULL_HWDPI_MODE | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SHADING_REPARK; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 50; +    model.shading_ta_lines = 50; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x04a9, 0x2229, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-100"; +    model.vendor = "Canon"; +    model.model = "LiDE 100"; +    model.model_id = ModelId::CANON_LIDE_100; +    model.asic_type = AsicType::GL847; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, 300, 200, 150, 100, 75 }, +            { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 1.1; +    model.y_offset = 8.3; +    model.x_size = 216.07; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 1.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_100; +    model.adc_id = AdcId::CANON_LIDE_200; +    model.gpio_id = GpioId::CANON_LIDE_200; +    model.motor_id = MotorId::CANON_LIDE_100; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_SIS_SENSOR | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW; +    model.shading_lines = 50; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x1904, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-110"; +    model.vendor = "Canon"; +    model.model = "LiDE 110"; +    model.model_id = ModelId::CANON_LIDE_110; +    model.asic_type = AsicType::GL124; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 }, +            { 4800, 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 2.2; +    model.y_offset = 9.0; +    model.x_size = 216.70; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_110; +    model.adc_id = AdcId::CANON_LIDE_110; +    model.gpio_id = GpioId::CANON_LIDE_110; +    model.motor_id = MotorId::CANON_LIDE_110; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW; +    model.shading_lines = 25; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x1909, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-120"; +    model.vendor = "Canon"; +    model.model = "LiDE 120"; +    model.model_id = ModelId::CANON_LIDE_120; +    model.asic_type = AsicType::GL124; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, 300, 150, 100, 75 }, +            { 4800, 2400, 1200, 600, 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 8.0; +    model.x_size = 216.0; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 1.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_120; +    model.adc_id = AdcId::CANON_LIDE_120; +    model.gpio_id = GpioId::CANON_LIDE_120; +    model.motor_id = MotorId::CANON_LIDE_120; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW; +    model.shading_lines = 50; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x190e, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-210"; +    model.vendor = "Canon"; +    model.model = "LiDE 210"; +    model.model_id = ModelId::CANON_LIDE_210; +    model.asic_type = AsicType::GL124; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            // BUG: 4800 resolution crashes +            { /*4800,*/ 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 }, +            { /*4800,*/ 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 2.2; +    model.y_offset = 8.7; +    model.x_size = 216.70; +    model.y_size = 297.5; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_210; +    model.adc_id = AdcId::CANON_LIDE_110; +    model.gpio_id = GpioId::CANON_LIDE_210; +    model.motor_id = MotorId::CANON_LIDE_210; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW | +                    GENESYS_HAS_EXTRA_SW; +    model.shading_lines = 60; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x190a, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-220"; +    model.vendor = "Canon"; +    model.model = "LiDE 220"; +    model.model_id = ModelId::CANON_LIDE_220; +    model.asic_type = AsicType::GL124; // or a compatible one + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            // BUG: 4800 resolution crashes +            { /*4800,*/ 2400, 1200, 600, 300, 150, 100, 75 }, +            { /*4800,*/ 2400, 1200, 600, 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 2.2; +    model.y_offset = 8.7; +    model.x_size = 216.70; +    model.y_size = 297.5; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_220; +    model.adc_id = AdcId::CANON_LIDE_110; +    model.gpio_id = GpioId::CANON_LIDE_210; +    model.motor_id = MotorId::CANON_LIDE_210; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW | +                    GENESYS_HAS_EXTRA_SW; +    model.shading_lines = 60; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x190f, model); + + +    model = Genesys_Model(); +    model.name = "canon-5600f"; +    model.vendor = "Canon"; +    model.model = "5600F"; +    model.model_id = ModelId::CANON_5600F; +    model.asic_type = AsicType::GL847; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 4800, 2400, 1200, 600, 400, 300, 200, 150, 100, 75 }, +            { 4800, 2400, 1200, 600, 400, 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 1.1; +    model.y_offset = 8.3; +    model.x_size = 216.07; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 3.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_200; +    model.adc_id = AdcId::CANON_LIDE_200; +    model.gpio_id = GpioId::CANON_LIDE_200; +    model.motor_id = MotorId::CANON_LIDE_200; +    model.flags = GENESYS_FLAG_UNTESTED | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_SIS_SENSOR | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW; +    model.shading_lines = 50; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x1906, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-700f"; +    model.vendor = "Canon"; +    model.model = "LiDE 700F"; +    model.model_id = ModelId::CANON_LIDE_700F; +    model.asic_type = AsicType::GL847; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 }, +            { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 3.1; +    model.y_offset = 8.1; +    model.x_size = 216.07; +    model.y_size = 297.0; + +    model.y_offset_calib_white = 1.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_700F; +    model.adc_id = AdcId::CANON_LIDE_700F; +    model.gpio_id = GpioId::CANON_LIDE_700F; +    model.motor_id = MotorId::CANON_LIDE_700; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_SIS_SENSOR | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW; +    model.shading_lines = 70; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x1907, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-200"; +    model.vendor = "Canon"; +    model.model = "LiDE 200"; +    model.model_id = ModelId::CANON_LIDE_200; +    model.asic_type = AsicType::GL847; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 }, +            { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 1.1; +    model.y_offset = 8.3; +    model.x_size = 216.07; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_200; +    model.adc_id = AdcId::CANON_LIDE_200; +    model.gpio_id = GpioId::CANON_LIDE_200; +    model.motor_id = MotorId::CANON_LIDE_200; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_SIS_SENSOR | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_FILE_SW; +    model.shading_lines = 50; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x1905, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-60"; +    model.vendor = "Canon"; +    model.model = "LiDE 60"; +    model.model_id = ModelId::CANON_LIDE_60; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 75 }, +            { 2400, 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.42; +    model.y_offset = 7.9; +    model.x_size = 218.0; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 6.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_35; +    model.adc_id = AdcId::CANON_LIDE_35; +    model.gpio_id = GpioId::CANON_LIDE_35; +    model.motor_id = MotorId::CANON_LIDE_35; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_WHITE_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; + +    model.buttons = GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_FILE_SW | +                    GENESYS_HAS_EMAIL_SW; +    model.shading_lines = 300; +    model.shading_ta_lines = 0; +    model.search_lines = 400; +    // this is completely untested +    s_usb_devices->emplace_back(0x04a9, 0x221c, model); + + +    model = Genesys_Model(); +    model.name = "canon-lide-80"; +    model.vendor = "Canon"; +    model.model = "LiDE 80"; +    model.model_id = ModelId::CANON_LIDE_80; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            {       1200, 600, 300, 150, 100, 75 }, +            { 2400, 1200, 600, 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; +    model.x_offset = 0.42; +    model.y_offset = 7.90; +    model.x_size = 216.07; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 4.5; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CIS_CANON_LIDE_80; +    model.adc_id = AdcId::CANON_LIDE_80; +    model.gpio_id = GpioId::CANON_LIDE_80; +    model.motor_id = MotorId::CANON_LIDE_80; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_WHITE_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | +                    GENESYS_HAS_FILE_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_COPY_SW; +    model.shading_lines = 160; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a9, 0x2214, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-2300c"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet 2300c"; +    model.model_id = ModelId::HP_SCANJET_2300C; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 2.0; +    model.y_offset = 7.5; +    model.x_size = 215.9; +    model.y_size = 295.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 1.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 16; +    model.ld_shift_g = 8; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_HP2300; +    model.adc_id = AdcId::WOLFSON_HP2300; +    model.gpio_id = GpioId::HP2300; +    model.motor_id = MotorId::HP2300; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_SEARCH_START | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_COPY_SW; +    model.shading_lines = 40; +    model.shading_ta_lines = 0; +    model.search_lines = 132; + +    s_usb_devices->emplace_back(0x03f0, 0x0901, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-2400c"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet 2400c"; +    model.model_id = ModelId::HP_SCANJET_2400C; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 100, 50 }, +            { 1200, 600, 300, 150, 100, 50 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 6.5; +    model.y_offset = 2.5; +    model.x_size = 220.0; +    model.y_size = 297.2; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 1.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_HP2400; +    model.adc_id = AdcId::WOLFSON_HP2400; +    model.gpio_id = GpioId::HP2400; +    model.motor_id = MotorId::HP2400; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_COPY_SW | GENESYS_HAS_EMAIL_SW | GENESYS_HAS_SCAN_SW; +    model.shading_lines = 20; +    model.shading_ta_lines = 0; +    model.search_lines = 132; + +    s_usb_devices->emplace_back(0x03f0, 0x0a01, model); + + +    model = Genesys_Model(); +    model.name = "visioneer-strobe-xp200"; +    model.vendor = "Visioneer"; +    model.model = "Strobe XP200"; +    model.model_id = ModelId::VISIONEER_STROBE_XP200; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 200, 100, 75 }, +            { 600, 300, 200, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.5; +    model.y_offset = 16.0; +    model.x_size = 215.9; +    model.y_size = 297.2; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CIS_XP200; +    model.adc_id = AdcId::AD_XP200; +    model.gpio_id = GpioId::XP200; +    model.motor_id = MotorId::XP200; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 120; +    model.shading_ta_lines = 0; +    model.search_lines = 132; + +    s_usb_devices->emplace_back(0x04a7, 0x0426, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-3670"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet 3670"; +    model.model_id = ModelId::HP_SCANJET_3670; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 100, 75, 50 }, +            { 1200, 600, 300, 150, 100, 75, 50 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 8.5; +    model.y_offset = 11.0; +    model.x_size = 215.9; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 1.0; + +    model.x_offset_ta = 104.0; +    model.y_offset_ta = 55.6; +    model.x_size_ta = 25.6; +    model.y_size_ta = 78.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 76.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_HP3670; +    model.adc_id = AdcId::WOLFSON_HP3670; +    model.gpio_id = GpioId::HP3670; +    model.motor_id = MotorId::HP3670; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_XPA | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_COPY_SW | GENESYS_HAS_EMAIL_SW | GENESYS_HAS_SCAN_SW; +    model.shading_lines = 20; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x03f0, 0x1405, model); + + +    model = Genesys_Model(); +    model.name = "plustek-opticpro-st12"; +    model.vendor = "Plustek"; +    model.model = "OpticPro ST12"; +    model.model_id = ModelId::PLUSTEK_OPTICPRO_ST12; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 3.5; +    model.y_offset = 7.5; +    model.x_size = 218.0; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 1.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 8; +    model.ld_shift_b = 16; + +    model.line_mode_color_order = ColorOrder::BGR; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_ST12; +    model.adc_id = AdcId::WOLFSON_ST12; +    model.gpio_id = GpioId::ST12; +    model.motor_id = MotorId::UMAX; +    model.flags = GENESYS_FLAG_UNTESTED | GENESYS_FLAG_14BIT_GAMMA; +    model.buttons = GENESYS_HAS_NO_BUTTONS; +    model.shading_lines = 20; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x07b3, 0x0600, model); + +    model = Genesys_Model(); +    model.name = "plustek-opticpro-st24"; +    model.vendor = "Plustek"; +    model.model = "OpticPro ST24"; +    model.model_id = ModelId::PLUSTEK_OPTICPRO_ST24; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 75 }, +            { 2400, 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 3.5; +    model.y_offset = 7.5; +    model.x_size = 218.0; +    model.y_size = 299.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 1.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 8; +    model.ld_shift_b = 16; + +    model.line_mode_color_order = ColorOrder::BGR; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_ST24; +    model.adc_id = AdcId::WOLFSON_ST24; +    model.gpio_id = GpioId::ST24; +    model.motor_id = MotorId::ST24; +    model.flags = GENESYS_FLAG_UNTESTED | +                  GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SEARCH_START | +                  GENESYS_FLAG_OFFSET_CALIBRATION; +    model.buttons = GENESYS_HAS_NO_BUTTONS; +    model.shading_lines = 20; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x07b3, 0x0601, model); + +    model = Genesys_Model(); +    model.name = "medion-md5345-model"; +    model.vendor = "Medion"; +    model.model = "MD5345/MD6228/MD6471"; +    model.model_id = ModelId::MEDION_MD5345; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 400, 300, 200, 150, 100, 75, 50 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100, 75, 50 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.30; +    model.y_offset = 0.80; +    model.x_size = 220.0; +    model.y_size = 296.4; + +    model.y_offset_calib_white = 0.00; +    model.x_offset_calib_black = 0.00; + +    model.x_offset_ta = 0.00; +    model.y_offset_ta = 0.00; +    model.x_size_ta = 0.00; +    model.y_size_ta = 0.00; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.00; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 48; +    model.ld_shift_g = 24; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_5345; +    model.adc_id = AdcId::WOLFSON_5345; +    model.gpio_id = GpioId::MD_5345; +    model.motor_id = MotorId::MD_5345; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_SEARCH_START | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_SHADING_NO_MOVE | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_POWER_SW | +                    GENESYS_HAS_OCR_SW | +                    GENESYS_HAS_SCAN_SW; +    model.shading_lines = 40; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x0461, 0x0377, model); + +    model = Genesys_Model(); +    model.name = "visioneer-strobe-xp300"; +    model.vendor = "Visioneer"; +    model.model = "Strobe XP300"; +    model.model_id = ModelId::VISIONEER_STROBE_XP300; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 1.0; +    model.x_size = 435.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 26.5; +    // this is larger than needed -- accounts for second sensor head, which is a calibration item +    model.eject_feed = 0.0; +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_XP300; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::XP300; +    model.motor_id = MotorId::XP300; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a7, 0x0474, model); + +    model = Genesys_Model(); +    model.name = "syscan-docketport-665"; +    model.vendor = "Syscan/Ambir"; +    model.model = "DocketPORT 665"; +    model.model_id = ModelId::SYSCAN_DOCKETPORT_665; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 108.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 17.5; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_DP665; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::DP665; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x0a82, 0x4803, model); + +    model = Genesys_Model(); +    model.name = "visioneer-roadwarrior"; +    model.vendor = "Visioneer"; +    model.model = "Readwarrior"; +    model.model_id = ModelId::VISIONEER_ROADWARRIOR; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 220.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 16.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_ROADWARRIOR; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::ROADWARRIOR; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a7, 0x0494, model); + +    model = Genesys_Model(); +    model.name = "syscan-docketport-465"; +    model.vendor = "Syscan"; +    model.model = "DocketPORT 465"; +    model.model_id = ModelId::SYSCAN_DOCKETPORT_465; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 220.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 16.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_ROADWARRIOR; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::ROADWARRIOR; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_NO_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_UNTESTED; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW; +    model.shading_lines = 300; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x0a82, 0x4802, model); + + +    model = Genesys_Model(); +    model.name = "visioneer-xp100-revision3"; +    model.vendor = "Visioneer"; +    model.model = "XP100 Revision 3"; +    model.model_id = ModelId::VISIONEER_STROBE_XP100_REVISION3; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 220.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 16.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_ROADWARRIOR; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::ROADWARRIOR; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a7, 0x049b, model); + +    model = Genesys_Model(); +    model.name = "pentax-dsmobile-600"; +    model.vendor = "Pentax"; +    model.model = "DSmobile 600"; +    model.model_id = ModelId::PENTAX_DSMOBILE_600; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 220.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 16.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_DSMOBILE600; +    model.adc_id = AdcId::WOLFSON_DSM600; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::DSMOBILE_600; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x0a17, 0x3210, model); +    // clone, only usb id is different +    s_usb_devices->emplace_back(0x04f9, 0x2038, model); + +    model = Genesys_Model(); +    model.name = "syscan-docketport-467"; +    model.vendor = "Syscan"; +    model.model = "DocketPORT 467"; +    model.model_id = ModelId::SYSCAN_DOCKETPORT_467; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 220.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 16.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_DSMOBILE600; +    model.adc_id = AdcId::WOLFSON_DSM600; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::DSMOBILE_600; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x1dcc, 0x4812, model); + +    model = Genesys_Model(); +    model.name = "syscan-docketport-685"; +    model.vendor = "Syscan/Ambir"; +    model.model = "DocketPORT 685"; +    model.model_id = ModelId::SYSCAN_DOCKETPORT_685; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 1.0; +    model.x_size = 212.0; +    model.y_size = 500; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 26.5; +    // this is larger than needed -- accounts for second sensor head, which is a calibration item +    model.eject_feed = 0.0; +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_DP685; +    model.adc_id = AdcId::WOLFSON_DSM600; +    model.gpio_id = GpioId::DP685; +    model.motor_id = MotorId::XP300; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + + +    s_usb_devices->emplace_back(0x0a82, 0x480c, model); + + +    model = Genesys_Model(); +    model.name = "syscan-docketport-485"; +    model.vendor = "Syscan/Ambir"; +    model.model = "DocketPORT 485"; +    model.model_id = ModelId::SYSCAN_DOCKETPORT_485; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 1.0; +    model.x_size = 435.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 26.5; +    // this is larger than needed -- accounts for second sensor head, which is a calibration item +    model.eject_feed = 0.0; +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_XP300; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::XP300; +    model.motor_id = MotorId::XP300; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x0a82, 0x4800, model); + + +    model = Genesys_Model(); +    model.name = "dct-docketport-487"; +    model.vendor = "DCT"; +    model.model = "DocketPORT 487"; +    model.model_id = ModelId::DCT_DOCKETPORT_487; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.0; +    model.y_offset = 1.0; +    model.x_size = 435.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 26.5; +    // this is larger than needed -- accounts for second sensor head, which is a calibration item +    model.eject_feed = 0.0; +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_XP300; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::XP300; +    model.motor_id = MotorId::XP300; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_UNTESTED; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x1dcc, 0x4810, model); + + +    model = Genesys_Model(); +    model.name = "visioneer-7100-model"; +    model.vendor = "Visioneer"; +    model.model = "OneTouch 7100"; +    model.model_id = ModelId::VISIONEER_7100; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 400, 300, 200, 150, 100, 75, 50 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100, 75, 50 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 4.00; +    model.y_offset = 0.80; +    model.x_size = 215.9; +    model.y_size = 296.4; + +    model.y_offset_calib_white = 0.00; +    model.x_offset_calib_black = 0.00; + +    model.x_offset_ta = 0.00; +    model.y_offset_ta = 0.00; +    model.x_size_ta = 0.00; +    model.y_size_ta = 0.00; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.00; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 48; +    model.ld_shift_g = 24; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_5345; +    model.adc_id = AdcId::WOLFSON_5345; +    model.gpio_id = GpioId::MD_5345; +    model.motor_id = MotorId::MD_5345; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_SEARCH_START | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_POWER_SW | +                    GENESYS_HAS_OCR_SW | +                    GENESYS_HAS_SCAN_SW; +    model.shading_lines = 40; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x04a7, 0x0229, model); + + +    model = Genesys_Model(); +    model.name = "xerox-2400-model"; +    model.vendor = "Xerox"; +    model.model = "OneTouch 2400"; +    model.model_id = ModelId::XEROX_2400; +    model.asic_type = AsicType::GL646; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 400, 300, 200, 150, 100, 75, 50 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100, 75, 50 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 4.00; +    model.y_offset = 0.80; +    model.x_size = 215.9; +    model.y_size = 296.4; + +    model.y_offset_calib_white = 0.00; +    model.x_offset_calib_black = 0.00; + +    model.x_offset_ta = 0.00; +    model.y_offset_ta = 0.00; +    model.x_size_ta = 0.00; +    model.y_size_ta = 0.00; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.00; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 48; +    model.ld_shift_g = 24; +    model.ld_shift_b = 0; +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_5345; +    model.adc_id = AdcId::WOLFSON_5345; +    model.gpio_id = GpioId::MD_5345; +    model.motor_id = MotorId::MD_5345; +    model.flags = GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_SEARCH_START | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_COPY_SW | +                    GENESYS_HAS_EMAIL_SW | +                    GENESYS_HAS_POWER_SW | +                    GENESYS_HAS_OCR_SW | +                    GENESYS_HAS_SCAN_SW; +    model.shading_lines = 40; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x0461, 0x038b, model); + + +    model = Genesys_Model(); +    model.name = "xerox-travelscanner"; +    model.vendor = "Xerox"; +    model.model = "Travelscanner 100"; +    model.model_id = ModelId::XEROX_TRAVELSCANNER_100; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 600, 300, 150, 75 }, +            { 1200, 600, 300, 150, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 4.0; +    model.y_offset = 0.0; +    model.x_size = 220.0; +    model.y_size = 511; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 16.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = true; +    model.is_sheetfed = true; +    model.sensor_id = SensorId::CCD_ROADWARRIOR; +    model.adc_id = AdcId::WOLFSON_XP300; +    model.gpio_id = GpioId::DP665; +    model.motor_id = MotorId::ROADWARRIOR; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION; +    model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 400; + +    s_usb_devices->emplace_back(0x04a7, 0x04ac, model); + + +    model = Genesys_Model(); +    model.name = "plustek-opticbook-3600"; +    model.vendor = "PLUSTEK"; +    model.model = "OpticBook 3600"; +    model.model_id = ModelId::PLUSTEK_OPTICPRO_3600; +    model.asic_type = AsicType::GL841; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { /*1200,*/ 600, 400, 300, 200, 150, 100, 75 }, +            { /*2400,*/ 1200, 600, 400, 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 0.42; +    model.y_offset = 6.75; +    model.x_size = 216.0; +    model.y_size = 297.0; + +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 0.0; +    model.y_size_ta = 0.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_PLUSTEK_OPTICPRO_3600; +    model.adc_id = AdcId::PLUSTEK_OPTICPRO_3600; +    model.gpio_id = GpioId::PLUSTEK_OPTICPRO_3600; +    model.motor_id = MotorId::PLUSTEK_OPTICPRO_3600; +    model.flags = GENESYS_FLAG_UNTESTED |                // not fully working yet +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION; +    model.buttons = GENESYS_HAS_NO_BUTTONS; +    model.shading_lines = 7; +    model.shading_ta_lines = 0; +    model.search_lines = 200; + +    s_usb_devices->emplace_back(0x07b3, 0x0900, model); + + +    model = Genesys_Model(); +    model.name = "plustek-opticfilm-7200i"; +    model.vendor = "PLUSTEK"; +    model.model = "OpticFilm 7200i"; +    model.model_id = ModelId::PLUSTEK_OPTICFILM_7200I; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::TRANSPARENCY, ScanMethod::TRANSPARENCY_INFRARED }, +            { 7200, 3600, 1800, 900 }, +            { 7200, 3600, 1800, 900 }, +        } +    }; + +    model.bpp_gray_values = { 16 }; +    model.bpp_color_values = { 16 }; +    model.default_method = ScanMethod::TRANSPARENCY; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 36.0; +    model.y_size = 44.0; +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 6.5; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 29.0; +    model.x_size_ta = 36.0; +    model.y_size_ta = 24.0; +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_black_ta = 6.5; +    model.y_offset_calib_white_ta = 0.0; +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 12; +    model.ld_shift_b = 24; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; + +    model.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7200I; +    model.adc_id = AdcId::PLUSTEK_OPTICFILM_7200I; +    model.gpio_id = GpioId::PLUSTEK_OPTICFILM_7200I; +    model.motor_id = MotorId::PLUSTEK_OPTICFILM_7200I; + +    model.flags = GENESYS_FLAG_HAS_UTA | +                  GENESYS_FLAG_HAS_UTA_INFRARED | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_HAS_NO_BUTTONS | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CALIBRATION_HOST_SIDE | +                  GENESYS_FLAG_16BIT_DATA_INVERTED; + +    model.shading_lines = 7; +    model.shading_ta_lines = 50; +    model.search_lines = 200; +    s_usb_devices->emplace_back(0x07b3, 0x0c04, model); + + +    model = Genesys_Model(); +    model.name = "plustek-opticfilm-7300"; +    model.vendor = "PLUSTEK"; +    model.model = "OpticFilm 7300"; +    model.model_id = ModelId::PLUSTEK_OPTICFILM_7300; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::TRANSPARENCY }, +            { 7200, 3600, 1800, 900 }, +            { 7200, 3600, 1800, 900 }, +        } +    }; + +    model.bpp_gray_values = { 16 }; +    model.bpp_color_values = { 16 }; +    model.default_method = ScanMethod::TRANSPARENCY; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 36.0; +    model.y_size = 44.0; +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 6.5; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 29.0; +    model.x_size_ta = 36.0; +    model.y_size_ta = 24.0; +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_black_ta = 6.5; +    model.y_offset_calib_white_ta = 0.0; +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 12; +    model.ld_shift_b = 24; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; + +    model.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7300; +    model.adc_id = AdcId::PLUSTEK_OPTICFILM_7300; +    model.gpio_id = GpioId::PLUSTEK_OPTICFILM_7300; +    model.motor_id = MotorId::PLUSTEK_OPTICFILM_7300; + +    model.flags = GENESYS_FLAG_HAS_UTA | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_HAS_NO_BUTTONS | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CALIBRATION_HOST_SIDE; + +    model.shading_lines = 7; +    model.shading_ta_lines = 50; +    model.search_lines = 200; +    s_usb_devices->emplace_back(0x07b3, 0x0c12, model); + + +    model = Genesys_Model(); +    model.name = "plustek-opticfilm-7500i"; +    model.vendor = "PLUSTEK"; +    model.model = "OpticFilm 7500i"; +    model.model_id = ModelId::PLUSTEK_OPTICFILM_7500I; +    model.asic_type = AsicType::GL843; + +    model.resolutions = { +        { +            { ScanMethod::TRANSPARENCY, ScanMethod::TRANSPARENCY_INFRARED }, +            { 7200, 3600, 1800, 900 }, +            { 7200, 3600, 1800, 900 }, +        } +    }; + +    model.bpp_gray_values = { 16 }; +    model.bpp_color_values = { 16 }; +    model.default_method = ScanMethod::TRANSPARENCY; + +    model.x_offset = 0.0; +    model.y_offset = 0.0; +    model.x_size = 36.0; +    model.y_size = 44.0; +    model.y_offset_calib_white = 0.0; +    model.x_offset_calib_black = 6.5; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 29.0; +    model.x_size_ta = 36.0; +    model.y_size_ta = 24.0; +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_black_ta = 6.5; +    model.y_offset_calib_white_ta = 0.0; +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 12; +    model.ld_shift_b = 24; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; + +    model.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7500I; +    model.adc_id = AdcId::PLUSTEK_OPTICFILM_7500I; +    model.gpio_id = GpioId::PLUSTEK_OPTICFILM_7500I; +    model.motor_id = MotorId::PLUSTEK_OPTICFILM_7500I; + +    model.flags = GENESYS_FLAG_HAS_UTA | +                  GENESYS_FLAG_HAS_UTA_INFRARED | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_HAS_NO_BUTTONS | +                  GENESYS_FLAG_SHADING_REPARK | +                  GENESYS_FLAG_CALIBRATION_HOST_SIDE; + +    model.shading_lines = 7; +    model.shading_ta_lines = 50; +    model.search_lines = 200; +    s_usb_devices->emplace_back(0x07b3, 0x0c13, model); + + +    model = Genesys_Model(); +    model.name = "hewlett-packard-scanjet-N6310"; +    model.vendor = "Hewlett Packard"; +    model.model = "ScanJet N6310"; +    model.model_id = ModelId::HP_SCANJET_N6310; +    model.asic_type = AsicType::GL847; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100, 75 }, +            { 2400, 1200, 600, 400, 300, 200, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 6; +    model.y_offset = 2; +    model.x_size = 216; +    model.y_size = 511; + +    model.y_offset_calib_white = 3.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 100.0; +    model.y_size_ta = 100.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0; + +    model.post_scan = 0; +    model.eject_feed = 0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 0; +    model.ld_shift_b = 0; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_HP_N6310; +    model.adc_id = AdcId::CANON_LIDE_200;        // Not defined yet for N6310 +    model.gpio_id = GpioId::HP_N6310; +    model.motor_id = MotorId::CANON_LIDE_200;    // Not defined yet for N6310 +    model.flags = GENESYS_FLAG_UNTESTED | +                  GENESYS_FLAG_14BIT_GAMMA | +                  GENESYS_FLAG_DARK_CALIBRATION | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_NO_CALIBRATION; + +    model.buttons = GENESYS_HAS_NO_BUTTONS; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x03f0, 0x4705, model); + + +    model = Genesys_Model(); +    model.name = "plustek-opticbook-3800"; +    model.vendor = "PLUSTEK"; +    model.model = "OpticBook 3800"; +    model.model_id = ModelId::PLUSTEK_OPTICBOOK_3800; +    model.asic_type = AsicType::GL845; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 100, 75 }, +            { 1200, 600, 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 7.2; +    model.y_offset = 14.7; +    model.x_size = 217.7; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 9.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 0.0; +    model.y_size_ta = 0.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_PLUSTEK_OPTICBOOK_3800; +    model.adc_id = AdcId::PLUSTEK_OPTICBOOK_3800; +    model.gpio_id = GpioId::PLUSTEK_OPTICBOOK_3800; +    model.motor_id = MotorId::PLUSTEK_OPTICBOOK_3800; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA; +    model.buttons = GENESYS_HAS_NO_BUTTONS;  // TODO there are 4 buttons to support +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x07b3, 0x1300, model); + + +    model = Genesys_Model(); +    model.name = "canon-image-formula-101"; +    model.vendor = "Canon"; +    model.model = "Image Formula 101"; +    model.model_id = ModelId::CANON_IMAGE_FORMULA_101; +    model.asic_type = AsicType::GL846; + +    model.resolutions = { +        { +            { ScanMethod::FLATBED }, +            { 1200, 600, 300, 150, 100, 75 }, +            { 1200, 600, 300, 150, 100, 75 }, +        } +    }; + +    model.bpp_gray_values = { 8, 16 }; +    model.bpp_color_values = { 8, 16 }; + +    model.x_offset = 7.2; +    model.y_offset = 14.7; +    model.x_size = 217.7; +    model.y_size = 300.0; + +    model.y_offset_calib_white = 9.0; +    model.x_offset_calib_black = 0.0; + +    model.x_offset_ta = 0.0; +    model.y_offset_ta = 0.0; +    model.x_size_ta = 0.0; +    model.y_size_ta = 0.0; + +    model.y_offset_sensor_to_ta = 0.0; +    model.y_offset_calib_white_ta = 0.0; + +    model.post_scan = 0.0; +    model.eject_feed = 0.0; + +    model.ld_shift_r = 0; +    model.ld_shift_g = 24; +    model.ld_shift_b = 48; + +    model.line_mode_color_order = ColorOrder::RGB; + +    model.is_cis = false; +    model.is_sheetfed = false; +    model.sensor_id = SensorId::CCD_IMG101; +    model.adc_id = AdcId::IMG101; +    model.gpio_id = GpioId::IMG101; +    model.motor_id = MotorId::IMG101; +    model.flags = GENESYS_FLAG_SKIP_WARMUP | +                  GENESYS_FLAG_OFFSET_CALIBRATION | +                  GENESYS_FLAG_CUSTOM_GAMMA | +                  GENESYS_FLAG_UNTESTED; +    model.buttons = GENESYS_HAS_NO_BUTTONS ; +    model.shading_lines = 100; +    model.shading_ta_lines = 0; +    model.search_lines = 100; + +    s_usb_devices->emplace_back(0x1083, 0x162e, model); + } + +} // namespace genesys diff --git a/backend/genesys/tables_motor.cpp b/backend/genesys/tables_motor.cpp new file mode 100644 index 0000000..2484d2d --- /dev/null +++ b/backend/genesys/tables_motor.cpp @@ -0,0 +1,325 @@ +/*  sane - Scanner Access Now Easy. + +    Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +    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 "low.h" + +namespace genesys { + +StaticInit<std::vector<Genesys_Motor>> s_motors; + +void genesys_init_motor_tables() +{ +    s_motors.init(); + +    Genesys_Motor motor; +    motor.id = MotorId::UMAX; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 2400; +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128)); +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::MD_5345; // MD5345/6228/6471 +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 2400; +    motor.slopes.push_back(MotorSlope::create_from_steps(2000, 1375, 128)); +    motor.slopes.push_back(MotorSlope::create_from_steps(2000, 1375, 128)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::ST24; +    motor.base_ydpi = 2400; +    motor.optical_ydpi = 2400; +    motor.slopes.push_back(MotorSlope::create_from_steps(2289, 2100, 128)); +    motor.slopes.push_back(MotorSlope::create_from_steps(2289, 2100, 128)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::HP3670; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 1200; +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128)); +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::HP2400; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 1200; +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128)); +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::HP2300; +    motor.base_ydpi = 600; +    motor.optical_ydpi = 1200; +    motor.slopes.push_back(MotorSlope::create_from_steps(3200, 1200, 128)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3200, 1200, 128)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_35; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 2400; +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1400, 60)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::XP200; +    motor.base_ydpi = 600; +    motor.optical_ydpi = 600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::XP300; +    motor.base_ydpi = 300; +    motor.optical_ydpi = 600; +    // works best with GPIO10, GPIO14 off +    motor.slopes.push_back(MotorSlope::create_from_steps(3700, 3700, 2)); +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 11000, 2)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::DP665; +    motor.base_ydpi = 750; +    motor.optical_ydpi = 1500; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 2500, 10)); +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 11000, 2)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::ROADWARRIOR; +    motor.base_ydpi = 750; +    motor.optical_ydpi = 1500; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 2600, 10)); +    motor.slopes.push_back(MotorSlope::create_from_steps(11000, 11000, 2)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::DSMOBILE_600; +    motor.base_ydpi = 750; +    motor.optical_ydpi = 1500; +    motor.slopes.push_back(MotorSlope::create_from_steps(6666, 3700, 8)); +    motor.slopes.push_back(MotorSlope::create_from_steps(6666, 3700, 8)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_100; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 6400; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 127)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1500, 127)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3 * 2712, 3 * 2712, 16)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_200; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 6400; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 127)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1500, 127)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3 * 2712, 3 * 2712, 16)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_700; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 6400; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 127)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1500, 127)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3 * 2712, 3 * 2712, 16)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::KVSS080; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 1200; +    motor.slopes.push_back(MotorSlope::create_from_steps(22222, 500, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(22222, 500, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(22222, 500, 246)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::G4050; +    motor.base_ydpi = 2400; +    motor.optical_ydpi = 9600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_4400F; +    motor.base_ydpi = 2400; +    motor.optical_ydpi = 9600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_8400F; +    motor.base_ydpi = 1600; +    motor.optical_ydpi = 6400; +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_8600F; +    motor.base_ydpi = 2400; +    motor.optical_ydpi = 9600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_110; +    motor.base_ydpi = 4800; +    motor.optical_ydpi = 9600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 256)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_120; +    motor.base_ydpi = 4800; +    motor.optical_ydpi = 9600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 256)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_210; +    motor.base_ydpi = 4800; +    motor.optical_ydpi = 9600; +    motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 256)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::PLUSTEK_OPTICPRO_3600; +    motor.base_ydpi = 1200; +    motor.optical_ydpi = 2400; +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 3250, 60)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::PLUSTEK_OPTICFILM_7200I; +    motor.base_ydpi = 3600; +    motor.optical_ydpi = 3600; +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::PLUSTEK_OPTICFILM_7300; +    motor.base_ydpi = 3600; +    motor.optical_ydpi = 3600; +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::PLUSTEK_OPTICFILM_7500I; +    motor.base_ydpi = 3600; +    motor.optical_ydpi = 3600; +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::IMG101; +    motor.base_ydpi = 600; +    motor.optical_ydpi = 1200; +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 3250, 60)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::PLUSTEK_OPTICBOOK_3800; +    motor.base_ydpi = 600; +    motor.optical_ydpi = 1200; +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60)); +    motor.slopes.push_back(MotorSlope::create_from_steps(3500, 3250, 60)); +    s_motors->push_back(std::move(motor)); + + +    motor = Genesys_Motor(); +    motor.id = MotorId::CANON_LIDE_80; +    motor.base_ydpi = 2400; +    motor.optical_ydpi = 4800; // 9600 +    motor.slopes.push_back(MotorSlope::create_from_steps(9560, 1912, 31)); +    s_motors->push_back(std::move(motor)); +} + +} // namespace genesys diff --git a/backend/genesys/tables_motor_profile.cpp b/backend/genesys/tables_motor_profile.cpp new file mode 100644 index 0000000..18f7271 --- /dev/null +++ b/backend/genesys/tables_motor_profile.cpp @@ -0,0 +1,380 @@ +/*  sane - Scanner Access Now Easy. + +    Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +    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 "low.h" + +namespace genesys { + +StaticInit<std::vector<Motor_Profile>> gl843_motor_profiles; + +void genesys_init_motor_profile_tables_gl843() +{ +    gl843_motor_profiles.init(); + +    auto profile = Motor_Profile(); +    profile.motor_id = MotorId::KVSS080; +    profile.exposure = 8000; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(44444, 500, 489); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::G4050; +    profile.exposure = 8016; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(7842, 320, 602); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::G4050; +    profile.exposure = 15624; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(9422, 254, 1004); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::G4050; +    profile.exposure = 42752; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(42752, 1706, 610); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::G4050; +    profile.exposure = 56064; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(28032, 2238, 604); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_4400F; +    profile.exposure = 11640; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(49152, 484, 1014); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_8400F; +    profile.exposure = 50000; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(8743, 300, 794); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_8600F; +    profile.exposure = 0x59d8; +    profile.step_type = StepType::QUARTER; +    // FIXME: if the exposure is lower then we'll select another motor +    profile.slope = MotorSlope::create_from_steps(54612, 1500, 219); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::PLUSTEK_OPTICFILM_7200I; +    profile.exposure = 0; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(39682, 1191, 15); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::PLUSTEK_OPTICFILM_7300; +    profile.exposure = 0x2f44; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(31250, 1512, 6); +    gl843_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::PLUSTEK_OPTICFILM_7500I; +    profile.exposure = 0; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(31250, 1375, 7); +    gl843_motor_profiles->push_back(profile); +} + +StaticInit<std::vector<Motor_Profile>> gl846_motor_profiles; + +void genesys_init_motor_profile_tables_gl846() +{ +    gl846_motor_profiles.init(); + +    auto profile = Motor_Profile(); +    profile.motor_id = MotorId::IMG101; +    profile.exposure = 11000; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(22000, 1000, 1017); + +    gl846_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::PLUSTEK_OPTICBOOK_3800; +    profile.exposure = 11000; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(22000, 1000, 1017); +    gl846_motor_profiles->push_back(profile); +} + +/** + * database of motor profiles + */ + +StaticInit<std::vector<Motor_Profile>> gl847_motor_profiles; + +void genesys_init_motor_profile_tables_gl847() +{ +    gl847_motor_profiles.init(); + +    auto profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_100; +    profile.exposure = 2848; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_100; +    profile.exposure = 1424; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_100; +    profile.exposure = 1432; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_100; +    profile.exposure = 2712; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 279); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_100; +    profile.exposure = 5280; +    profile.step_type = StepType::EIGHTH; +    profile.slope = MotorSlope::create_from_steps(31680, 534, 247); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_200; +    profile.exposure = 2848; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_200; +    profile.exposure = 1424; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_200; +    profile.exposure = 1432; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_200; +    profile.exposure = 2712; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 279); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_200; +    profile.exposure = 5280; +    profile.step_type = StepType::EIGHTH; +    profile.slope = MotorSlope::create_from_steps(31680, 534, 247); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_200; +    profile.exposure = 10416; +    profile.step_type = StepType::EIGHTH; +    profile.slope = MotorSlope::create_from_steps(31680, 534, 247); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_700; +    profile.exposure = 2848; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_700; +    profile.exposure = 1424; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_700; +    profile.exposure = 1504; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 534, 255); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_700; +    profile.exposure = 2696; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(46876, 2022, 127); +    gl847_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_700; +    profile.exposure = 10576; +    profile.step_type = StepType::EIGHTH; +    profile.slope = MotorSlope::create_from_steps(46876, 15864, 2); +    gl847_motor_profiles->push_back(profile); +} + +StaticInit<std::vector<Motor_Profile>> gl124_motor_profiles; + +void genesys_init_motor_profile_tables_gl124() +{ +    gl124_motor_profiles.init(); + +    // NEXT LPERIOD=PREVIOUS*2-192 +    Motor_Profile profile; +    profile.motor_id = MotorId::CANON_LIDE_110; +    profile.exposure = 2768; +    profile.step_type = StepType::FULL; +    profile.slope = MotorSlope::create_from_steps(62496, 335, 255); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_110; +    profile.exposure = 5360; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(62496, 335, 469); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_110; +    profile.exposure = 10528; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(62496, 2632, 3); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_110; +    profile.exposure = 20864; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(62496, 10432, 3); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_120; +    profile.exposure = 4608; +    profile.step_type = StepType::FULL; +    profile.slope = MotorSlope::create_from_steps(62496, 864, 127); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_120; +    profile.exposure = 5360; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(62496, 2010, 63); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_120; +    profile.exposure = 10528; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(62464, 2632, 3); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_120; +    profile.exposure = 20864; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(62592, 10432, 5); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_210; +    profile.exposure = 2768; +    profile.step_type = StepType::FULL; +    profile.slope = MotorSlope::create_from_steps(62496, 335, 255); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_210; +    profile.exposure = 5360; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(62496, 335, 469); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_210; +    profile.exposure = 10528; +    profile.step_type = StepType::HALF; +    profile.slope = MotorSlope::create_from_steps(62496, 2632, 3); +    gl124_motor_profiles->push_back(profile); + +    profile = Motor_Profile(); +    profile.motor_id = MotorId::CANON_LIDE_210; +    profile.exposure = 20864; +    profile.step_type = StepType::QUARTER; +    profile.slope = MotorSlope::create_from_steps(62496, 10432, 4); +    gl124_motor_profiles->push_back(profile); +} + +void genesys_init_motor_profile_tables() +{ +    genesys_init_motor_profile_tables_gl843(); +    genesys_init_motor_profile_tables_gl846(); +    genesys_init_motor_profile_tables_gl847(); +    genesys_init_motor_profile_tables_gl124(); +} + +} // namespace genesys diff --git a/backend/genesys/tables_sensor.cpp b/backend/genesys/tables_sensor.cpp new file mode 100644 index 0000000..bbbe441 --- /dev/null +++ b/backend/genesys/tables_sensor.cpp @@ -0,0 +1,3668 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "low.h" + +namespace genesys { + +inline unsigned default_get_logical_hwdpi(const Genesys_Sensor& sensor, unsigned xres) +{ +    if (sensor.logical_dpihw_override) +        return sensor.logical_dpihw_override; + +    // can't be below 600 dpi +    if (xres <= 600) { +        return 600; +    } +    if (xres <= static_cast<unsigned>(sensor.optical_res) / 4) { +        return sensor.optical_res / 4; +    } +    if (xres <= static_cast<unsigned>(sensor.optical_res) / 2) { +        return sensor.optical_res / 2; +    } +    return sensor.optical_res; +} + +inline unsigned get_sensor_optical_with_ccd_divisor(const Genesys_Sensor& sensor, unsigned xres) +{ +    unsigned hwres = sensor.optical_res / sensor.get_ccd_size_divisor_for_dpi(xres); + +    if (xres <= hwres / 4) { +        return hwres / 4; +    } +    if (xres <= hwres / 2) { +        return hwres / 2; +    } +    return hwres; +} + +inline unsigned default_get_ccd_size_divisor_for_dpi(const Genesys_Sensor& sensor, unsigned xres) +{ +    if (sensor.ccd_size_divisor >= 4 && xres * 4 <= static_cast<unsigned>(sensor.optical_res)) { +        return 4; +    } +    if (sensor.ccd_size_divisor >= 2 && xres * 2 <= static_cast<unsigned>(sensor.optical_res)) { +        return 2; +    } +    return 1; +} + +inline unsigned get_ccd_size_divisor_exact(const Genesys_Sensor& sensor, unsigned xres) +{ +    (void) xres; +    return sensor.ccd_size_divisor; +} + +inline unsigned get_ccd_size_divisor_gl124(const Genesys_Sensor& sensor, unsigned xres) +{ +    // we have 2 domains for ccd: xres below or above half ccd max dpi +    if (xres <= 300 && sensor.ccd_size_divisor > 1) { +        return 2; +    } +    return 1; +} + +inline unsigned default_get_hwdpi_divisor_for_dpi(const Genesys_Sensor& sensor, unsigned xres) +{ +    return sensor.optical_res / default_get_logical_hwdpi(sensor, xres); +} + +StaticInit<std::vector<Genesys_Sensor>> s_sensors; + +void genesys_init_sensor_tables() +{ +    s_sensors.init(); + +    Genesys_Sensor sensor; + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_UMAX; +    sensor.optical_res = 1200; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 64; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10800; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_regs = { +        { 0x08, 0x01 }, +        { 0x09, 0x03 }, +        { 0x0a, 0x05 }, +        { 0x0b, 0x07 }, +        { 0x16, 0x33 }, +        { 0x17, 0x05 }, +        { 0x18, 0x31 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x13 }, +        { 0x53, 0x17 }, +        { 0x54, 0x03 }, +        { 0x55, 0x07 }, +        { 0x56, 0x0b }, +        { 0x57, 0x0f }, +        { 0x58, 0x23 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_ST12; +    sensor.optical_res = 600; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 85; +    sensor.ccd_start_xoffset = 152; +    sensor.sensor_pixels = 5416; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_regs = { +        { 0x08, 0x02 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x06 }, +        { 0x0b, 0x04 }, +        { 0x16, 0x2b }, +        { 0x17, 0x08 }, +        { 0x18, 0x20 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x0c }, +        { 0x1d, 0x03 }, +        { 0x52, 0x0f }, +        { 0x53, 0x13 }, +        { 0x54, 0x17 }, +        { 0x55, 0x03 }, +        { 0x56, 0x07 }, +        { 0x57, 0x0b }, +        { 0x58, 0x83 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_ST24; +    sensor.optical_res = 1200; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 64; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10800; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_regs = { +        { 0x08, 0x0e }, +        { 0x09, 0x0c }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x0c }, +        { 0x16, 0x33 }, +        { 0x17, 0x08 }, +        { 0x18, 0x31 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x17 }, +        { 0x53, 0x03 }, +        { 0x54, 0x07 }, +        { 0x55, 0x0b }, +        { 0x56, 0x0f }, +        { 0x57, 0x13 }, +        { 0x58, 0x03 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_5345; +    sensor.optical_res = 1200; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 16; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10872; +    sensor.fau_gain_white_ref = 190; +    sensor.gain_white_ref = 190; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.stagger_config = StaggerConfig{ 1200, 4 }; // FIXME: may be incorrect +    sensor.custom_base_regs = { +        { 0x08, 0x0d }, +        { 0x09, 0x0f }, +        { 0x0a, 0x11 }, +        { 0x0b, 0x13 }, +        { 0x16, 0x0b }, +        { 0x17, 0x0a }, +        { 0x18, 0x30 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x03 }, +        { 0x52, 0x0f }, +        { 0x53, 0x13 }, +        { 0x54, 0x17 }, +        { 0x55, 0x03 }, +        { 0x56, 0x07 }, +        { 0x57, 0x0b }, +        { 0x58, 0x23 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 2.38f, 2.35f, 2.34f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            unsigned exposure_lperiod; +            unsigned ccd_size_divisor; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 50 }, 12000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 75 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 100 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 150 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 200 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 300 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 400 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 600 }, 11000, 2, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x28 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 1200 }, 11000, 1, { +                    { 0x08, 0x0d }, +                    { 0x09, 0x0f }, +                    { 0x0a, 0x11 }, +                    { 0x0b, 0x13 }, +                    { 0x16, 0x0b }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x30 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x03 }, +                    { 0x52, 0x03 }, +                    { 0x53, 0x07 }, +                    { 0x54, 0x0b }, +                    { 0x55, 0x0f }, +                    { 0x56, 0x13 }, +                    { 0x57, 0x17 }, +                    { 0x58, 0x23 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.ccd_size_divisor = setting.ccd_size_divisor; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_HP2400; +    sensor.optical_res = 1200; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 15; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10872; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.stagger_config = StaggerConfig{1200, 4}; // FIXME: may be incorrect +    sensor.custom_base_regs = { +        { 0x08, 0x14 }, +        { 0x09, 0x15 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0xbf }, +        { 0x17, 0x08 }, +        { 0x18, 0x3f }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x0b }, +        { 0x53, 0x0f }, +        { 0x54, 0x13 }, +        { 0x55, 0x17 }, +        { 0x56, 0x03 }, +        { 0x57, 0x07 }, +        { 0x58, 0x63 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x0e }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 2.1f, 2.1f, 2.1f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            unsigned exposure_lperiod; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 50 }, 7211, { +                    { 0x08, 0x14 }, +                    { 0x09, 0x15 }, +                    { 0x0a, 0x00 }, +                    { 0x0b, 0x00 }, +                    { 0x16, 0xbf }, +                    { 0x17, 0x08 }, +                    { 0x18, 0x3f }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x02 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0f }, +                    { 0x54, 0x13 }, +                    { 0x55, 0x17 }, +                    { 0x56, 0x03 }, +                    { 0x57, 0x07 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 100 }, 7211, { +                    { 0x08, 0x14 }, +                    { 0x09, 0x15 }, +                    { 0x0a, 0x00 }, +                    { 0x0b, 0x00 }, +                    { 0x16, 0xbf }, +                    { 0x17, 0x08 }, +                    { 0x18, 0x3f }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x02 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0f }, +                    { 0x54, 0x13 }, +                    { 0x55, 0x17 }, +                    { 0x56, 0x03 }, +                    { 0x57, 0x07 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 150 }, 7211, { +                    { 0x08, 0x14 }, +                    { 0x09, 0x15 }, +                    { 0x0a, 0x00 }, +                    { 0x0b, 0x00 }, +                    { 0x16, 0xbf }, +                    { 0x17, 0x08 }, +                    { 0x18, 0x3f }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x02 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0f }, +                    { 0x54, 0x13 }, +                    { 0x55, 0x17 }, +                    { 0x56, 0x03 }, +                    { 0x57, 0x07 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 300 }, 8751, { +                    { 0x08, 0x14 }, +                    { 0x09, 0x15 }, +                    { 0x0a, 0x00 }, +                    { 0x0b, 0x00 }, +                    { 0x16, 0xbf }, +                    { 0x17, 0x08 }, +                    { 0x18, 0x3f }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x02 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0f }, +                    { 0x54, 0x13 }, +                    { 0x55, 0x17 }, +                    { 0x56, 0x03 }, +                    { 0x57, 0x07 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 600 }, 18760, { +                    { 0x08, 0x0e }, +                    { 0x09, 0x0f }, +                    { 0x0a, 0x00 }, +                    { 0x0b, 0x00 }, +                    { 0x16, 0xbf }, +                    { 0x17, 0x08 }, +                    { 0x18, 0x31 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x02 }, +                    { 0x52, 0x03 }, +                    { 0x53, 0x07 }, +                    { 0x54, 0x0b }, +                    { 0x55, 0x0f }, +                    { 0x56, 0x13 }, +                    { 0x57, 0x17 }, +                    { 0x58, 0x23 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 1200 }, 21749, { +                    { 0x08, 0x02 }, +                    { 0x09, 0x04 }, +                    { 0x0a, 0x00 }, +                    { 0x0b, 0x00 }, +                    { 0x16, 0xbf }, +                    { 0x17, 0x08 }, +                    { 0x18, 0x30 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x42 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0f }, +                    { 0x54, 0x13 }, +                    { 0x55, 0x17 }, +                    { 0x56, 0x03 }, +                    { 0x57, 0x07 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x0e }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_HP2300; +    sensor.optical_res = 600; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 5368; +    sensor.fau_gain_white_ref = 180; +    sensor.gain_white_ref = 180; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_base_regs = { +        { 0x08, 0x16 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x01 }, +        { 0x0b, 0x03 }, +        { 0x16, 0xb7 }, +        { 0x17, 0x0a }, +        { 0x18, 0x20 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x6a }, +        { 0x1b, 0x8a }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x05 }, +        { 0x52, 0x0f }, +        { 0x53, 0x13 }, +        { 0x54, 0x17 }, +        { 0x55, 0x03 }, +        { 0x56, 0x07 }, +        { 0x57, 0x0b }, +        { 0x58, 0x83 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x06 }, +        { 0x5c, 0x0b }, +        { 0x5d, 0x10 }, +        { 0x5e, 0x16 }, +    }; +    sensor.gamma = { 2.1f, 2.1f, 2.1f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            unsigned exposure_lperiod; +            unsigned ccd_size_divisor; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 75 }, 4480, 2, { +                    { 0x08, 0x16 }, +                    { 0x09, 0x00 }, +                    { 0x0a, 0x01 }, +                    { 0x0b, 0x03 }, +                    { 0x16, 0xb7 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x20 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x6a }, +                    { 0x1b, 0x8a }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x85 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x06 }, +                    { 0x5c, 0x0b }, +                    { 0x5d, 0x10 }, +                    { 0x5e, 0x16 } +                } +            }, +            { { 150 }, 4350, 2, { +                    { 0x08, 0x16 }, +                    { 0x09, 0x00 }, +                    { 0x0a, 0x01 }, +                    { 0x0b, 0x03 }, +                    { 0x16, 0xb7 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x20 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x6a }, +                    { 0x1b, 0x8a }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x85 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x06 }, +                    { 0x5c, 0x0b }, +                    { 0x5d, 0x10 }, +                    { 0x5e, 0x16 } +                } +            }, +            { { 300 }, 4350, 2, { +                    { 0x08, 0x16 }, +                    { 0x09, 0x00 }, +                    { 0x0a, 0x01 }, +                    { 0x0b, 0x03 }, +                    { 0x16, 0xb7 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x20 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x6a }, +                    { 0x1b, 0x8a }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x85 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x06 }, +                    { 0x5c, 0x0b }, +                    { 0x5d, 0x10 }, +                    { 0x5e, 0x16 } +                } +            }, +            { { 600 }, 8700, 1, { +                    { 0x08, 0x01 }, +                    { 0x09, 0x03 }, +                    { 0x0a, 0x04 }, +                    { 0x0b, 0x06 }, +                    { 0x16, 0xb7 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x20 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x6a }, +                    { 0x1b, 0x8a }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x05 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x06 }, +                    { 0x5c, 0x0b }, +                    { 0x5d, 0x10 }, +                    { 0x5e, 0x16 } +                } +            }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.ccd_size_divisor = setting.ccd_size_divisor; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_35; +    sensor.optical_res = 1200; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 87; +    sensor.dummy_pixel = 87; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10400; +    sensor.fau_gain_white_ref = 0; +    sensor.gain_white_ref = 0; +    sensor.exposure = { 0x0400, 0x0400, 0x0400 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x00 }, +        { 0x17, 0x02 }, +        { 0x18, 0x00 }, +        { 0x19, 0x50 }, +        { 0x1a, 0x00 }, // TODO: 1a-1d: these do no harm, but may be neccessery for CCD +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x05 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x07 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x3a }, +        { 0x59, 0x03 }, +        { 0x5a, 0x40 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_XP200; +    sensor.optical_res = 600; +    sensor.black_pixels = 5; +    sensor.dummy_pixel = 38; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 5200; +    sensor.fau_gain_white_ref = 200; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x1450, 0x0c80, 0x0a28 }; +    sensor.custom_base_regs = { +        { 0x08, 0x16 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x01 }, +        { 0x0b, 0x03 }, +        { 0x16, 0xb7 }, +        { 0x17, 0x0a }, +        { 0x18, 0x20 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x6a }, +        { 0x1b, 0x8a }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x05 }, +        { 0x52, 0x0f }, +        { 0x53, 0x13 }, +        { 0x54, 0x17 }, +        { 0x55, 0x03 }, +        { 0x56, 0x07 }, +        { 0x57, 0x0b }, +        { 0x58, 0x83 }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc1 }, +        { 0x5b, 0x06 }, +        { 0x5c, 0x0b }, +        { 0x5d, 0x10 }, +        { 0x5e, 0x16 }, +    }; +    sensor.custom_regs = { +        { 0x08, 0x06 }, +        { 0x09, 0x07 }, +        { 0x0a, 0x0a }, +        { 0x0b, 0x04 }, +        { 0x16, 0x24 }, +        { 0x17, 0x04 }, +        { 0x18, 0x00 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x0a }, +        { 0x1b, 0x0a }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x11 }, +        { 0x52, 0x08 }, +        { 0x53, 0x02 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x1a }, +        { 0x59, 0x51 }, +        { 0x5a, 0x00 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x00 } +    }; +    sensor.gamma = { 2.1f, 2.1f, 2.1f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            std::vector<unsigned> channels; +            unsigned exposure_lperiod; +            SensorExposure exposure; +        }; + +        CustomSensorSettings custom_settings[] = { +            {  { 75 }, { 3 },  5700, { 0x1644, 0x0c80, 0x092e } }, +            { { 100 }, { 3 },  5700, { 0x1644, 0x0c80, 0x092e } }, +            { { 200 }, { 3 },  5700, { 0x1644, 0x0c80, 0x092e } }, +            { { 300 }, { 3 },  9000, { 0x1644, 0x0c80, 0x092e } }, +            { { 600 }, { 3 }, 16000, { 0x1644, 0x0c80, 0x092e } }, +            {  { 75 }, { 1 }, 16000, { 0x050a, 0x0fa0, 0x1010 } }, +            { { 100 }, { 1 },  7800, { 0x050a, 0x0fa0, 0x1010 } }, +            { { 200 }, { 1 }, 11000, { 0x050a, 0x0fa0, 0x1010 } }, +            { { 300 }, { 1 }, 13000, { 0x050a, 0x0fa0, 0x1010 } }, +            { { 600 }, { 1 }, 24000, { 0x050a, 0x0fa0, 0x1010 } }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.channels = setting.channels; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_HP3670; +    sensor.optical_res = 1200; +    sensor.black_pixels = 48; +    sensor.dummy_pixel = 16; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10872; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0, 0, 0 }; +    sensor.stagger_config = StaggerConfig{1200, 4}; // FIXME: may be incorrect +    sensor.custom_base_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x0a }, +        { 0x0a, 0x0b }, +        { 0x0b, 0x0d }, +        { 0x16, 0x33 }, +        { 0x17, 0x07 }, +        { 0x18, 0x20 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0xc0 }, +        { 0x1d, 0x43 }, +        { 0x52, 0x0f }, +        { 0x53, 0x13 }, +        { 0x54, 0x17 }, +        { 0x55, 0x03 }, +        { 0x56, 0x07 }, +        { 0x57, 0x0b }, +        { 0x58, 0x83 }, +        { 0x59, 0x00 }, +        { 0x5a, 0x15 }, +        { 0x5b, 0x05 }, +        { 0x5c, 0x0a }, +        { 0x5d, 0x0f }, +        { 0x5e, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            unsigned exposure_lperiod; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 50 }, 5758, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x0a }, +                    { 0x0a, 0x0b }, +                    { 0x0b, 0x0d }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x33 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x02 }, +                    { 0x1b, 0x13 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x15 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x05 }, +                    { 0x5c, 0x0a }, +                    { 0x5d, 0x0f }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 75 }, 4879, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x0a }, +                    { 0x0a, 0x0b }, +                    { 0x0b, 0x0d }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x33 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x02 }, +                    { 0x1b, 0x13 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x15 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x05 }, +                    { 0x5c, 0x0a }, +                    { 0x5d, 0x0f }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 100 }, 4487, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x0a }, +                    { 0x0a, 0x0b }, +                    { 0x0b, 0x0d }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x33 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x02 }, +                    { 0x1b, 0x13 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x15 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x05 }, +                    { 0x5c, 0x0a }, +                    { 0x5d, 0x0f }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 150 }, 4879, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x0a }, +                    { 0x0a, 0x0b }, +                    { 0x0b, 0x0d }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x33 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x02 }, +                    { 0x1b, 0x13 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x15 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x05 }, +                    { 0x5c, 0x0a }, +                    { 0x5d, 0x0f }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 300 }, 4503, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x0a }, +                    { 0x0a, 0x0b }, +                    { 0x0b, 0x0d }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x33 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x02 }, +                    { 0x1b, 0x13 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x0f }, +                    { 0x53, 0x13 }, +                    { 0x54, 0x17 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x83 }, +                    { 0x59, 0x15 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x05 }, +                    { 0x5c, 0x0a }, +                    { 0x5d, 0x0f }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 600 }, 10251, { +                    { 0x08, 0x00 }, +                    { 0x09, 0x05 }, +                    { 0x0a, 0x06 }, +                    { 0x0b, 0x08 }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x31 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x02 }, +                    { 0x1b, 0x0e }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0f }, +                    { 0x54, 0x13 }, +                    { 0x55, 0x17 }, +                    { 0x56, 0x03 }, +                    { 0x57, 0x07 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x02 }, +                    { 0x5c, 0x0e }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +            { { 1200 }, 12750, { +                    { 0x08, 0x0d }, +                    { 0x09, 0x0f }, +                    { 0x0a, 0x11 }, +                    { 0x0b, 0x13 }, +                    { 0x16, 0x2b }, +                    { 0x17, 0x07 }, +                    { 0x18, 0x30 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x00 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x43 }, +                    { 0x52, 0x03 }, +                    { 0x53, 0x07 }, +                    { 0x54, 0x0b }, +                    { 0x55, 0x0f }, +                    { 0x56, 0x13 }, +                    { 0x57, 0x17 }, +                    { 0x58, 0x23 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc1 }, +                    { 0x5b, 0x00 }, +                    { 0x5c, 0x00 }, +                    { 0x5d, 0x00 }, +                    { 0x5e, 0x00 } +                } +            }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_DP665; +    sensor.optical_res = 600; +    sensor.black_pixels = 27; +    sensor.dummy_pixel = 27; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 2496; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x1100, 0x1100, 0x1100 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x00 }, +        { 0x17, 0x02 }, +        { 0x18, 0x04 }, +        { 0x19, 0x50 }, +        { 0x1a, 0x10 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x05 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x54 }, +        { 0x59, 0x03 }, +        { 0x5a, 0x00 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x01 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_ROADWARRIOR; +    sensor.optical_res = 600; +    sensor.black_pixels = 27; +    sensor.dummy_pixel = 27; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 5200; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x1100, 0x1100, 0x1100 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x00 }, +        { 0x17, 0x02 }, +        { 0x18, 0x04 }, +        { 0x19, 0x50 }, +        { 0x1a, 0x10 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x05 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x54 }, +        { 0x59, 0x03 }, +        { 0x5a, 0x00 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x01 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_DSMOBILE600; +    sensor.optical_res = 600; +    sensor.black_pixels = 28; +    sensor.dummy_pixel = 28; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 5200; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x1544, 0x1544, 0x1544 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x00 }, +        { 0x17, 0x02 }, +        { 0x18, 0x04 }, +        { 0x19, 0x50 }, +        { 0x1a, 0x10 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x05 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x54 }, +        { 0x59, 0x03 }, +        { 0x5a, 0x00 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x01 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_XP300; +    sensor.optical_res = 600; +    sensor.black_pixels = 27; +    sensor.dummy_pixel = 27; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10240; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x1100, 0x1100, 0x1100 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x00 }, +        { 0x17, 0x02 }, +        { 0x18, 0x04 }, +        { 0x19, 0x50 }, +        { 0x1a, 0x10 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x05 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x54 }, +        { 0x59, 0x03 }, +        { 0x5a, 0x00 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x01 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_DP685; +    sensor.optical_res = 600; +    sensor.black_pixels = 27; +    sensor.dummy_pixel = 27; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 5020; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x1100, 0x1100, 0x1100 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x00 }, +        { 0x17, 0x02 }, +        { 0x18, 0x04 }, +        { 0x19, 0x50 }, +        { 0x1a, 0x10 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x02 }, +        { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x05 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x54 }, +        { 0x59, 0x03 }, +        { 0x5a, 0x00 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x01 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_200; +    sensor.optical_res = 4800; +    sensor.black_pixels = 87*4; +    sensor.dummy_pixel = 16*4; +    sensor.ccd_start_xoffset = 320*8; +    sensor.sensor_pixels = 5136*8; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.gamma = { 2.2f, 2.2f, 2.2f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            unsigned segment_size; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            // Note: Windows driver uses 1424 lperiod and enables dummy line (0x17) +            {   { 75, 100, 150, 200 }, 2848, { 304, 203, 180 }, 5136, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            // Note: Windows driver uses 788 lperiod and enables dummy line (0x17) +            {   { 300, 400 }, 1424, { 304, 203, 180 }, 5136, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 600 }, 1432, { 492, 326, 296 }, 5136, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 1200 }, 2712, { 935, 592, 538 }, 5136, { 0, 1 }, { +                    { 0x16, 0x10 }, { 0x17, 0x08 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 2400 }, 5280, { 1777, 1125, 979 }, 5136, { 0, 2, 1, 3 }, { +                    { 0x16, 0x10 }, { 0x17, 0x06 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 4800 }, 10416, { 3377, 2138, 1780 }, 5136, { 0, 2, 4, 6, 1, 3, 5, 7 }, { +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            } +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_size = setting.segment_size; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_700F; +    sensor.optical_res = 4800; +    sensor.black_pixels = 73*8; // black pixels 73 at 600 dpi +    sensor.dummy_pixel = 16*8; +    // 384 at 600 dpi +    sensor.ccd_start_xoffset = 384*8; +    // 8x5570 segments, 5187+1 for rounding +    sensor.sensor_pixels = 5188*8; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            unsigned segment_size; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 75, 100, 150, 200 }, 2848, { 465, 310, 239 }, 5187, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 300 }, 1424, { 465, 310, 239 }, 5187, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 600 }, 1504, { 465, 310, 239 }, 5187, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 1200 }, 2696, { 1464, 844, 555 }, 5187, { 0, 1 }, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 2400 }, 10576, { 2798, 1558, 972 }, 5187, { 0, 1, 2, 3 }, { +                    { 0x16, 0x10 }, { 0x17, 0x08 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 4800 }, 10576, { 2798, 1558, 972 }, 5187, { 0, 1, 4, 5, 2, 3, 6, 7 }, { +                    { 0x16, 0x10 }, { 0x17, 0x06 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            } +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_size = setting.segment_size; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_100; +    sensor.optical_res = 2400; +    sensor.black_pixels = 87*4; +    sensor.dummy_pixel = 16*4; +    sensor.ccd_start_xoffset = 320*4; +    sensor.sensor_pixels = 5136*4; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x01c1, 0x0126, 0x00e5 }; +    sensor.gamma = { 2.2f, 2.2f, 2.2f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            unsigned segment_size; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 75, 100, 150, 200 }, 2304, { 423, 294, 242 }, 5136, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 300 }, 1728, { 423, 294, 242 }, 5136, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 600 }, 1432, { 423, 294, 242 }, 5136, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                }, +            }, +            {   { 1200 }, 2712, { 791, 542, 403 }, 5136, {0, 1}, { +                    { 0x16, 0x10 }, { 0x17, 0x08 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            }, +            {   { 2400 }, 5280, { 1504, 1030, 766 }, 5136, {0, 2, 1, 3}, { +                    { 0x16, 0x10 }, { 0x17, 0x06 }, { 0x18, 0x00 }, { 0x19, 0xff }, +                    { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 }, +                    { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 }, +                    { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                } +            } +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_size = setting.segment_size; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_KVSS080; +    sensor.optical_res = 600; +    sensor.black_pixels = 38; +    sensor.dummy_pixel = 38; +    sensor.ccd_start_xoffset = 152; +    sensor.sensor_pixels = 5376; +    sensor.fau_gain_white_ref = 160; +    sensor.gain_white_ref = 160; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.exposure_lperiod = 8000; +    sensor.custom_regs = { +        { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 }, +        { 0x77, 0x00 }, { 0x78, 0xff }, { 0x79, 0xff }, +        { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff }, +        { 0x0c, 0x00 }, +        { 0x70, 0x01 }, +        { 0x71, 0x03 }, +        { 0x9e, 0x00 }, +        { 0xaa, 0x00 }, +        { 0x16, 0x33 }, +        { 0x17, 0x1c }, +        { 0x18, 0x00 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x2c }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x04 }, +        { 0x52, 0x0c }, +        { 0x53, 0x0f }, +        { 0x54, 0x00 }, +        { 0x55, 0x03 }, +        { 0x56, 0x06 }, +        { 0x57, 0x09 }, +        { 0x58, 0x6b }, +        { 0x59, 0x00 }, +        { 0x5a, 0xc0 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_G4050; +    sensor.optical_res = 4800; +    sensor.black_pixels = 50*8; +    // 31 at 600 dpi dummy_pixels 58 at 1200 +    sensor.dummy_pixel = 58; +    sensor.ccd_start_xoffset = 152; +    sensor.sensor_pixels = 5360*8; +    sensor.fau_gain_white_ref = 160; +    sensor.gain_white_ref = 160; +    sensor.exposure = { 0x2c09, 0x22b8, 0x10f0 }; +    sensor.stagger_config = StaggerConfig{ 2400, 4 }; // FIXME: may be incorrect +    sensor.custom_regs = {}; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            ScanMethod method; +            GenesysRegisterSettingSet extra_custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 100, 150, 200, 300, 400, 600 }, 8016, ScanMethod::FLATBED, { +                    { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff }, +                    { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff }, +                    { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff }, +                    { 0x0c, 0x00 }, +                    { 0x70, 0x00 }, +                    { 0x71, 0x02 }, +                    { 0x9e, 0x00 }, +                    { 0xaa, 0x00 }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x00 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x08 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0e }, +                    { 0x54, 0x11 }, +                    { 0x55, 0x02 }, +                    { 0x56, 0x05 }, +                    { 0x57, 0x08 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                } +            }, +            { { 1200 }, 56064, ScanMethod::FLATBED, { +                    { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x01 }, { 0x79, 0xff }, +                    { 0x7a, 0x00 }, { 0x7b, 0x01 }, { 0x7c, 0xff }, +                    { 0x0c, 0x20 }, +                    { 0x70, 0x08 }, +                    { 0x71, 0x0c }, +                    { 0x9e, 0xc0 }, +                    { 0xaa, 0x05 }, +                    { 0x16, 0x3b }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x38 }, +                    { 0x1b, 0x10 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x08 }, +                    { 0x52, 0x02 }, +                    { 0x53, 0x05 }, +                    { 0x54, 0x08 }, +                    { 0x55, 0x0b }, +                    { 0x56, 0x0e }, +                    { 0x57, 0x11 }, +                    { 0x58, 0x1b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                } +            }, +            { { 2400 }, 56064, ScanMethod::FLATBED, { +                    { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +                    { 0x0c, 0x20 }, +                    { 0x70, 0x08 }, +                    { 0x71, 0x0a }, +                    { 0x9e, 0xc0 }, +                    { 0xaa, 0x05 }, +                    { 0x16, 0x3b }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x38 }, +                    { 0x1b, 0x10 }, +                    { 0x1c, 0xc0 }, +                    { 0x1d, 0x08 }, +                    { 0x52, 0x02 }, +                    { 0x53, 0x05 }, +                    { 0x54, 0x08 }, +                    { 0x55, 0x0b }, +                    { 0x56, 0x0e }, +                    { 0x57, 0x11 }, +                    { 0x58, 0x1b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                } +            }, +            { { 4800 }, 42752, ScanMethod::FLATBED, { +                    { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +                    { 0x0c, 0x21 }, +                    { 0x70, 0x08 }, +                    { 0x71, 0x0a }, +                    { 0x9e, 0xc0 }, +                    { 0xaa, 0x07 }, +                    { 0x16, 0x3b }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x38 }, +                    { 0x1b, 0x10 }, +                    { 0x1c, 0xc1 }, +                    { 0x1d, 0x08 }, +                    { 0x52, 0x02 }, +                    { 0x53, 0x05 }, +                    { 0x54, 0x08 }, +                    { 0x55, 0x0b }, +                    { 0x56, 0x0e }, +                    { 0x57, 0x11 }, +                    { 0x58, 0x1b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                } +            }, +            { ResolutionFilter::ANY, 15624, ScanMethod::TRANSPARENCY, { +                    { 0x74, 0x00 }, { 0x75, 0x1c }, { 0x76, 0x7f }, +                    { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff }, +                    { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff }, +                    { 0x0c, 0x00 }, +                    { 0x70, 0x00 }, +                    { 0x71, 0x02 }, +                    { 0x9e, 0x00 }, +                    { 0xaa, 0x00 }, +                    { 0x16, 0x33 }, +                    { 0x17, 0x4c }, +                    { 0x18, 0x01 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x08 }, +                    { 0x52, 0x0e }, +                    { 0x53, 0x11 }, +                    { 0x54, 0x02 }, +                    { 0x55, 0x05 }, +                    { 0x56, 0x08 }, +                    { 0x57, 0x0b }, +                    { 0x58, 0x6b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0xc0 }, +                } +            } +        }; + +        auto base_custom_regs = sensor.custom_regs; +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.method = setting.method; +            sensor.custom_regs = base_custom_regs; +            sensor.custom_regs.merge(setting.extra_custom_regs); +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_HP_4850C; +    sensor.optical_res = 4800; +    sensor.black_pixels = 100; +    sensor.dummy_pixel = 58; +    sensor.ccd_start_xoffset = 152; +    sensor.sensor_pixels = 5360*8; +    sensor.fau_gain_white_ref = 160; +    sensor.gain_white_ref = 160; +    sensor.exposure = { 0x2c09, 0x22b8, 0x10f0 }; +    sensor.stagger_config = StaggerConfig{ 2400, 4 }; // FIXME: may be incorrect +    sensor.custom_regs = {}; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            ScanMethod method; +            GenesysRegisterSettingSet extra_custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 100, 150, 200, 300, 400, 600 }, 8016, ScanMethod::FLATBED, { +                    { 0x0c, 0x00 }, +                    { 0x16, 0x33 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x08 }, +                    { 0x52, 0x0b }, { 0x53, 0x0e }, { 0x54, 0x11 }, { 0x55, 0x02 }, +                    { 0x56, 0x05 }, { 0x57, 0x08 }, { 0x58, 0x63 }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, +                    { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff }, +                    { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff }, +                    { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff }, +                    { 0x9e, 0x00 }, +                    { 0xaa, 0x00 }, +                } +            }, +            {   { 1200 }, 56064, ScanMethod::FLATBED, { +                    { 0x0c, 0x20 }, +                    { 0x16, 0x3b }, { 0x17, 0x0c }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x38 }, { 0x1b, 0x10 }, { 0x1c, 0x00 }, { 0x1d, 0x08 }, +                    { 0x52, 0x02 }, { 0x53, 0x05 }, { 0x54, 0x08 }, { 0x55, 0x0b }, +                    { 0x56, 0x0e }, { 0x57, 0x11 }, { 0x58, 0x1b }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x08 }, { 0x71, 0x0c }, +                    { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x01 }, { 0x79, 0xff }, +                    { 0x7a, 0x00 }, { 0x7b, 0x01 }, { 0x7c, 0xff }, +                    { 0x9e, 0xc0 }, +                    { 0xaa, 0x05 }, +                } +            }, +            {   { 2400 }, 56064, ScanMethod::FLATBED, { +                    { 0x0c, 0x20 }, +                    { 0x16, 0x3b }, { 0x17, 0x0c }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x38 }, { 0x1b, 0x10 }, { 0x1c, 0xc0 }, { 0x1d, 0x08 }, +                    { 0x52, 0x02 }, { 0x53, 0x05 }, { 0x54, 0x08 }, { 0x55, 0x0b }, +                    { 0x56, 0x0e }, { 0x57, 0x11 }, { 0x58, 0x1b }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x08 }, { 0x71, 0x0a }, +                    { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +                    { 0x9e, 0xc0 }, +                    { 0xaa, 0x05 }, +                } +            }, +            {   { 4800 }, 42752, ScanMethod::FLATBED, { +                    { 0x0c, 0x21 }, +                    { 0x16, 0x3b }, { 0x17, 0x0c }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x38 }, { 0x1b, 0x10 }, { 0x1c, 0xc1 }, { 0x1d, 0x08 }, +                    { 0x52, 0x02 }, { 0x53, 0x05 }, { 0x54, 0x08 }, { 0x55, 0x0b }, +                    { 0x56, 0x0e }, { 0x57, 0x11 }, { 0x58, 0x1b }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x08 }, { 0x71, 0x0a }, +                    { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +                    { 0x9e, 0xc0 }, +                    { 0xaa, 0x07 }, +                } +            }, +            {   ResolutionFilter::ANY, 15624, ScanMethod::TRANSPARENCY, { +                    { 0x0c, 0x00 }, +                    { 0x16, 0x33 }, { 0x17, 0x4c }, { 0x18, 0x01 }, { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x08 }, +                    { 0x52, 0x0e }, { 0x53, 0x11 }, { 0x54, 0x02 }, { 0x55, 0x05 }, +                    { 0x56, 0x08 }, { 0x57, 0x0b }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0xc0 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, +                    { 0x74, 0x00 }, { 0x75, 0x1c }, { 0x76, 0x7f }, +                    { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff }, +                    { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff }, +                    { 0x9e, 0x00 }, +                    { 0xaa, 0x00 }, +                } +            } +        }; + +        auto base_custom_regs = sensor.custom_regs; +        for (const CustomSensorSettings& setting : custom_settings) +        { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.method = setting.method; +            sensor.custom_regs = base_custom_regs; +            sensor.custom_regs.merge(setting.extra_custom_regs); +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_CANON_4400F; +    sensor.optical_res = 4800; +    sensor.ccd_size_divisor = 4; +    sensor.black_pixels = 50*8; +    // 31 at 600 dpi, 58 at 1200 dpi +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 152; +    // 5360 max at 600 dpi +    sensor.sensor_pixels = 5700 * 8; +    sensor.fau_gain_white_ref = 160; +    sensor.gain_white_ref = 160; +    sensor.exposure = { 0x9c40, 0x9c40, 0x9c40 }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = get_sensor_optical_with_ccd_divisor; +    sensor.get_register_hwdpi_fun = [](const Genesys_Sensor&, unsigned) { return 4800; }; +    sensor.get_hwdpi_divisor_fun = [](const Genesys_Sensor&, unsigned) { return 1; }; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            std::vector<ScanMethod> methods; +            GenesysRegisterSettingSet extra_custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 300, 600, 1200 }, 11640, { ScanMethod::FLATBED }, { +                    { 0x16, 0x13 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x6b }, +                    { 0x52, 0x0a }, +                    { 0x53, 0x0d }, +                    { 0x54, 0x00 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x06 }, +                    { 0x57, 0x08 }, +                    { 0x58, 0x5b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x01 }, { 0x73, 0x03 }, +                    { 0x74, 0x00 }, { 0x75, 0xf8 }, { 0x76, 0x38 }, +                    { 0x77, 0x00 }, { 0x78, 0xfc }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0xa4 }, +                    { 0x9e, 0x2d }, +                } +            }, +            { { 300, 600, 1200 }, 33300, { ScanMethod::TRANSPARENCY }, { +                    { 0x16, 0x13 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x6b }, +                    { 0x52, 0x0a }, +                    { 0x53, 0x0d }, +                    { 0x54, 0x00 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x06 }, +                    { 0x57, 0x08 }, +                    { 0x58, 0x5b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x00 }, { 0x73, 0x02 }, +                    { 0x74, 0x00 }, { 0x75, 0xf8 }, { 0x76, 0x38 }, +                    { 0x77, 0x00 }, { 0x78, 0xfc }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0xa4 }, +                    { 0x9e, 0x2d }, +                } +            }, +            { { 2400 }, 33300, { ScanMethod::TRANSPARENCY }, { +                    { 0x16, 0x13 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x01 }, +                    { 0x1d, 0x75 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0d }, +                    { 0x54, 0x00 }, +                    { 0x55, 0x03 }, +                    { 0x56, 0x06 }, +                    { 0x57, 0x09 }, +                    { 0x58, 0x53 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 }, +                    { 0x74, 0x00 }, { 0x75, 0xff }, { 0x76, 0x00 }, +                    { 0x77, 0x00 }, { 0x78, 0xff }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x54 }, { 0x7c, 0x92 }, +                    { 0x9e, 0x2d }, +                } +            }, +            { { 4800 }, 33300, { ScanMethod::TRANSPARENCY }, { +                    { 0x16, 0x13 }, +                    { 0x17, 0x0a }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x61 }, +                    { 0x1d, 0x75 }, +                    { 0x52, 0x02 }, +                    { 0x53, 0x05 }, +                    { 0x54, 0x08 }, +                    { 0x55, 0x0b }, +                    { 0x56, 0x0d }, +                    { 0x57, 0x0f }, +                    { 0x58, 0x1b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x08 }, { 0x71, 0x0a }, { 0x72, 0x0a }, { 0x73, 0x0c }, +                    { 0x74, 0x00 }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0xff }, { 0x79, 0xff }, +                    { 0x7a, 0x00 }, { 0x7b, 0x54 }, { 0x7c, 0x92 }, +                    { 0x9e, 0x2d }, +                } +            } +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            for (auto method : setting.methods) { +                sensor.resolutions = setting.resolutions; +                sensor.exposure_lperiod = setting.exposure_lperiod; +                sensor.method = method; +                sensor.custom_regs = setting.extra_custom_regs; +                s_sensors->push_back(sensor); +            } +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_CANON_8400F; +    sensor.optical_res = 3200; +    sensor.register_dpihw_override = 4800; +    sensor.ccd_size_divisor = 1; +    sensor.black_pixels = 50*8; +    // 31 at 600 dpi, 58 at 1200 dpi +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 152; +    sensor.sensor_pixels = 27200; +    sensor.fau_gain_white_ref = 160; +    sensor.gain_white_ref = 160; +    sensor.exposure = { 0x9c40, 0x9c40, 0x9c40 }; +    sensor.stagger_config = StaggerConfig{ 3200, 6 }; +    sensor.custom_regs = {}; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = get_sensor_optical_with_ccd_divisor; +    sensor.get_register_hwdpi_fun = [](const Genesys_Sensor&, unsigned) { return 4800; }; +    sensor.get_hwdpi_divisor_fun = [](const Genesys_Sensor&, unsigned) { return 1; }; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            unsigned dpiset_override; +            unsigned pixel_count_multiplier; +            int exposure_lperiod; +            std::vector<ScanMethod> methods; +            GenesysRegisterSettingSet extra_custom_regs; +            GenesysRegisterSettingSet custom_fe_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 400 }, 2400, 1, 7200, { ScanMethod::FLATBED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x13 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa0 }, +                    { 0x52, 0x0d }, +                    { 0x53, 0x10 }, +                    { 0x54, 0x01 }, +                    { 0x55, 0x04 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0a }, +                    { 0x58, 0x6b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 }, +                    { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb }, +                    { 0x80, 0x2a }, +                }, {} +            }, +            { { 800 }, 4800, 1, 7200, { ScanMethod::FLATBED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x13 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa0 }, +                    { 0x52, 0x0d }, +                    { 0x53, 0x10 }, +                    { 0x54, 0x01 }, +                    { 0x55, 0x04 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0a }, +                    { 0x58, 0x6b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 }, +                    { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb }, +                    { 0x80, 0x20 }, +                }, {} +            }, +            { { 1600 }, 4800, 1, 14400, { ScanMethod::FLATBED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x11 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa1 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0e }, +                    { 0x54, 0x11 }, +                    { 0x55, 0x02 }, +                    { 0x56, 0x05 }, +                    { 0x57, 0x08 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x03 }, +                    { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 }, +                    { 0x80, 0x28 }, +                }, { +                    { 0x03, 0x1f }, +                } +            }, +            { { 3200 }, 4800, 1, 28800, { ScanMethod::FLATBED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x20 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa1 }, +                    { 0x52, 0x02 }, +                    { 0x53, 0x05 }, +                    { 0x54, 0x08 }, +                    { 0x55, 0x0b }, +                    { 0x56, 0x0e }, +                    { 0x57, 0x11 }, +                    { 0x58, 0x1b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x09 }, { 0x71, 0x0a }, { 0x72, 0x0b }, { 0x73, 0x0c }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 }, +                    { 0x80, 0x2b }, +                }, { +                    { 0x03, 0x1f }, +                }, +            }, +            { { 400 }, 2400, 1, 14400, { ScanMethod::TRANSPARENCY, +                                         ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x13 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa0 }, +                    { 0x52, 0x0d }, +                    { 0x53, 0x10 }, +                    { 0x54, 0x01 }, +                    { 0x55, 0x04 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0a }, +                    { 0x58, 0x6b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 }, +                    { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb }, +                    { 0x80, 0x20 }, +                }, {} +            }, +            { { 800 }, 4800, 1, 14400, { ScanMethod::TRANSPARENCY, +                                         ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x13 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa0 }, +                    { 0x52, 0x0d }, +                    { 0x53, 0x10 }, +                    { 0x54, 0x01 }, +                    { 0x55, 0x04 }, +                    { 0x56, 0x07 }, +                    { 0x57, 0x0a }, +                    { 0x58, 0x6b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 }, +                    { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb }, +                    { 0x80, 0x20 }, +                }, {} +            }, +            { { 1600 }, 4800, 1, 28800, { ScanMethod::TRANSPARENCY, +                                          ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x11 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x00 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa0 }, +                    { 0x52, 0x0b }, +                    { 0x53, 0x0e }, +                    { 0x54, 0x11 }, +                    { 0x55, 0x02 }, +                    { 0x56, 0x05 }, +                    { 0x57, 0x08 }, +                    { 0x58, 0x63 }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x01 }, { 0x72, 0x02 }, { 0x73, 0x03 }, +                    { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 }, +                    { 0x80, 0x29 }, +                }, { +                    { 0x03, 0x1f }, +                }, +            }, +            { { 3200 }, 4800, 1, 28800, { ScanMethod::TRANSPARENCY, +                                          ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x16, 0x33 }, +                    { 0x17, 0x0c }, +                    { 0x18, 0x10 }, +                    { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, +                    { 0x1b, 0x00 }, +                    { 0x1c, 0x20 }, +                    { 0x1d, 0x84 }, +                    { 0x1e, 0xa0 }, +                    { 0x52, 0x02 }, +                    { 0x53, 0x05 }, +                    { 0x54, 0x08 }, +                    { 0x55, 0x0b }, +                    { 0x56, 0x0e }, +                    { 0x57, 0x11 }, +                    { 0x58, 0x1b }, +                    { 0x59, 0x00 }, +                    { 0x5a, 0x40 }, +                    { 0x70, 0x09 }, { 0x71, 0x0a }, { 0x72, 0x0b }, { 0x73, 0x0c }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 }, +                    { 0x80, 0x2b }, +                }, { +                    { 0x03, 0x1f }, +                }, +            }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) +        { +            for (auto method : setting.methods) { +                sensor.resolutions = setting.resolutions; +                sensor.dpiset_override = setting.dpiset_override; +                sensor.pixel_count_multiplier = setting.pixel_count_multiplier; +                sensor.exposure_lperiod = setting.exposure_lperiod; +                sensor.method = method; +                sensor.custom_regs = setting.extra_custom_regs; +                sensor.custom_fe_regs = setting.custom_fe_regs; +                s_sensors->push_back(sensor); +            } +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_CANON_8600F; +    sensor.optical_res = 4800; +    sensor.ccd_size_divisor = 4; +    sensor.black_pixels = 31; +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 0; // not used at the moment +    // 11372 pixels at 1200 dpi +    sensor.sensor_pixels = 11372*4; +    sensor.fau_gain_white_ref = 160; +    sensor.gain_white_ref = 160; +    sensor.exposure = { 0x9c40, 0x9c40, 0x9c40 }; +    sensor.stagger_config = StaggerConfig{4800, 8}; +    sensor.custom_regs = {}; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = get_sensor_optical_with_ccd_divisor; +    sensor.get_register_hwdpi_fun = [](const Genesys_Sensor&, unsigned) { return 4800; }; +    sensor.get_hwdpi_divisor_fun = [](const Genesys_Sensor&, unsigned) { return 1; }; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            std::vector<ScanMethod> methods; +            GenesysRegisterSettingSet extra_custom_regs; +            GenesysRegisterSettingSet custom_fe_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 300, 600, 1200 }, 24000, { ScanMethod::FLATBED }, { +                    { 0x0c, 0x00 }, +                    { 0x16, 0x13 }, { 0x17, 0x0a }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x6b }, +                    { 0x52, 0x0c }, { 0x53, 0x0f }, { 0x54, 0x00 }, { 0x55, 0x03 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 }, +                    { 0x56, 0x06 }, { 0x57, 0x09 }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x74, 0x03 }, { 0x75, 0xf0 }, { 0x76, 0xf0 }, +                    { 0x77, 0x03 }, { 0x78, 0xfe }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 }, +                    { 0x9e, 0x2d }, +                    { 0xaa, 0x00 }, +                }, +                {}, +            }, +            {   { 300, 600, 1200 }, 45000, { ScanMethod::TRANSPARENCY, +                                             ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x0c, 0x00 }, +                    { 0x16, 0x13 }, { 0x17, 0x0a }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x6b }, +                    { 0x52, 0x0c }, { 0x53, 0x0f }, { 0x54, 0x00 }, { 0x55, 0x03 }, +                    { 0x56, 0x06 }, { 0x57, 0x09 }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 }, +                    { 0x74, 0x03 }, { 0x75, 0xf0 }, { 0x76, 0xf0 }, +                    { 0x77, 0x03 }, { 0x78, 0xfe }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 }, +                    { 0x9e, 0x2d }, +                    { 0xaa, 0x00 }, +                }, +                {}, +            }, +            {   { 2400 }, 45000, { ScanMethod::TRANSPARENCY, +                                   ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x0c, 0x00 }, +                    { 0x16, 0x13 }, { 0x17, 0x15 }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x01 }, { 0x1d, 0x75 }, +                    { 0x52, 0x0c }, { 0x53, 0x0f }, { 0x54, 0x00 }, { 0x55, 0x03 }, +                    { 0x56, 0x06 }, { 0x57, 0x09 }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 }, +                    { 0x74, 0x03 }, { 0x75, 0xfe }, { 0x76, 0x00 }, +                    { 0x77, 0x03 }, { 0x78, 0xfe }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 }, +                    { 0x9e, 0x2d }, +                    { 0xaa, 0x00 }, +                }, +                {}, +            }, +            {   { 4800 }, 45000, { ScanMethod::TRANSPARENCY, +                                   ScanMethod::TRANSPARENCY_INFRARED }, { +                    { 0x0c, 0x00 }, +                    { 0x16, 0x13 }, { 0x17, 0x15 }, { 0x18, 0x10 }, { 0x19, 0x2a }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x61 }, { 0x1d, 0x75 }, +                    { 0x52, 0x03 }, { 0x53, 0x06 }, { 0x54, 0x09 }, { 0x55, 0x0c }, +                    { 0x56, 0x0f }, { 0x57, 0x00 }, { 0x58, 0x23 }, { 0x59, 0x00 }, { 0x5a, 0x40 }, +                    { 0x70, 0x0a }, { 0x71, 0x0c }, { 0x72, 0x0c }, { 0x73, 0x0e }, +                    { 0x74, 0x03 }, { 0x75, 0xff }, { 0x76, 0xff }, +                    { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff }, +                    { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 }, +                    { 0x9e, 0x2d }, +                    { 0xaa, 0x00 }, +                }, +                {   { 0x03, 0x1f }, +                }, +            }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) { +            for (auto method : setting.methods) { +                sensor.resolutions = setting.resolutions; +                sensor.method = method; +                sensor.exposure_lperiod = setting.exposure_lperiod; +                sensor.custom_regs = setting.extra_custom_regs; +                sensor.custom_fe_regs = setting.custom_fe_regs; +                s_sensors->push_back(sensor); +            } +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_HP_N6310; +    sensor.optical_res = 2400; +    // sensor.ccd_size_divisor = 2; Possibly half CCD, needs checking +    sensor.black_pixels = 96; +    sensor.dummy_pixel = 26; +    sensor.ccd_start_xoffset = 128; +    sensor.sensor_pixels = 42720; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_regs = { +        { 0x16, 0x33 }, +        { 0x17, 0x0c }, +        { 0x18, 0x02 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x30 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x08 }, +        { 0x52, 0x0b }, +        { 0x53, 0x0e }, +        { 0x54, 0x11 }, +        { 0x55, 0x02 }, +        { 0x56, 0x05 }, +        { 0x57, 0x08 }, +        { 0x58, 0x63 }, +        { 0x59, 0x00 }, +        { 0x5a, 0x40 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_110; +    sensor.optical_res = 2400; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 87; +    sensor.dummy_pixel = 16; +    sensor.ccd_start_xoffset = 303; +    sensor.sensor_pixels = 5168*4; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.gamma = { 2.2f, 2.2f, 2.2f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 75, 100, 150 }, 4608, { 462, 609, 453 }, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0c }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 }, +                    { 0x96, 0x00 }, { 0x97, 0x9a }, +                    { 0x98, 0x21 }, +                } +            }, +            {   { 300 }, 4608, { 462, 609, 453 }, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x0c }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 }, +                    { 0x96, 0x00 }, { 0x97, 0x9a }, +                    { 0x98, 0x21 }, +                } +            }, +            {   { 600 }, 5360, { 823, 1117, 805 }, std::vector<unsigned>{}, { +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x0a }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x21 }, +                }, +            }, +            {   { 1200 }, 10528, { 6071, 6670, 6042 }, { 0, 1 }, { +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 },{ 0x20, 0x08 }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x12 }, { 0x89, 0x47 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x22 }, +                } +            }, +            {   { 2400 }, 20864, { 7451, 8661, 7405 }, { 0, 2, 1, 3 }, { +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x06 }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x12 }, { 0x89, 0x47 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x24 }, +                } +            } +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_120; +    sensor.optical_res = 2400; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 87; +    sensor.dummy_pixel = 16; +    sensor.ccd_start_xoffset = 303; +    // SEGCNT at 600 DPI by number of segments +    sensor.sensor_pixels = 5104*4; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.gamma = { 2.2f, 2.2f, 2.2f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 75, 100, 150, 300 }, 4608, { 1244, 1294, 1144 }, std::vector<unsigned>{}, { +                    { 0x16, 0x15 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 }, +                    { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x00 }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x5e }, +                    { 0x93, 0x00 }, { 0x94, 0x09 }, { 0x95, 0xf8 }, +                    { 0x96, 0x00 }, { 0x97, 0x70 }, +                    { 0x98, 0x21 }, +                }, +            }, +            {   { 600 }, 5360, { 2394, 2444, 2144 }, std::vector<unsigned>{}, { +                    { 0x16, 0x11 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 }, +                    { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x1f }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x5e }, +                    { 0x93, 0x00 }, { 0x94, 0x13 }, { 0x95, 0xf0 }, +                    { 0x96, 0x00 }, { 0x97, 0x8b }, +                    { 0x98, 0x21 }, +                }, +            }, +            {   { 1200 }, 10528, { 4694, 4644, 4094 }, std::vector<unsigned>{}, { +                    { 0x16, 0x15 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 }, +                    { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x1f }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x5e }, +                    { 0x93, 0x00 }, { 0x94, 0x27 }, { 0x95, 0xe0 }, +                    { 0x96, 0x00 }, { 0x97, 0xc0 }, +                    { 0x98, 0x21 }, +                }, +            }, +            {   { 2400 }, 20864, { 8944, 8144, 7994 }, std::vector<unsigned>{}, { +                    { 0x16, 0x11 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 }, +                    { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 }, +                    { 0x61, 0x20 }, +                    { 0x70, 0x00 }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a }, +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x5e }, +                    { 0x93, 0x00 }, { 0x94, 0x4f }, { 0x95, 0xc0 }, +                    { 0x96, 0x01 }, { 0x97, 0x2a }, +                    { 0x98, 0x21 }, +                } +            }, +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_210; +    sensor.optical_res = 2400; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 87; +    sensor.dummy_pixel = 16; +    sensor.ccd_start_xoffset = 303; +    sensor.sensor_pixels = 5168*4; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.gamma = { 2.2f, 2.2f, 2.2f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 75, 100, 150, 300 }, 2768, { 388, 574, 393 }, std::vector<unsigned>{}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0c }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 }, +                    { 0x96, 0x00 }, { 0x97, 0x9a }, +                    { 0x98, 0x21 }, +                } +            }, +            {   { 600 }, 5360, { 388, 574, 393 }, std::vector<unsigned>{}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0a }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x21 }, +                } +            }, +            {   { 1200 }, 10528, { 388, 574, 393 }, {0, 1}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x08 }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x22 }, +                }, +            }, +            {   { 2400 }, 20864, { 6839, 8401, 6859 }, {0, 2, 1, 3}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x06 }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x12 }, { 0x89, 0x47 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x24 }, +                }, +            } +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_220; +    sensor.optical_res = 2400; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 87; +    sensor.dummy_pixel = 16; +    sensor.ccd_start_xoffset = 303; +    sensor.sensor_pixels = 5168*4; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.gamma = { 2.2f, 2.2f, 2.2f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124; + +    { +        struct CustomSensorSettings { +            ResolutionFilter resolutions; +            int exposure_lperiod; +            SensorExposure exposure; +            std::vector<unsigned> segment_order; +            GenesysRegisterSettingSet custom_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            {   { 75, 100, 150, 300 }, 2768, { 388, 574, 393 }, std::vector<unsigned>{}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0c }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 }, +                    { 0x96, 0x00 }, { 0x97, 0x9a }, +                    { 0x98, 0x21 }, +                } +            }, +            {   { 600 }, 5360, { 388, 574, 393 }, std::vector<unsigned>{}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0a }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x21 }, +                } +            }, +            {   { 1200 }, 10528, { 388, 574, 393 }, {0, 1}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x08 }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x00 }, { 0x89, 0x65 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x22 }, +                } +            }, +            {   { 2400 }, 20864, { 6839, 8401, 6859 }, {0, 2, 1, 3}, { +                    // { 0x16, 0x00 }, // FIXME: check if default value is different +                    { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 }, +                    { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x06 }, +                    { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 }, +                    { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 }, +                    { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 }, +                    { 0x61, 0x20 }, +                    // { 0x70, 0x00 }, // FIXME: check if default value is different +                    { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f }, +                    { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +                    { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +                    { 0x88, 0x12 }, { 0x89, 0x47 }, +                    { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 }, +                    { 0x96, 0x00 }, { 0x97, 0xa3 }, +                    { 0x98, 0x24 }, +                }, +            } +        }; + +        for (const auto& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.exposure = setting.exposure; +            sensor.segment_order = setting.segment_order; +            sensor.custom_regs = setting.custom_regs; +            s_sensors->push_back(sensor); +        } +    } + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICPRO_3600; +    sensor.optical_res = 1200; +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 87; +    sensor.dummy_pixel = 87; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10100; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x0b, 0x00 }, +        { 0x16, 0x33 }, +        { 0x17, 0x0b }, +        { 0x18, 0x11 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0xc4 }, +        { 0x52, 0x07 }, // [GB](HI|LOW) not needed for cis +        { 0x53, 0x0a }, +        { 0x54, 0x0c }, +        { 0x55, 0x00 }, +        { 0x56, 0x02 }, +        { 0x57, 0x06 }, +        { 0x58, 0x22 }, +        { 0x59, 0x69 }, +        { 0x5a, 0x40 }, +        { 0x5b, 0x00 }, // TODO: 5b-5e +        { 0x5c, 0x00 }, +        { 0x5d, 0x00 }, +        { 0x5e, 0x02 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7200I; +    sensor.optical_res = 7200; +    sensor.register_dpihw_override = 1200; +    sensor.black_pixels = 88; // TODO +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10200; // TODO +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.stagger_config = StaggerConfig{7200, 4}; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x16, 0x23 }, +        { 0x17, 0x0c }, +        { 0x18, 0x10 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x21 }, +        { 0x1d, 0x84 }, +        { 0x52, 0x0a }, +        { 0x53, 0x0d }, +        { 0x54, 0x10 }, +        { 0x55, 0x01 }, +        { 0x56, 0x04 }, +        { 0x57, 0x07 }, +        { 0x58, 0x3a }, +        { 0x59, 0x81 }, +        { 0x5a, 0xc0 }, +        { 0x70, 0x0a }, +        { 0x71, 0x0b }, +        { 0x72, 0x0c }, +        { 0x73, 0x0d }, +        { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 }, +        { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +        { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; +    { +        struct CustomSensorSettings +        { +            ResolutionFilter resolutions; +            ScanMethod method; +            unsigned ccd_size_divisor; +            unsigned logical_dpihw_override; +            unsigned pixel_count_multiplier; +            unsigned exposure_lperiod; +            unsigned dpiset_override; +            GenesysRegisterSettingSet custom_fe_regs; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 900 }, ScanMethod::TRANSPARENCY, 1, 900, 8, 0x2538, 150, {} }, +            { { 1800 }, ScanMethod::TRANSPARENCY, 1, 1800, 4, 0x2538, 300, {} }, +            { { 3600 }, ScanMethod::TRANSPARENCY, 1, 3600, 2, 0x2538, 600, {} }, +            { { 7200 }, ScanMethod::TRANSPARENCY, 1, 7200, 1, 0x19c8, 1200, { +                    { 0x02, 0x1b }, +                    { 0x03, 0x14 }, +                    { 0x04, 0x20 }, +                } +            }, +            { { 900 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 900, 8, 0x1f54, 150, {} }, +            { { 1800 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 1800, 4, 0x1f54, 300, {} }, +            { { 3600 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 3600, 2, 0x1f54, 600, {} }, +            { { 7200 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 7200, 1, 0x1f54, 1200, {} }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.method = setting.method; +            sensor.ccd_size_divisor = setting.ccd_size_divisor; +            sensor.logical_dpihw_override = setting.logical_dpihw_override; +            sensor.pixel_count_multiplier = setting.pixel_count_multiplier; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.dpiset_override = setting.dpiset_override; +            sensor.custom_fe_regs = setting.custom_fe_regs; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7300; +    sensor.optical_res = 7200; +    sensor.method = ScanMethod::TRANSPARENCY; +    sensor.register_dpihw_override = 1200; +    sensor.black_pixels = 88; // TODO +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10200; // TODO +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.exposure_lperiod = 0x2f44; +    sensor.stagger_config = StaggerConfig{7200, 4}; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x16, 0x27 }, +        { 0x17, 0x0c }, +        { 0x18, 0x10 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x84 }, +        { 0x52, 0x0a }, +        { 0x53, 0x0d }, +        { 0x54, 0x0f }, +        { 0x55, 0x01 }, +        { 0x56, 0x04 }, +        { 0x57, 0x07 }, +        { 0x58, 0x31 }, +        { 0x59, 0x79 }, +        { 0x5a, 0xc0 }, +        { 0x70, 0x0c }, +        { 0x71, 0x0d }, +        { 0x72, 0x0e }, +        { 0x73, 0x0f }, +        { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 }, +        { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +        { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; +    { +        struct CustomSensorSettings +        { +            ResolutionFilter resolutions; +            unsigned ccd_size_divisor; +            unsigned logical_dpihw_override; +            unsigned pixel_count_multiplier; +            unsigned dpiset_override; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 900 }, 1, 900, 8, 150 }, +            { { 1800 }, 1, 1800, 4, 300 }, +            { { 3600 }, 1, 3600, 2, 600 }, +            { { 7200 }, 1, 7200, 1, 1200 }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.ccd_size_divisor = setting.ccd_size_divisor; +            sensor.logical_dpihw_override = setting.logical_dpihw_override; +            sensor.pixel_count_multiplier = setting.pixel_count_multiplier; +            sensor.dpiset_override = setting.dpiset_override; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7500I; +    sensor.optical_res = 7200; +    sensor.register_dpihw_override = 1200; +    sensor.black_pixels = 88; // TODO +    sensor.dummy_pixel = 20; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10200; // TODO +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 230; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.stagger_config = StaggerConfig{7200, 4}; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x00 }, +        { 0x0a, 0x00 }, +        { 0x16, 0x27 }, +        { 0x17, 0x0c }, +        { 0x18, 0x10 }, +        { 0x19, 0x2a }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x20 }, +        { 0x1d, 0x84 }, +        { 0x52, 0x0a }, +        { 0x53, 0x0d }, +        { 0x54, 0x0f }, +        { 0x55, 0x01 }, +        { 0x56, 0x04 }, +        { 0x57, 0x07 }, +        { 0x58, 0x31 }, +        { 0x59, 0x79 }, +        { 0x5a, 0xc0 }, +        { 0x70, 0x0c }, +        { 0x71, 0x0d }, +        { 0x72, 0x0e }, +        { 0x73, 0x0f }, +        { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 }, +        { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 }, +        { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact; +    { +        struct CustomSensorSettings +        { +            ResolutionFilter resolutions; +            ScanMethod method; +            unsigned ccd_size_divisor; +            unsigned logical_dpihw_override; +            unsigned pixel_count_multiplier; +            unsigned exposure_lperiod; +            unsigned dpiset_override; +        }; + +        CustomSensorSettings custom_settings[] = { +            { { 900 }, ScanMethod::TRANSPARENCY, 1, 900, 8, 0x2f44, 150 }, +            { { 1800 }, ScanMethod::TRANSPARENCY, 1, 1800, 4, 0x2f44, 300 }, +            { { 3600 }, ScanMethod::TRANSPARENCY, 1, 3600, 2, 0x2f44, 600 }, +            { { 7200 }, ScanMethod::TRANSPARENCY, 1, 7200, 1, 0x2f44, 1200 }, +            { { 900 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 900, 8, 0x2af8, 150 }, +            { { 1800 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 1800, 4, 0x2af8, 300 }, +            { { 3600 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 3600, 2, 0x2af8, 600 }, +            { { 7200 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 7200, 1, 0x2af8, 1200 }, +        }; + +        for (const CustomSensorSettings& setting : custom_settings) { +            sensor.resolutions = setting.resolutions; +            sensor.method = setting.method; +            sensor.ccd_size_divisor = setting.ccd_size_divisor; +            sensor.logical_dpihw_override = setting.logical_dpihw_override; +            sensor.pixel_count_multiplier = setting.pixel_count_multiplier; +            sensor.exposure_lperiod = setting.exposure_lperiod; +            sensor.dpiset_override = setting.dpiset_override; +            s_sensors->push_back(sensor); +        } +    } + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_IMG101; +    sensor.resolutions = { 75, 100, 150, 300, 600, 1200 }; +    sensor.exposure_lperiod = 11000; +    sensor.segment_size = 5136; +    sensor.segment_order = {0, 1}; +    sensor.optical_res = 1200; +    sensor.black_pixels = 31; +    sensor.dummy_pixel = 31; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10800; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0x0000, 0x0000, 0x0000 }; +    sensor.custom_regs = { +        { 0x16, 0xbb }, { 0x17, 0x13 }, { 0x18, 0x10 }, { 0x19, 0xff }, +        { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x20 }, { 0x1d, 0x06 }, +        { 0x52, 0x02 }, { 0x53, 0x04 }, { 0x54, 0x06 }, { 0x55, 0x08 }, +        { 0x56, 0x0a }, { 0x57, 0x00 }, { 0x58, 0x59 }, { 0x59, 0x31 }, { 0x5a, 0x40 }, +        { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +        { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +        { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +    }; +    sensor.gamma = { 1.7f, 1.7f, 1.7f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICBOOK_3800; +    sensor.resolutions = { 75, 100, 150, 300, 600, 1200 }; +    sensor.exposure_lperiod = 11000; +    sensor.optical_res = 1200; +    sensor.black_pixels = 31; +    sensor.dummy_pixel = 31; +    sensor.ccd_start_xoffset = 0; +    sensor.sensor_pixels = 10200; +    sensor.fau_gain_white_ref = 210; +    sensor.gain_white_ref = 200; +    sensor.exposure = { 0, 0, 0 }; +    sensor.custom_regs = { +        { 0x16, 0xbb }, { 0x17, 0x13 }, { 0x18, 0x10 }, { 0x19, 0xff }, +        { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x20 }, { 0x1d, 0x06 }, +        { 0x52, 0x02 }, { 0x53, 0x04 }, { 0x54, 0x06 }, { 0x55, 0x08 }, +        { 0x56, 0x0a }, { 0x57, 0x00 }, { 0x58, 0x59 }, { 0x59, 0x31 }, { 0x5a, 0x40 }, +        { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c }, +        { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f }, +        { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 }, +    }; +    sensor.gamma = { 1.7f, 1.7f, 1.7f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); + + +    sensor = Genesys_Sensor(); +    sensor.sensor_id = SensorId::CIS_CANON_LIDE_80; +    sensor.optical_res = 1200; // real hardware limit is 2400 +    sensor.ccd_size_divisor = 2; +    sensor.black_pixels = 20; +    sensor.dummy_pixel = 6; +    // tuned to give 3*8 multiple startx coordinate during shading calibration +    sensor.ccd_start_xoffset = 34; // 14=>3, 20=>2 +    // 10400, too wide=>10288 in shading data 10240~ +    // 10208 too short for shading, max shading data = 10240 pixels, endpix-startpix=10208 +    sensor.sensor_pixels = 10240; +    sensor.fau_gain_white_ref = 150; +    sensor.gain_white_ref = 150; +    // maps to 0x70-0x73 for GL841 +    sensor.exposure = { 0x1000, 0x1000, 0x0500 }; +    sensor.custom_regs = { +        { 0x08, 0x00 }, +        { 0x09, 0x05 }, +        { 0x0a, 0x07 }, +        { 0x0b, 0x09 }, +        { 0x16, 0x00 }, +        { 0x17, 0x01 }, +        { 0x18, 0x00 }, +        { 0x19, 0x06 }, +        { 0x1a, 0x00 }, +        { 0x1b, 0x00 }, +        { 0x1c, 0x00 }, +        { 0x1d, 0x04 }, +        { 0x52, 0x03 }, +        { 0x53, 0x07 }, +        { 0x54, 0x00 }, +        { 0x55, 0x00 }, +        { 0x56, 0x00 }, +        { 0x57, 0x00 }, +        { 0x58, 0x29 }, +        { 0x59, 0x69 }, +        { 0x5a, 0x55 }, +        { 0x5b, 0x00 }, +        { 0x5c, 0x00 }, +        { 0x5d, 0x20 }, +        { 0x5e, 0x41 }, +    }; +    sensor.gamma = { 1.0f, 1.0f, 1.0f }; +    sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_register_hwdpi_fun = default_get_logical_hwdpi; +    sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi; +    sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi; +    s_sensors->push_back(sensor); +} + +} // namespace genesys diff --git a/backend/genesys/test_scanner_interface.cpp b/backend/genesys/test_scanner_interface.cpp new file mode 100644 index 0000000..12f726f --- /dev/null +++ b/backend/genesys/test_scanner_interface.cpp @@ -0,0 +1,229 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "test_scanner_interface.h" +#include "device.h" +#include <cstring> + +namespace genesys { + +TestScannerInterface::TestScannerInterface(Genesys_Device* dev) : dev_{dev} +{ +    // initialize status registers +    if (dev_->model->asic_type == AsicType::GL124) { +        write_register(0x101, 0x00); +    } else { +        write_register(0x41, 0x00); +    } +    if (dev_->model->asic_type == AsicType::GL841 || +        dev_->model->asic_type == AsicType::GL843 || +        dev_->model->asic_type == AsicType::GL845 || +        dev_->model->asic_type == AsicType::GL846 || +        dev_->model->asic_type == AsicType::GL847) +    { +        write_register(0x40, 0x00); +    } + +    // initialize other registers that we read on init +    if (dev_->model->asic_type == AsicType::GL124) { +        write_register(0x33, 0x00); +        write_register(0xbd, 0x00); +        write_register(0xbe, 0x00); +        write_register(0x100, 0x00); +    } + +    if (dev_->model->asic_type == AsicType::GL845 || +        dev_->model->asic_type == AsicType::GL846 || +        dev_->model->asic_type == AsicType::GL847) +    { +        write_register(0xbd, 0x00); +        write_register(0xbe, 0x00); + +        write_register(0xd0, 0x00); +        write_register(0xd1, 0x01); +        write_register(0xd2, 0x02); +        write_register(0xd3, 0x03); +        write_register(0xd4, 0x04); +        write_register(0xd5, 0x05); +        write_register(0xd6, 0x06); +        write_register(0xd7, 0x07); +        write_register(0xd8, 0x08); +        write_register(0xd9, 0x09); +    } +} + +TestScannerInterface::~TestScannerInterface() = default; + +bool TestScannerInterface::is_mock() const +{ +    return true; +} + +std::uint8_t TestScannerInterface::read_register(std::uint16_t address) +{ +    return cached_regs_.get(address); +} + +void TestScannerInterface::write_register(std::uint16_t address, std::uint8_t value) +{ +    cached_regs_.update(address, value); +} + +void TestScannerInterface::write_registers(const Genesys_Register_Set& regs) +{ +    cached_regs_.update(regs); +} + + +void TestScannerInterface::write_0x8c(std::uint8_t index, std::uint8_t value) +{ +    (void) index; +    (void) value; +} + +void TestScannerInterface::bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) +{ +    (void) addr; +    std::memset(data, 0, size); +} + +void TestScannerInterface::bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) +{ +    (void) addr; +    (void) data; +    (void) size; +} + +void TestScannerInterface::write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                                        std::size_t size, Flags flags) +{ +    (void) type; +    (void) addr; +    (void) data; +    (void) size; +    (void) flags; +} + +void TestScannerInterface::write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                                       std::size_t size, Flags flags) +{ +    (void) type; +    (void) addr; +    (void) data; +    (void) size; +    (void) flags; +} + +void TestScannerInterface::write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) +{ +    (void) addr; +    (void) size; +    (void) data; +} + +std::uint16_t TestScannerInterface::read_fe_register(std::uint8_t address) +{ +    return cached_fe_regs_.get(address); +} + +void TestScannerInterface::write_fe_register(std::uint8_t address, std::uint16_t value) +{ +    cached_fe_regs_.update(address, value); +} + +IUsbDevice& TestScannerInterface::get_usb_device() +{ +    return usb_dev_; +} + +void TestScannerInterface::sleep_us(unsigned microseconds) +{ +    (void) microseconds; +} + +void TestScannerInterface::record_slope_table(unsigned table_nr, +                                              const std::vector<std::uint16_t>& steps) +{ +    slope_tables_[table_nr] = steps; +} + +std::map<unsigned, std::vector<std::uint16_t>>& TestScannerInterface::recorded_slope_tables() +{ +    return slope_tables_; +} + +void TestScannerInterface::record_progress_message(const char* msg) +{ +    last_progress_message_ = msg; +} + +const std::string& TestScannerInterface::last_progress_message() const +{ +    return last_progress_message_; +} + +void TestScannerInterface::record_key_value(const std::string& key, const std::string& value) +{ +    key_values_[key] = value; +} + +std::map<std::string, std::string>& TestScannerInterface::recorded_key_values() +{ +    return key_values_; +} + +void TestScannerInterface::test_checkpoint(const std::string& name) +{ +    if (checkpoint_callback_) { +        checkpoint_callback_(*dev_, *this, name); +    } +} + +void TestScannerInterface::set_checkpoint_callback(TestCheckpointCallback callback) +{ +    checkpoint_callback_ = callback; +} + +} // namespace genesys diff --git a/backend/genesys/test_scanner_interface.h b/backend/genesys/test_scanner_interface.h new file mode 100644 index 0000000..acf0f6d --- /dev/null +++ b/backend/genesys/test_scanner_interface.h @@ -0,0 +1,122 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_TEST_SCANNER_INTERFACE_H +#define BACKEND_GENESYS_TEST_SCANNER_INTERFACE_H + +#include "scanner_interface.h" +#include "register_cache.h" +#include "test_usb_device.h" +#include "test_settings.h" + +#include <map> + +namespace genesys { + +class TestScannerInterface : public ScannerInterface +{ +public: +    TestScannerInterface(Genesys_Device* dev); + +    ~TestScannerInterface() override; + +    bool is_mock() const override; + +    const RegisterCache<std::uint8_t>& cached_regs() const { return cached_regs_; } +    const RegisterCache<std::uint16_t>& cached_fe_regs() const { return cached_fe_regs_; } + +    std::uint8_t read_register(std::uint16_t address) override; +    void write_register(std::uint16_t address, std::uint8_t value) override; +    void write_registers(const Genesys_Register_Set& regs) override; + +    void write_0x8c(std::uint8_t index, std::uint8_t value) override; +    void bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override; +    void bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override; + +    void write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                      std::size_t size, Flags flags) override; +    void write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data, +                     std::size_t size, Flags flags) override; +    void write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) override; + +    std::uint16_t read_fe_register(std::uint8_t address) override; +    void write_fe_register(std::uint8_t address, std::uint16_t value) override; + +    IUsbDevice& get_usb_device() override; + +    void sleep_us(unsigned microseconds) override; + +    void record_progress_message(const char* msg) override; + +    const std::string& last_progress_message() const; + +    void record_slope_table(unsigned table_nr, const std::vector<std::uint16_t>& steps) override; + +    std::map<unsigned, std::vector<std::uint16_t>>& recorded_slope_tables(); + +    void record_key_value(const std::string& key, const std::string& value) override; + +    std::map<std::string, std::string>& recorded_key_values(); + +    void test_checkpoint(const std::string& name) override; + +    void set_checkpoint_callback(TestCheckpointCallback callback); + +private: +    Genesys_Device* dev_; + +    RegisterCache<std::uint8_t> cached_regs_; +    RegisterCache<std::uint16_t> cached_fe_regs_; +    TestUsbDevice usb_dev_; + +    TestCheckpointCallback checkpoint_callback_; + +    std::map<unsigned, std::vector<std::uint16_t>> slope_tables_; + +    std::string last_progress_message_; +    std::map<std::string, std::string> key_values_; +}; + +} // namespace genesys + +#endif diff --git a/backend/genesys/test_settings.cpp b/backend/genesys/test_settings.cpp new file mode 100644 index 0000000..425f09c --- /dev/null +++ b/backend/genesys/test_settings.cpp @@ -0,0 +1,106 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "test_settings.h" + +namespace genesys { + +namespace { + +bool s_testing_mode = false; +std::uint16_t s_vendor_id = 0; +std::uint16_t s_product_id = 0; +TestCheckpointCallback s_checkpoint_callback; + +} // namespace + +bool is_testing_mode() +{ +    return s_testing_mode; +} + +void disable_testing_mode() +{ +    s_testing_mode = false; +    s_vendor_id = 0; +    s_product_id = 0; + +} + +void enable_testing_mode(std::uint16_t vendor_id, std::uint16_t product_id, +                         TestCheckpointCallback checkpoint_callback) +{ +    s_testing_mode = true; +    s_vendor_id = vendor_id; +    s_product_id = product_id; +    s_checkpoint_callback = checkpoint_callback; +} + +std::uint16_t get_testing_vendor_id() +{ +    return s_vendor_id; +} + +std::uint16_t get_testing_product_id() +{ +    return s_product_id; +} + +std::string get_testing_device_name() +{ +    std::string name; +    unsigned max_size = 50; +    name.resize(max_size); +    name.resize(std::snprintf(&name.front(), max_size, "test device:0x%04x:0x%04x", +                              s_vendor_id, s_product_id)); +    return name; +} + +TestCheckpointCallback get_testing_checkpoint_callback() +{ +    return s_checkpoint_callback; +} + +} // namespace genesys diff --git a/backend/genesys/test_settings.h b/backend/genesys/test_settings.h new file mode 100644 index 0000000..8ac03e0 --- /dev/null +++ b/backend/genesys/test_settings.h @@ -0,0 +1,70 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_TEST_SETTINGS_H +#define BACKEND_GENESYS_TEST_SETTINGS_H + +#include "scanner_interface.h" +#include "register_cache.h" +#include "test_usb_device.h" +#include <functional> + +namespace genesys { + +using TestCheckpointCallback = std::function<void(const Genesys_Device&, +                                                  TestScannerInterface&, +                                                  const std::string&)>; + +bool is_testing_mode(); +void disable_testing_mode(); +void enable_testing_mode(std::uint16_t vendor_id, std::uint16_t product_id, +                         TestCheckpointCallback checkpoint_callback); +std::uint16_t get_testing_vendor_id(); +std::uint16_t get_testing_product_id(); +std::string get_testing_device_name(); +TestCheckpointCallback get_testing_checkpoint_callback(); + + +} // namespace genesys + +#endif // BACKEND_GENESYS_TEST_SETTINGS_H diff --git a/backend/genesys/test_usb_device.cpp b/backend/genesys/test_usb_device.cpp new file mode 100644 index 0000000..de2399e --- /dev/null +++ b/backend/genesys/test_usb_device.cpp @@ -0,0 +1,141 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "test_usb_device.h" +#include "low.h" + +namespace genesys { + +TestUsbDevice::TestUsbDevice(std::uint16_t vendor, std::uint16_t product) : +    vendor_{vendor}, +    product_{product} +{ +} + +TestUsbDevice::~TestUsbDevice() +{ +    if (is_open()) { +        DBG(DBG_error, "TestUsbDevice not closed; closing automatically"); +        close(); +    } +} + +void TestUsbDevice::open(const char* dev_name) +{ +    DBG_HELPER(dbg); + +    if (is_open()) { +        throw SaneException("device already open"); +    } +    name_ = dev_name; +    is_open_ = true; +} + +void TestUsbDevice::clear_halt() +{ +    DBG_HELPER(dbg); +    assert_is_open(); +} + +void TestUsbDevice::reset() +{ +    DBG_HELPER(dbg); +    assert_is_open(); +} + +void TestUsbDevice::close() +{ +    DBG_HELPER(dbg); +    assert_is_open(); + +    is_open_ = false; +    name_ = ""; +} + +void TestUsbDevice::get_vendor_product(int& vendor, int& product) +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    vendor = vendor_; +    product = product_; +} + +void TestUsbDevice::control_msg(int rtype, int reg, int value, int index, int length, +                                std::uint8_t* data) +{ +    (void) reg; +    (void) value; +    (void) index; +    DBG_HELPER(dbg); +    assert_is_open(); +    if (rtype == REQUEST_TYPE_IN) { +        std::memset(data, 0, length); +    } +} + +void TestUsbDevice::bulk_read(std::uint8_t* buffer, std::size_t* size) +{ + +    DBG_HELPER(dbg); +    assert_is_open(); +    std::memset(buffer, 0, *size); +} + +void TestUsbDevice::bulk_write(const std::uint8_t* buffer, std::size_t* size) +{ +    (void) buffer; +    (void) size; +    DBG_HELPER(dbg); +    assert_is_open(); +} + +void TestUsbDevice::assert_is_open() const +{ +    if (!is_open()) { +        throw SaneException("device not open"); +    } +} + +} // namespace genesys diff --git a/backend/genesys/test_usb_device.h b/backend/genesys/test_usb_device.h new file mode 100644 index 0000000..abbd78a --- /dev/null +++ b/backend/genesys/test_usb_device.h @@ -0,0 +1,85 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_TEST_USB_DEVICE_H +#define BACKEND_GENESYS_TEST_USB_DEVICE_H + +#include "usb_device.h" + +namespace genesys { + +class TestUsbDevice : public IUsbDevice { +public: +    TestUsbDevice(std::uint16_t vendor, std::uint16_t product); +    TestUsbDevice() = default; + +    ~TestUsbDevice() override; + +    bool is_open() const override { return is_open_; } + +    const std::string& name() const override { return name_; } + +    void open(const char* dev_name) override; + +    void clear_halt() override; +    void reset() override; +    void close() override; + +    void get_vendor_product(int& vendor, int& product) override; + +    void control_msg(int rtype, int reg, int value, int index, int length, +                     std::uint8_t* data) override; +    void bulk_read(std::uint8_t* buffer, std::size_t* size) override; +    void bulk_write(const std::uint8_t* buffer, std::size_t* size) override; +private: +    void assert_is_open() const; + +    std::string name_; +    bool is_open_ = false; +    std::uint16_t vendor_ = 0; +    std::uint16_t product_ = 0; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_TEST_USB_DEVICE_H diff --git a/backend/genesys/usb_device.cpp b/backend/genesys/usb_device.cpp new file mode 100644 index 0000000..2d02219 --- /dev/null +++ b/backend/genesys/usb_device.cpp @@ -0,0 +1,147 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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 "usb_device.h" + +namespace genesys { + +IUsbDevice::~IUsbDevice() = default; + +UsbDevice::~UsbDevice() +{ +    if (is_open()) { +        DBG(DBG_error, "UsbDevice not closed; closing automatically"); +        close(); +    } +} + +void UsbDevice::open(const char* dev_name) +{ +    DBG_HELPER(dbg); + +    if (is_open()) { +        throw SaneException("device already open"); +    } +    int device_num = 0; + +    dbg.status("open device"); +    TIE(sanei_usb_open(dev_name, &device_num)); + +    name_ = dev_name; +    device_num_ = device_num; +    is_open_ = true; +} + +void UsbDevice::clear_halt() +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    TIE(sanei_usb_clear_halt(device_num_)); +} + +void UsbDevice::reset() +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    TIE(sanei_usb_reset(device_num_)); +} + +void UsbDevice::close() +{ +    DBG_HELPER(dbg); +    assert_is_open(); + +    // we can't do much if closing fails, so we close the device on our side regardless of the +    // function succeeds +    int device_num = device_num_; + +    set_not_open(); +    sanei_usb_close(device_num); +} + +void UsbDevice::get_vendor_product(int& vendor, int& product) +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    TIE(sanei_usb_get_vendor_product(device_num_, &vendor, &product)); +} + +void UsbDevice::control_msg(int rtype, int reg, int value, int index, int length, +                            std::uint8_t* data) +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    TIE(sanei_usb_control_msg(device_num_, rtype, reg, value, index, length, data)); +} + +void UsbDevice::bulk_read(std::uint8_t* buffer, std::size_t* size) +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    TIE(sanei_usb_read_bulk(device_num_, buffer, size)); +} + +void UsbDevice::bulk_write(const std::uint8_t* buffer, std::size_t* size) +{ +    DBG_HELPER(dbg); +    assert_is_open(); +    TIE(sanei_usb_write_bulk(device_num_, buffer, size)); +} + +void UsbDevice::assert_is_open() const +{ +    if (!is_open()) { +        throw SaneException("device not open"); +    } +} + +void UsbDevice::set_not_open() +{ +    device_num_ = 0; +    is_open_ = false; +    name_ = ""; +} + +} // namespace genesys diff --git a/backend/genesys/usb_device.h b/backend/genesys/usb_device.h new file mode 100644 index 0000000..265c57c --- /dev/null +++ b/backend/genesys/usb_device.h @@ -0,0 +1,118 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_USB_DEVICE_H +#define BACKEND_GENESYS_USB_DEVICE_H + +#include "error.h" +#include "../include/sane/sanei_usb.h" + +#include <cstdint> +#include <string> + +namespace genesys { + +class IUsbDevice { +public: +    IUsbDevice() = default; + +    IUsbDevice(const IUsbDevice& other) = delete; +    IUsbDevice& operator=(const IUsbDevice&) = delete; + +    virtual ~IUsbDevice(); + +    virtual bool is_open() const = 0; + +    virtual const std::string& name() const = 0; + +    virtual void open(const char* dev_name) = 0; + +    virtual void clear_halt() = 0; +    virtual void reset() = 0; +    virtual void close() = 0; + +    virtual void get_vendor_product(int& vendor, int& product) = 0; + +    virtual void control_msg(int rtype, int reg, int value, int index, int length, +                             std::uint8_t* data) = 0; +    virtual void bulk_read(std::uint8_t* buffer, std::size_t* size) = 0; +    virtual void bulk_write(const std::uint8_t* buffer, std::size_t* size) = 0; + +}; + +class UsbDevice : public IUsbDevice { +public: +    UsbDevice() = default; + +    ~UsbDevice() override; + +    bool is_open() const override { return is_open_; } + +    const std::string& name() const override { return name_; } + +    void open(const char* dev_name) override; + +    void clear_halt() override; +    void reset() override; +    void close() override; + +    void get_vendor_product(int& vendor, int& product) override; + +    void control_msg(int rtype, int reg, int value, int index, int length, +                     std::uint8_t* data) override; +    void bulk_read(std::uint8_t* buffer, std::size_t* size) override; +    void bulk_write(const std::uint8_t* buffer, std::size_t* size) override; + +private: + +    void assert_is_open() const; +    void set_not_open(); + +    std::string name_; +    bool is_open_ = false; +    int device_num_ = 0; +}; + +} // namespace genesys + +#endif // BACKEND_GENESYS_USB_DEVICE_H diff --git a/backend/genesys/utilities.h b/backend/genesys/utilities.h new file mode 100644 index 0000000..1e268b5 --- /dev/null +++ b/backend/genesys/utilities.h @@ -0,0 +1,180 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> + +   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. +*/ + +#ifndef BACKEND_GENESYS_UTILITIES_H +#define BACKEND_GENESYS_UTILITIES_H + +#include "error.h" +#include <algorithm> +#include <iostream> +#include <sstream> +#include <vector> + +namespace genesys { + +template<class T> +void compute_array_percentile_approx(T* result, const T* data, +                                     std::size_t line_count, std::size_t elements_per_line, +                                     float percentile) +{ +    if (line_count == 0) { +        throw SaneException("invalid line count"); +    } + +    if (line_count == 1) { +        std::copy(data, data + elements_per_line, result); +        return; +    } + +    std::vector<T> column_elems; +    column_elems.resize(line_count, 0); + +    std::size_t select_elem = std::min(static_cast<std::size_t>(line_count * percentile), +                                       line_count - 1); + +    auto select_it = column_elems.begin() + select_elem; + +    for (std::size_t ix = 0; ix < elements_per_line; ++ix) { +        for (std::size_t iy = 0; iy < line_count; ++iy) { +            column_elems[iy] = data[iy * elements_per_line + ix]; +        } + +        std::nth_element(column_elems.begin(), select_it, column_elems.end()); + +        *result++ = *select_it; +    } +} + +template<class Char, class Traits> +class BasicStreamStateSaver +{ +public: +    explicit BasicStreamStateSaver(std::basic_ios<Char, Traits>& stream) : +        stream_{stream} +    { +        flags_ = stream_.flags(); +        width_ = stream_.width(); +        precision_ = stream_.precision(); +        fill_ = stream_.fill(); +    } + +    ~BasicStreamStateSaver() +    { +        stream_.flags(flags_); +        stream_.width(width_); +        stream_.precision(precision_); +        stream_.fill(fill_); +    } + +    BasicStreamStateSaver(const BasicStreamStateSaver&) = delete; +    BasicStreamStateSaver& operator=(const BasicStreamStateSaver&) = delete; + +private: +    std::basic_ios<Char, Traits>& stream_; +    std::ios_base::fmtflags flags_; +    std::streamsize width_ = 0; +    std::streamsize precision_ = 0; +    Char fill_ = ' '; +}; + +using StreamStateSaver = BasicStreamStateSaver<char, std::char_traits<char>>; + +template<class T> +std::string format_indent_braced_list(unsigned indent, const T& x) +{ +    std::string indent_str(indent, ' '); +    std::ostringstream out; +    out << x; +    auto formatted_str = out.str(); +    if (formatted_str.empty()) { +        return formatted_str; +    } + +    std::string out_str; +    for (std::size_t i = 0; i < formatted_str.size(); ++i) { +        out_str += formatted_str[i]; + +        if (formatted_str[i] == '\n' && +            i < formatted_str.size() - 1 && +            formatted_str[i + 1] != '\n') +        { +            out_str += indent_str; +        } +    } +    return out_str; +} + +template<class T> +std::string format_vector_unsigned(unsigned indent, const std::vector<T>& arg) +{ +    std::ostringstream out; +    std::string indent_str(indent, ' '); + +    out << "std::vector<T>{ "; +    for (const auto& el : arg) { +        out << indent_str << static_cast<unsigned>(el) << "\n"; +    } +    out << "}"; +    return out.str(); +} + +template<class T> +std::string format_vector_indent_braced(unsigned indent, const char* type, +                                        const std::vector<T>& arg) +{ +    if (arg.empty()) { +        return "{}"; +    } +    std::string indent_str(indent, ' '); +    std::stringstream out; +    out << "std::vector<" << type << ">{\n"; +    for (const auto& item : arg) { +        out << indent_str << format_indent_braced_list(indent, item) << '\n'; +    } +    out << "}"; +    return out.str(); +} + +} // namespace genesys + +#endif // BACKEND_GENESYS_UTILITIES_H  | 
