/* sane - Scanner Access Now Easy. Copyright (C) 2003 Oliver Rauch Copyright (C) 2003, 2004 Henning Meier-Geinitz Copyright (C) 2004 Gerhard Jaeger Copyright (C) 2004-2013 Stéphane Voltz Copyright (C) 2005-2009 Pierre Willenbrock Copyright (C) 2007 Luke Copyright (C) 2011 Alexey Osipov 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 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& 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(res) - static_cast(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(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(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& 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 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(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(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((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( (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 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(dev->model->y_offset); } // add tl_y to base movement } move += static_cast(settings.tl_y); if (move < 0) { DBG(DBG_error, "%s: overriding negative move value %f\n", __func__, move); move = 0; } } move = static_cast((move * dev->motor.optical_ydpi) / MM_PER_INCH); DBG(DBG_info, "%s: move=%f steps\n", __func__, move); float start = static_cast(settings.tl_x); if (xcorrection) { if (settings.scan_method == ScanMethod::FLATBED) { start += static_cast(dev->model->x_offset); } else { start += static_cast(dev->model->x_offset_ta); } } start = static_cast((start * sensor.optical_res) / MM_PER_INCH); ScanSession session; session.params.xres = settings.xres; session.params.yres = settings.yres; session.params.startx = static_cast(start); session.params.starty = static_cast(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 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 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 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(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 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 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((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 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(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(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& 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 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((distance * settings.xres) / MM_PER_INCH); settings.depth = 8; settings.color_filter = ColorFilter::RED; settings.disable_interpolation = 0; settings.threshold = 0; std::vector 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((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((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 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(dev->model->y_offset); // add tl_y to base movement } move += static_cast(settings.tl_y); if (move < 0) { DBG(DBG_error, "%s: overriding negative move value %f\n", __func__, move); move = 0; } move = static_cast((move * dev->motor.optical_ydpi) / MM_PER_INCH); float start = static_cast(settings.tl_x); if (settings.scan_method == ScanMethod::FLATBED) { start += static_cast(dev->model->x_offset); } else { start += static_cast(dev->model->x_offset_ta); } start = static_cast((start * sensor.optical_res) / MM_PER_INCH); ScanSession session; session.params.xres = settings.xres; session.params.yres = settings.yres; session.params.startx = static_cast(start); session.params.starty = static_cast(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 create_gl646_cmd_set() { return std::unique_ptr(new CommandSetGl646{}); } } // namespace gl646 } // namespace genesys