/* sane - Scanner Access Now Easy. Copyright (C) 2010-2013 Stéphane Voltz 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 #include #include /* ------------------------------------------------------------------------ */ /* functions calling ASIC specific functions */ /* ------------------------------------------------------------------------ */ namespace genesys { /** * setup the hardware dependent functions */ namespace gl124 { std::unique_ptr create_gl124_cmd_set(); } namespace gl646 { std::unique_ptr create_gl646_cmd_set(); } namespace gl841 { std::unique_ptr create_gl841_cmd_set(); } namespace gl843 { std::unique_ptr create_gl843_cmd_set(); } namespace gl846 { std::unique_ptr create_gl846_cmd_set(); } namespace gl847 { std::unique_ptr 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(std::pow(static_cast(2), static_cast(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(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 */ 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(image); if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && session.params.depth == 16) { dev->pipeline.push_node(); } #ifdef WORDS_BIGENDIAN if (depth == 16) { dev->pipeline.push_node(); } #endif if (dev->model->is_cis && session.params.channels == 3) { dev->pipeline.push_node(dev->model->line_mode_color_order); } if (dev->pipeline.get_output_format() == PixelFormat::BGR888) { dev->pipeline.push_node(PixelFormat::RGB888); } if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) { dev->pipeline.push_node(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 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 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 rgamma = get_gamma_table(dev, sensor, GENESYS_RED); std::vector ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN); std::vector bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE); if(dev->settings.contrast!=0 || dev->settings.brightness!=0) { std::vector lut(65536); sanei_genesys_load_lut(reinterpret_cast(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 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 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( 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(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( 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("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(); } #ifdef WORDS_BIGENDIAN if (depth == 16) { dev->pipeline.push_node(); } #endif if (DBG_LEVEL >= DBG_io2) { dev->pipeline.push_node("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(dev->model->line_mode_color_order); } if (dev->pipeline.get_output_format() == PixelFormat::BGR888) { dev->pipeline.push_node(PixelFormat::RGB888); } if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) { dev->pipeline.push_node(PixelFormat::RGB161616); } if (session.max_color_shift_lines > 0 && session.params.channels == 3) { dev->pipeline.push_node( session.color_shift_lines_r, session.color_shift_lines_g, session.color_shift_lines_b); } if (DBG_LEVEL >= DBG_io2) { dev->pipeline.push_node("gl_pipeline_" + std::to_string(s_pipeline_index) + "_2_after_shift.pnm"); } if (session.num_staggered_lines > 0) { std::vector shifts{0, session.num_staggered_lines}; dev->pipeline.push_node(shifts); } if (DBG_LEVEL >= DBG_io2) { dev->pipeline.push_node("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(dev->dark_average_data, dev->white_average_data); if (DBG_LEVEL >= DBG_io2) { dev->pipeline.push_node("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(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(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((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(regs.get8(gl646::REG_0x06) & gl646::REG_0x06_GAIN4); case AsicType::GL841: return static_cast(regs.get8(gl841::REG_0x06) & gl841::REG_0x06_GAIN4); case AsicType::GL843: return static_cast(regs.get8(gl843::REG_0x06) & gl843::REG_0x06_GAIN4); case AsicType::GL845: case AsicType::GL846: return static_cast(regs.get8(gl846::REG_0x06) & gl846::REG_0x06_GAIN4); case AsicType::GL847: return static_cast(regs.get8(gl847::REG_0x06) & gl847::REG_0x06_GAIN4); case AsicType::GL124: return static_cast(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& 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].exposuremodel->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(dev_params.scan_method), static_cast(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(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(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(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(offset) / 127 * max_out_val / 2; for (i = 0; i <= max_in_val; i++) { j = static_cast(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