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 |