sane-project-backends/backend/genesys_low.cc

1899 wiersze
59 KiB
C++

/* sane - Scanner Access Now Easy.
Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
This file is part of the SANE package.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.
As a special exception, the authors of SANE give permission for
additional uses of the libraries contained in this release of SANE.
The exception is that, if you link a SANE library with other files
to produce an executable, this does not by itself cause the
resulting executable to be covered by the GNU General Public
License. Your use of that executable is in no way restricted on
account of linking the SANE library code into it.
This exception does not, however, invalidate any other reasons why
the executable file might be covered by the GNU General Public
License.
If you submit changes to SANE to the maintainers to be included in
a subsequent release, you agree by submitting the changes that
those changes may be distributed with this exception intact.
If you write modifications of your own for SANE, it is your choice
whether to permit this exception to apply to your modifications.
If you do not wish that, delete this exception notice.
*/
#define DEBUG_DECLARE_ONLY
#include "genesys_low.h"
#include "assert.h"
#include <vector>
/* ------------------------------------------------------------------------ */
/* functions calling ASIC specific functions */
/* ------------------------------------------------------------------------ */
/**
* setup the hardware dependent functions
*/
extern Genesys_Command_Set gl124_cmd_set;
extern Genesys_Command_Set gl646_cmd_set;
extern Genesys_Command_Set gl841_cmd_set;
extern Genesys_Command_Set gl843_cmd_set;
extern Genesys_Command_Set gl846_cmd_set;
extern Genesys_Command_Set gl847_cmd_set;
void sanei_genesys_init_cmd_set(Genesys_Device* dev)
{
DBG_INIT ();
DBG_HELPER(dbg);
switch (dev->model->asic_type) {
case AsicType::GL646: dev->cmd_set = &gl646_cmd_set; break;
case AsicType::GL841: dev->cmd_set = &gl841_cmd_set; break;
case AsicType::GL843: dev->cmd_set = &gl843_cmd_set; break;
case AsicType::GL845: // since only a few reg bits differs we handle both together
case AsicType::GL846: dev->cmd_set = &gl846_cmd_set; break;
case AsicType::GL847: dev->cmd_set = &gl847_cmd_set; break;
case AsicType::GL124: dev->cmd_set = &gl124_cmd_set; break;
default: throw SaneException(SANE_STATUS_INVAL, "unknown ASIC type");
}
}
/* ------------------------------------------------------------------------ */
/* General IO and debugging functions */
/* ------------------------------------------------------------------------ */
void sanei_genesys_write_file(const char* filename, uint8_t* data, size_t length)
{
DBG_HELPER(dbg);
FILE *out;
out = fopen (filename, "w");
if (!out) {
throw SaneException("could not open %s for writing: %s", filename, strerror(errno));
}
fwrite(data, 1, length, out);
fclose(out);
}
// Write data to a pnm file (e.g. calibration). For debugging only
// data is RGB or grey, with little endian byte order
void sanei_genesys_write_pnm_file(const char* filename, uint8_t* data, int depth, int channels,
int pixels_per_line, int lines)
{
DBG_HELPER_ARGS(dbg, "depth=%d, channels=%d, ppl=%d, lines=%d", depth, channels,
pixels_per_line, lines);
FILE *out;
int count;
out = fopen (filename, "w");
if (!out)
{
throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno));
}
if(depth==1)
{
fprintf (out, "P4\n%d\n%d\n", pixels_per_line, lines);
}
else
{
fprintf (out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6',
pixels_per_line, lines, (int) pow (2, depth) - 1);
}
if (channels == 3)
{
for (count = 0; count < (pixels_per_line * lines * 3); count++)
{
if (depth == 16)
fputc (*(data + 1), out);
fputc (*(data++), out);
if (depth == 16)
data++;
}
}
else
{
if (depth==1)
{
pixels_per_line/=8;
}
for (count = 0; count < (pixels_per_line * lines); count++)
{
switch (depth)
{
case 8:
fputc (*(data + count), out);
break;
case 16:
fputc (*(data + 1), out);
fputc (*(data), out);
data += 2;
break;
default:
fputc(data[count], out);
break;
}
}
}
fclose (out);
}
void sanei_genesys_write_pnm_file16(const char* filename, uint16_t* data, unsigned channels,
unsigned pixels_per_line, unsigned lines)
{
DBG_HELPER_ARGS(dbg, "channels=%d, ppl=%d, lines=%d", channels,
pixels_per_line, lines);
FILE* out = std::fopen(filename, "w");
if (!out) {
throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno));
}
std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6',
pixels_per_line, lines, (int) pow (2, 16) - 1);
for (unsigned count = 0; count < (pixels_per_line * lines * channels); count++) {
fputc(*data >> 8, out);
fputc(*data & 0xff, out);
data++;
}
std::fclose(out);
}
/* ------------------------------------------------------------------------ */
/* Read and write RAM, registers and AFE */
/* ------------------------------------------------------------------------ */
extern unsigned sanei_genesys_get_bulk_max_size(Genesys_Device * dev)
{
/* Genesys supports 0xFE00 maximum size in general, wheraus GL646 supports
0xFFC0. We use 0xF000 because that's the packet limit in the Linux usbmon
USB capture stack. By default it limits packet size to b_size / 5 where
b_size is the size of the ring buffer. By default it's 300*1024, so the
packet is limited 61440 without any visibility to acquiring software.
*/
if (dev->model->asic_type == AsicType::GL124 ||
dev->model->asic_type == AsicType::GL846 ||
dev->model->asic_type == AsicType::GL847)
{
return 0xeff0;
}
return 0xf000;
}
void sanei_genesys_bulk_read_data_send_header(Genesys_Device* dev, size_t len)
{
DBG_HELPER(dbg);
uint8_t outdata[8];
if (dev->model->asic_type == AsicType::GL124 ||
dev->model->asic_type == AsicType::GL846 ||
dev->model->asic_type == AsicType::GL847)
{
// hard coded 0x10000000 address
outdata[0] = 0;
outdata[1] = 0;
outdata[2] = 0;
outdata[3] = 0x10;
} else if (dev->model->asic_type == AsicType::GL841 ||
dev->model->asic_type == AsicType::GL843) {
outdata[0] = BULK_IN;
outdata[1] = BULK_RAM;
outdata[2] = 0x82; //
outdata[3] = 0x00;
} else {
outdata[0] = BULK_IN;
outdata[1] = BULK_RAM;
outdata[2] = 0x00;
outdata[3] = 0x00;
}
/* data size to transfer */
outdata[4] = (len & 0xff);
outdata[5] = ((len >> 8) & 0xff);
outdata[6] = ((len >> 16) & 0xff);
outdata[7] = ((len >> 24) & 0xff);
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x00,
sizeof(outdata), outdata);
}
void sanei_genesys_bulk_read_data(Genesys_Device * dev, uint8_t addr, uint8_t* data,
size_t len)
{
DBG_HELPER(dbg);
// currently supported: GL646, GL841, GL843, GL846, GL847, GL124
size_t size, target;
uint8_t *buffer;
unsigned is_addr_used = 1;
unsigned has_header_before_each_chunk = 0;
if (dev->model->asic_type == AsicType::GL124 ||
dev->model->asic_type == AsicType::GL846 ||
dev->model->asic_type == AsicType::GL847)
{
is_addr_used = 0;
has_header_before_each_chunk = 1;
}
if (is_addr_used) {
DBG(DBG_io, "%s: requesting %lu bytes from 0x%02x addr\n", __func__, (u_long) len, addr);
} else {
DBG(DBG_io, "%s: requesting %lu bytes\n", __func__, (u_long) len);
}
if (len == 0)
return;
if (is_addr_used) {
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, 0x00,
1, &addr);
}
target = len;
buffer = data;
size_t max_in_size = sanei_genesys_get_bulk_max_size(dev);
if (!has_header_before_each_chunk) {
sanei_genesys_bulk_read_data_send_header(dev, len);
}
// loop until computed data size is read
while (target) {
if (target > max_in_size) {
size = max_in_size;
} else {
size = target;
}
if (has_header_before_each_chunk) {
sanei_genesys_bulk_read_data_send_header(dev, size);
}
DBG(DBG_io2, "%s: trying to read %lu bytes of data\n", __func__, (u_long) size);
dev->usb_dev.bulk_read(data, &size);
DBG(DBG_io2, "%s: read %lu bytes, %lu remaining\n", __func__,
(u_long) size, (u_long) (target - size));
target -= size;
data += size;
}
if (DBG_LEVEL >= DBG_data && dev->binary!=NULL) {
fwrite(buffer, len, 1, dev->binary);
}
}
void sanei_genesys_bulk_write_data(Genesys_Device* dev, uint8_t addr, uint8_t* data, size_t len)
{
DBG_HELPER_ARGS(dbg, "writing %lu bytes", (u_long) len);
// supported: GL646, GL841, GL843
size_t size;
uint8_t outdata[8];
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX,
1, &addr);
size_t max_out_size = sanei_genesys_get_bulk_max_size(dev);
while (len) {
if (len > max_out_size)
size = max_out_size;
else
size = len;
if (dev->model->asic_type == AsicType::GL841) {
outdata[0] = BULK_OUT;
outdata[1] = BULK_RAM;
// both 0x82 and 0x00 works on GL841.
outdata[2] = 0x82;
outdata[3] = 0x00;
} else {
outdata[0] = BULK_OUT;
outdata[1] = BULK_RAM;
// 8600F uses 0x82, but 0x00 works too. 8400F uses 0x02 for certain transactions.
outdata[2] = 0x00;
outdata[3] = 0x00;
}
outdata[4] = (size & 0xff);
outdata[5] = ((size >> 8) & 0xff);
outdata[6] = ((size >> 16) & 0xff);
outdata[7] = ((size >> 24) & 0xff);
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x00,
sizeof(outdata), outdata);
dev->usb_dev.bulk_write(data, &size);
DBG(DBG_io2, "%s: wrote %lu bytes, %lu remaining\n", __func__, (u_long) size,
(u_long) (len - size));
len -= size;
data += size;
}
}
/** @brief write to one high (addr >= 0x100) register
* write to a register which address is higher than 0xff.
* @param dev opened device to write to
* @param reg LSB of register address
* @param val value to write
*/
static void sanei_genesys_write_hregister(Genesys_Device* dev, uint16_t reg, uint8_t val)
{
DBG_HELPER(dbg);
uint8_t buffer[2];
buffer[0]=reg & 0xff;
buffer[1]=val;
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, 0x100 | VALUE_SET_REGISTER, INDEX,
2, buffer);
DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, reg, val);
}
/** @brief read from one high (addr >= 0x100) register
* Read to a register which address is higher than 0xff. Second byte is check to detect
* physical link errors.
* @param dev opened device to read from
* @param reg LSB of register address
* @param val value to write
*/
static void sanei_genesys_read_hregister(Genesys_Device* dev, uint16_t reg, uint8_t* val)
{
DBG_HELPER(dbg);
SANE_Byte value[2];
dev->usb_dev.control_msg(REQUEST_TYPE_IN, REQUEST_BUFFER, 0x100 | VALUE_GET_REGISTER,
0x22+((reg & 0xff)<<8), 2, value);
*val=value[0];
DBG(DBG_io2, "%s(0x%02x)=0x%02x\n", __func__, reg, *val);
/* check usb link status */
if ((value[1] & 0xff) != 0x55) {
throw SaneException(SANE_STATUS_IO_ERROR, "invalid read, scanner unplugged");
}
}
/**
* Write to one GL847 ASIC register
URB 10 control 0x40 0x04 0x83 0x00 len 2 wrote 0xa6 0x04
*/
static void sanei_genesys_write_gl847_register(Genesys_Device* dev, uint8_t reg, uint8_t val)
{
DBG_HELPER(dbg);
uint8_t buffer[2];
buffer[0]=reg;
buffer[1]=val;
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_SET_REGISTER, INDEX,
2, buffer);
DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, reg, val);
}
/**
* Write to one ASIC register
*/
void sanei_genesys_write_register(Genesys_Device* dev, uint16_t reg, uint8_t val)
{
DBG_HELPER(dbg);
SANE_Byte reg8;
// 16 bit register address space
if (reg > 255) {
sanei_genesys_write_hregister(dev, reg, val);
return;
}
// route to gl847 function if needed
if (dev->model->asic_type == AsicType::GL847 ||
dev->model->asic_type == AsicType::GL845 ||
dev->model->asic_type == AsicType::GL846 ||
dev->model->asic_type == AsicType::GL124)
{
sanei_genesys_write_gl847_register(dev, reg, val);
return;
}
reg8=reg & 0xff;
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX,
1, &reg8);
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_WRITE_REGISTER, INDEX,
1, &val);
DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, reg, val);
return;
}
/**
* @brief write command to 0x8c endpoint
* Write a value to 0x8c end point (end access), for USB firmware related operations
* Known values are 0x0f, 0x11 for USB 2.0 data transfer and 0x0f,0x14 for USB1.1
* @param dev device to write to
* @param index index of the command
* @param val value to write
*/
void sanei_genesys_write_0x8c(Genesys_Device* dev, uint8_t index, uint8_t val)
{
DBG_HELPER_ARGS(dbg, "0x%02x,0x%02x", index, val);
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_BUF_ENDACCESS, index, 1,
&val);
}
/* read reg 0x41:
* URB 164 control 0xc0 0x04 0x8e 0x4122 len 2 read 0xfc 0x55
*/
static void sanei_genesys_read_gl847_register(Genesys_Device* dev, uint16_t reg, uint8_t* val)
{
DBG_HELPER(dbg);
SANE_Byte value[2];
dev->usb_dev.control_msg(REQUEST_TYPE_IN, REQUEST_BUFFER, VALUE_GET_REGISTER, 0x22+(reg<<8),
2, value);
*val=value[0];
DBG(DBG_io2, "%s(0x%02x)=0x%02x\n", __func__, reg, *val);
/* check usb link status */
if((value[1] & 0xff) != 0x55)
{
throw SaneException(SANE_STATUS_IO_ERROR, "invalid read, scanner unplugged?");
}
}
// Read from one register
void sanei_genesys_read_register(Genesys_Device* dev, uint16_t reg, uint8_t* val)
{
DBG_HELPER(dbg);
SANE_Byte reg8;
// 16 bit register address space
if (reg > 255) {
sanei_genesys_read_hregister(dev, reg, val);
return;
}
// route to gl847 function if needed
if (dev->model->asic_type == AsicType::GL847 ||
dev->model->asic_type == AsicType::GL845 ||
dev->model->asic_type == AsicType::GL846 ||
dev->model->asic_type == AsicType::GL124)
{
sanei_genesys_read_gl847_register(dev, reg, val);
return;
}
/* 8 bit register address space */
reg8=(SANE_Byte)(reg& 0Xff);
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX,
1, &reg8);
*val = 0;
dev->usb_dev.control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, VALUE_READ_REGISTER, INDEX,
1, val);
DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, reg, *val);
}
// Set address for writing data
void sanei_genesys_set_buffer_address(Genesys_Device* dev, uint32_t addr)
{
DBG_HELPER(dbg);
if (dev->model->asic_type==AsicType::GL847 ||
dev->model->asic_type==AsicType::GL845 ||
dev->model->asic_type==AsicType::GL846 ||
dev->model->asic_type==AsicType::GL124)
{
DBG(DBG_warn, "%s: shouldn't be used for GL846+ ASICs\n", __func__);
return;
}
DBG(DBG_io, "%s: setting address to 0x%05x\n", __func__, addr & 0xfffffff0);
addr = addr >> 4;
dev->write_register(0x2b, (addr & 0xff));
addr = addr >> 8;
dev->write_register(0x2a, (addr & 0xff));
}
/**@brief read data from analog frontend (AFE)
* @param dev device owning the AFE
* @param addr register address to read
* @param data placeholder for the result
*/
void sanei_genesys_fe_read_data (Genesys_Device* dev, uint8_t addr, uint16_t* data)
{
DBG_HELPER(dbg);
Genesys_Register_Set reg;
reg.init_reg(0x50, addr);
// set up read address
dev->write_registers(reg);
// read data
uint8_t value = dev->read_register(0x46);
*data = 256 * value;
value = dev->read_register(0x47);
*data += value;
DBG(DBG_io, "%s (0x%02x, 0x%04x)\n", __func__, addr, *data);
}
/*@brief write data to analog frontend
* writes data to analog frontend to set it up accordingly
* to the sensor settings (exposure, timings, color, bit depth, ...)
* @param dev devie owning the AFE to write to
* @param addr AFE rister address
* @param data value to write to AFE register
**/
void sanei_genesys_fe_write_data(Genesys_Device* dev, uint8_t addr, uint16_t data)
{
DBG_HELPER_ARGS(dbg, "0x%02x, 0x%04x", addr, data);
Genesys_Register_Set reg(Genesys_Register_Set::SEQUENTIAL);
reg.init_reg(0x51, addr);
if (dev->model->asic_type == AsicType::GL124) {
reg.init_reg(0x5d, (data / 256) & 0xff);
reg.init_reg(0x5e, data & 0xff);
} else {
reg.init_reg(0x3a, (data / 256) & 0xff);
reg.init_reg(0x3b, data & 0xff);
}
dev->write_registers(reg);
}
/* ------------------------------------------------------------------------ */
/* Medium level functions */
/* ------------------------------------------------------------------------ */
/** read the status register
*/
void sanei_genesys_get_status(Genesys_Device* dev, uint8_t* status)
{
DBG_HELPER(dbg);
if (dev->model->asic_type == AsicType::GL124) {
sanei_genesys_read_hregister(dev, 0x101, status);
return;
}
*status = dev->read_register(0x41);
}
/**
* decodes and prints content of status register
* @param val value read from status register
*/
void sanei_genesys_print_status (uint8_t val)
{
char msg[80];
sprintf (msg, "%s%s%s%s%s%s%s%s",
val & PWRBIT ? "PWRBIT " : "",
val & BUFEMPTY ? "BUFEMPTY " : "",
val & FEEDFSH ? "FEEDFSH " : "",
val & SCANFSH ? "SCANFSH " : "",
val & HOMESNR ? "HOMESNR " : "",
val & LAMPSTS ? "LAMPSTS " : "",
val & FEBUSY ? "FEBUSY " : "",
val & MOTORENB ? "MOTORENB" : "");
DBG(DBG_info, "status=%s\n", msg);
}
#if 0
/* returns pixels per line from register set */
/*candidate for moving into chip specific files?*/
static int
genesys_pixels_per_line (Genesys_Register_Set * reg)
{
int pixels_per_line;
pixels_per_line = reg->get8(0x32) * 256 + reg->get8(0x33);
pixels_per_line -= (reg->get8(0x30) * 256 + reg->get8(0x31));
return pixels_per_line;
}
/* returns dpiset from register set */
/*candidate for moving into chip specific files?*/
static int
genesys_dpiset (Genesys_Register_Set * reg)
{
return reg->get8(0x2c) * 256 + reg->get8(0x2d);
}
#endif
/** read the number of valid words in scanner's RAM
* ie registers 42-43-44
*/
// candidate for moving into chip specific files?
void sanei_genesys_read_valid_words(Genesys_Device* dev, unsigned int* words)
{
DBG_HELPER(dbg);
switch (dev->model->asic_type)
{
case AsicType::GL124:
*words = dev->read_register(0x102) & 0x03;
*words = *words * 256 + dev->read_register(0x103);
*words = *words * 256 + dev->read_register(0x104);
*words = *words * 256 + dev->read_register(0x105);
break;
case AsicType::GL845:
case AsicType::GL846:
*words = dev->read_register(0x42) & 0x02;
*words = *words * 256 + dev->read_register(0x43);
*words = *words * 256 + dev->read_register(0x44);
*words = *words * 256 + dev->read_register(0x45);
break;
case AsicType::GL847:
*words = dev->read_register(0x42) & 0x03;
*words = *words * 256 + dev->read_register(0x43);
*words = *words * 256 + dev->read_register(0x44);
*words = *words * 256 + dev->read_register(0x45);
break;
default:
*words = dev->read_register(0x44);
*words += dev->read_register(0x43) * 256;
if (dev->model->asic_type == AsicType::GL646) {
*words += ((dev->read_register(0x42) & 0x03) * 256 * 256);
} else {
*words += ((dev->read_register(0x42) & 0x0f) * 256 * 256);
}
}
DBG(DBG_proc, "%s: %d words\n", __func__, *words);
}
/** read the number of lines scanned
* ie registers 4b-4c-4d
*/
void sanei_genesys_read_scancnt(Genesys_Device* dev, unsigned int* words)
{
DBG_HELPER(dbg);
if (dev->model->asic_type == AsicType::GL124) {
*words = (dev->read_register(0x10b) & 0x0f) << 16;
*words += (dev->read_register(0x10c) << 8);
*words += dev->read_register(0x10d);
}
else
{
*words = dev->read_register(0x4d);
*words += dev->read_register(0x4c) * 256;
if (dev->model->asic_type == AsicType::GL646) {
*words += ((dev->read_register(0x4b) & 0x03) * 256 * 256);
} else {
*words += ((dev->read_register(0x4b) & 0x0f) * 256 * 256);
}
}
DBG(DBG_proc, "%s: %d lines\n", __func__, *words);
}
/** @brief Check if the scanner's internal data buffer is empty
* @param *dev device to test for data
* @param *empty return value
* @return empty will be set to SANE_TRUE if there is no scanned data.
**/
void sanei_genesys_test_buffer_empty(Genesys_Device* dev, SANE_Bool* empty)
{
DBG_HELPER(dbg);
uint8_t val = 0;
sanei_genesys_sleep_ms(1);
sanei_genesys_get_status(dev, &val);
if (dev->cmd_set->test_buffer_empty_bit(val)) {
/* fix timing issue on USB3 (or just may be too fast) hardware
* spotted by John S. Weber <jweber53@gmail.com>
*/
sanei_genesys_sleep_ms(1);
DBG(DBG_io2, "%s: buffer is empty\n", __func__);
*empty = SANE_TRUE;
return;
}
*empty = SANE_FALSE;
DBG(DBG_io, "%s: buffer is filled\n", __func__);
}
// Read data (e.g scanned image) from scan buffer
void sanei_genesys_read_data_from_scanner(Genesys_Device* dev, uint8_t* data, size_t size)
{
DBG_HELPER_ARGS(dbg, "size = %lu bytes", (u_long) size);
int time_count = 0;
unsigned int words = 0;
if (size & 1)
DBG(DBG_info, "WARNING %s: odd number of bytes\n", __func__);
// wait until buffer not empty for up to 5 seconds
do {
sanei_genesys_read_valid_words (dev, &words);
if (words == 0)
{
sanei_genesys_sleep_ms(10);
time_count++;
}
}
while ((time_count < 2500*2) && (words == 0));
if (words == 0) /* timeout, buffer does not get filled */
{
throw SaneException(SANE_STATUS_IO_ERROR, "timeout, buffer does not get filled");
}
dev->cmd_set->bulk_read_data(dev, 0x45, data, size);
}
void sanei_genesys_read_feed_steps(Genesys_Device* dev, unsigned int* steps)
{
DBG_HELPER(dbg);
if (dev->model->asic_type == AsicType::GL124) {
*steps = (dev->read_register(0x108) & 0x1f) << 16;
*steps += (dev->read_register(0x109) << 8);
*steps += dev->read_register(0x10a);
}
else
{
*steps = dev->read_register(0x4a);
*steps += dev->read_register(0x49) * 256;
if (dev->model->asic_type == AsicType::GL646) {
*steps += ((dev->read_register(0x48) & 0x03) * 256 * 256);
} else if (dev->model->asic_type == AsicType::GL841) {
*steps += ((dev->read_register(0x48) & 0x0f) * 256 * 256);
} else {
*steps += ((dev->read_register(0x48) & 0x1f) * 256 * 256);
}
}
DBG(DBG_proc, "%s: %d steps\n", __func__, *steps);
}
void sanei_genesys_set_lamp_power(Genesys_Device* dev, const Genesys_Sensor& sensor,
Genesys_Register_Set& regs, bool set)
{
static const uint8_t REG03_LAMPPWR = 0x10;
if (set) {
regs.find_reg(0x03).value |= REG03_LAMPPWR;
if (dev->model->asic_type == AsicType::GL841) {
sanei_genesys_set_exposure(regs, sanei_genesys_fixup_exposure(sensor.exposure));
regs.set8(0x19, 0x50);
}
if (dev->model->asic_type == AsicType::GL843) {
sanei_genesys_set_exposure(regs, sensor.exposure);
// we don't actually turn on lamp on infrared scan
if ((dev->model->model_id == MODEL_CANON_CANOSCAN_8400F ||
dev->model->model_id == MODEL_CANON_CANOSCAN_8600F) &&
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
{
regs.find_reg(0x03).value &= ~REG03_LAMPPWR;
}
}
} else {
regs.find_reg(0x03).value &= ~REG03_LAMPPWR;
if (dev->model->asic_type == AsicType::GL841) {
sanei_genesys_set_exposure(regs, {0x0101, 0x0101, 0x0101});
regs.set8(0x19, 0xff);
}
if (dev->model->asic_type == AsicType::GL843) {
if (dev->model->model_id == MODEL_PANASONIC_KV_SS080 ||
dev->model->model_id == MODEL_HP_SCANJET_4850C ||
dev->model->model_id == MODEL_HP_SCANJET_G4010 ||
dev->model->model_id == MODEL_HP_SCANJET_G4050)
{
// BUG: datasheet says we shouldn't set exposure to zero
sanei_genesys_set_exposure(regs, {0, 0, 0});
}
}
}
regs.state.is_lamp_on = set;
}
void sanei_genesys_set_motor_power(Genesys_Register_Set& regs, bool set)
{
static const uint8_t REG02_MTRPWR = 0x10;
if (set) {
regs.find_reg(0x02).value |= REG02_MTRPWR;
} else {
regs.find_reg(0x02).value &= ~REG02_MTRPWR;
}
}
/**
* Write to many registers at once
* Note: sequential call to write register, no effective
* bulk write implemented.
* @param dev device to write to
* @param reg pointer to an array of registers
* @param elems size of the array
*/
void sanei_genesys_bulk_write_register(Genesys_Device* dev, const Genesys_Register_Set& reg)
{
DBG_HELPER(dbg);
if (dev->model->asic_type == AsicType::GL646 ||
dev->model->asic_type == AsicType::GL841)
{
uint8_t outdata[8];
std::vector<uint8_t> buffer;
buffer.reserve(reg.size() * 2);
/* copy registers and values in data buffer */
for (const auto& r : reg) {
buffer.push_back(r.address);
buffer.push_back(r.value);
}
DBG(DBG_io, "%s (elems= %lu, size = %lu)\n", __func__, (u_long) reg.size(),
(u_long) buffer.size());
if (dev->model->asic_type == AsicType::GL646) {
outdata[0] = BULK_OUT;
outdata[1] = BULK_REGISTER;
outdata[2] = 0x00;
outdata[3] = 0x00;
outdata[4] = (buffer.size() & 0xff);
outdata[5] = ((buffer.size() >> 8) & 0xff);
outdata[6] = ((buffer.size() >> 16) & 0xff);
outdata[7] = ((buffer.size() >> 24) & 0xff);
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, INDEX,
sizeof(outdata), outdata);
size_t write_size = buffer.size();
dev->usb_dev.bulk_write(buffer.data(), &write_size);
} else {
for (size_t i = 0; i < reg.size();) {
size_t c = reg.size() - i;
if (c > 32) /*32 is max on GL841. checked that.*/
c = 32;
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_SET_REGISTER,
INDEX, c * 2, buffer.data() + i * 2);
i += c;
}
}
} else {
for (const auto& r : reg) {
dev->write_register(r.address, r.value);
}
}
DBG (DBG_io, "%s: wrote %lu registers\n", __func__, (u_long) reg.size());
}
/**
* writes a block of data to AHB
* @param dn USB device index
* @param usb_mode usb mode : 1 usb 1.1, 2 usb 2.0
* @param addr AHB address to write to
* @param size size of the chunk of data
* @param data pointer to the data to write
*/
void sanei_genesys_write_ahb(Genesys_Device* dev, uint32_t addr, uint32_t size, uint8_t* data)
{
DBG_HELPER(dbg);
uint8_t outdata[8];
size_t written,blksize;
int i;
char msg[100]="AHB=";
outdata[0] = addr & 0xff;
outdata[1] = ((addr >> 8) & 0xff);
outdata[2] = ((addr >> 16) & 0xff);
outdata[3] = ((addr >> 24) & 0xff);
outdata[4] = (size & 0xff);
outdata[5] = ((size >> 8) & 0xff);
outdata[6] = ((size >> 16) & 0xff);
outdata[7] = ((size >> 24) & 0xff);
if (DBG_LEVEL >= DBG_io)
{
for (i = 0; i < 8; i++)
{
sprintf (msg+strlen(msg), " 0x%02x", outdata[i]);
}
DBG (DBG_io, "%s: write(0x%08x,0x%08x)\n", __func__, addr,size);
DBG (DBG_io, "%s: %s\n", __func__, msg);
}
// write addr and size for AHB
dev->usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x01, 8, outdata);
size_t max_out_size = sanei_genesys_get_bulk_max_size(dev);
/* write actual data */
written = 0;
do
{
if (size - written > max_out_size)
{
blksize = max_out_size;
}
else
{
blksize = size - written;
}
dev->usb_dev.bulk_write(data + written, &blksize);
written += blksize;
}
while (written < size);
}
std::vector<uint16_t> get_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor,
int color)
{
if (!dev->gamma_override_tables[color].empty()) {
return dev->gamma_override_tables[color];
} else {
std::vector<uint16_t> ret;
sanei_genesys_create_default_gamma_table(dev, ret, sensor.gamma[color]);
return ret;
}
}
/** @brief generates gamma buffer to transfer
* Generates gamma table buffer to send to ASIC. Applies
* contrast and brightness if set.
* @param dev device to set up
* @param bits number of bits used by gamma
* @param max value for gamma
* @param size of the gamma table
* @param gamma allocated gamma buffer to fill
*/
void sanei_genesys_generate_gamma_buffer(Genesys_Device* dev,
const Genesys_Sensor& sensor,
int bits,
int max,
int size,
uint8_t* gamma)
{
DBG_HELPER(dbg);
std::vector<uint16_t> rgamma = get_gamma_table(dev, sensor, GENESYS_RED);
std::vector<uint16_t> ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN);
std::vector<uint16_t> bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE);
if(dev->settings.contrast!=0 || dev->settings.brightness!=0)
{
std::vector<uint16_t> lut(65536);
sanei_genesys_load_lut((unsigned char *)lut.data(),
bits,
bits,
0,
max,
dev->settings.contrast,
dev->settings.brightness);
for (int i = 0; i < size; i++)
{
uint16_t value=rgamma[i];
value=lut[value];
gamma[i * 2 + size * 0 + 0] = value & 0xff;
gamma[i * 2 + size * 0 + 1] = (value >> 8) & 0xff;
value=ggamma[i];
value=lut[value];
gamma[i * 2 + size * 2 + 0] = value & 0xff;
gamma[i * 2 + size * 2 + 1] = (value >> 8) & 0xff;
value=bgamma[i];
value=lut[value];
gamma[i * 2 + size * 4 + 0] = value & 0xff;
gamma[i * 2 + size * 4 + 1] = (value >> 8) & 0xff;
}
}
else
{
for (int i = 0; i < size; i++)
{
uint16_t value=rgamma[i];
gamma[i * 2 + size * 0 + 0] = value & 0xff;
gamma[i * 2 + size * 0 + 1] = (value >> 8) & 0xff;
value=ggamma[i];
gamma[i * 2 + size * 2 + 0] = value & 0xff;
gamma[i * 2 + size * 2 + 1] = (value >> 8) & 0xff;
value=bgamma[i];
gamma[i * 2 + size * 4 + 0] = value & 0xff;
gamma[i * 2 + size * 4 + 1] = (value >> 8) & 0xff;
}
}
}
/** @brief send gamma table to scanner
* This function sends generic gamma table (ie ones built with
* provided gamma) or the user defined one if provided by
* fontend. Used by gl846+ ASICs
* @param dev device to write to
*/
void sanei_genesys_send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
int size;
int i;
size = 256 + 1;
/* allocate temporary gamma tables: 16 bits words, 3 channels */
std::vector<uint8_t> gamma(size * 2 * 3, 255);
sanei_genesys_generate_gamma_buffer(dev, sensor, 16, 65535, size, gamma.data());
// loop sending gamma tables NOTE: 0x01000000 not 0x10000000
for (i = 0; i < 3; i++) {
// clear corresponding GMM_N bit
uint8_t val = dev->read_register(0xbd);
val &= ~(0x01 << i);
dev->write_register(0xbd, val);
// clear corresponding GMM_F bit
val = dev->read_register(0xbe);
val &= ~(0x01 << i);
dev->write_register(0xbe, val);
// FIXME: currently the last word of each gamma table is not initialied, so to work around
// unstable data, just set it to 0 which is the most likely value of uninitialized memory
// (proper value is probably 0xff)
gamma[size * 2 * i + size * 2 - 2] = 0;
gamma[size * 2 * i + size * 2 - 1] = 0;
/* set GMM_Z */
dev->write_register(0xc5+2*i, gamma[size*2*i+1]);
dev->write_register(0xc6+2*i, gamma[size*2*i]);
sanei_genesys_write_ahb(dev, 0x01000000 + 0x200 * i, (size-1) * 2,
gamma.data() + i * size * 2+2);
}
}
static unsigned align_int_up(unsigned num, unsigned alignment)
{
unsigned mask = alignment - 1;
if (num & mask)
num = (num & ~mask) + alignment;
return num;
}
void compute_session(Genesys_Device* dev, ScanSession& s, const Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
(void) dev;
s.params.assert_valid();
// compute optical and output resolutions
s.ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(s.params.xres);
if (dev->model->asic_type == AsicType::GL646) {
s.optical_resolution = sensor.optical_res;
} else {
s.optical_resolution = sensor.optical_res / s.ccd_size_divisor;
}
s.output_resolution = s.params.xres;
if (s.output_resolution > s.optical_resolution) {
throw std::runtime_error("output resolution higher than optical resolution");
}
// compute the number of optical pixels that will be acquired by the chip
s.optical_pixels = (s.params.pixels * s.optical_resolution) / s.output_resolution;
if (s.optical_pixels * s.output_resolution < s.params.pixels * s.optical_resolution) {
s.optical_pixels++;
}
if (dev->model->asic_type == AsicType::GL841) {
if (s.optical_pixels & 1)
s.optical_pixels++;
}
if (dev->model->asic_type == AsicType::GL646 && s.params.xres == 400) {
s.optical_pixels = (s.optical_pixels / 6) * 6;
}
if (dev->model->asic_type == AsicType::GL843) {
// ensure the number of optical pixels is divisible by 2.
// In quarter-CCD mode optical_pixels is 4x larger than the actual physical number
s.optical_pixels = align_int_up(s.optical_pixels, 2 * s.ccd_size_divisor);
}
}
/** @brief initialize device
* Initialize backend and ASIC : registers, motor tables, and gamma tables
* then ensure scanner's head is at home. Designed for gl846+ ASICs.
* Detects cold boot (ie first boot since device plugged) in this case
* an extensice setup up is done at hardware level.
*
* @param dev device to initialize
* @param max_regs umber of maximum used registers
*/
void sanei_genesys_asic_init(Genesys_Device* dev, int /*max_regs*/)
{
DBG_HELPER(dbg);
uint8_t val;
SANE_Bool cold = SANE_TRUE;
// URB 16 control 0xc0 0x0c 0x8e 0x0b len 1 read 0x00 */
dev->usb_dev.control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, VALUE_GET_REGISTER, 0x00, 1, &val);
DBG (DBG_io2, "%s: value=0x%02x\n", __func__, val);
DBG (DBG_info, "%s: device is %s\n", __func__, (val & 0x08) ? "USB 1.0" : "USB2.0");
if (val & 0x08)
{
dev->usb_mode = 1;
}
else
{
dev->usb_mode = 2;
}
/* Check if the device has already been initialized and powered up. We read register 0x06 and
check PWRBIT, if reset scanner has been freshly powered up. This bit will be set to later
so that following reads can detect power down/up cycle
*/
if (dev->read_register(0x06) & 0x10) {
cold = SANE_FALSE;
}
DBG (DBG_info, "%s: device is %s\n", __func__, cold ? "cold" : "warm");
/* don't do anything if backend is initialized and hardware hasn't been
* replug */
if (dev->already_initialized && !cold)
{
DBG (DBG_info, "%s: already initialized, nothing to do\n", __func__);
return;
}
// set up hardware and registers
dev->cmd_set->asic_boot(dev, cold);
/* now hardware part is OK, set up device struct */
dev->white_average_data.clear();
dev->dark_average_data.clear();
dev->settings.color_filter = ColorFilter::RED;
/* duplicate initial values into calibration registers */
dev->calib_reg = dev->reg;
const auto& sensor = sanei_genesys_find_sensor_any(dev);
// Set analog frontend
dev->cmd_set->set_fe(dev, sensor, AFE_INIT);
dev->already_initialized = SANE_TRUE;
// Move to home if needed
dev->cmd_set->slow_back_home(dev, SANE_TRUE);
dev->scanhead_position_in_steps = 0;
// Set powersaving (default = 15 minutes)
dev->cmd_set->set_powersaving(dev, 15);
}
void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& sensor,
unsigned dpihw)
{
// same across GL646, GL841, GL843, GL846, GL847, GL124
const uint8_t REG05_DPIHW_MASK = 0xc0;
const uint8_t REG05_DPIHW_600 = 0x00;
const uint8_t REG05_DPIHW_1200 = 0x40;
const uint8_t REG05_DPIHW_2400 = 0x80;
const uint8_t REG05_DPIHW_4800 = 0xc0;
if (sensor.dpihw_override != 0) {
dpihw = sensor.dpihw_override;
}
uint8_t dpihw_setting;
switch (dpihw) {
case 600:
dpihw_setting = REG05_DPIHW_600;
break;
case 1200:
dpihw_setting = REG05_DPIHW_1200;
break;
case 2400:
dpihw_setting = REG05_DPIHW_2400;
break;
case 4800:
dpihw_setting = REG05_DPIHW_4800;
break;
default:
throw SaneException("Unknown dpihw value: %d", dpihw);
}
regs.set8_mask(0x05, dpihw_setting, REG05_DPIHW_MASK);
}
/**
* Wait for the scanning head to park
*/
void sanei_genesys_wait_for_home(Genesys_Device* dev)
{
DBG_HELPER(dbg);
uint8_t val;
int loop;
int max=300;
/* clear the parking status whatever the outcome of the function */
dev->parking=SANE_FALSE;
// read initial status, if head isn't at home and motor is on we are parking, so we wait.
// gl847/gl124 need 2 reads for reliable results
sanei_genesys_get_status(dev, &val);
sanei_genesys_sleep_ms(10);
sanei_genesys_get_status(dev, &val);
/* if at home, return */
if(val & HOMESNR)
{
DBG (DBG_info,
"%s: already at home\n", __func__);
return;
}
/* loop for 30 s max, polling home sensor */
loop = 0;
do
{
sanei_genesys_sleep_ms(100);
sanei_genesys_get_status(dev, &val);
if (DBG_LEVEL >= DBG_io2)
{
sanei_genesys_print_status (val);
}
++loop;
} while (loop < max && !(val & HOMESNR));
/* if after the timeout, head is still not parked, error out */
if (loop >= max && !(val & HOMESNR)) {
DBG (DBG_error, "%s: failed to reach park position in %dseconds\n", __func__, max/10);
throw SaneException(SANE_STATUS_IO_ERROR, "failed to reach park position");
}
}
/** @brief motor profile
* search for the database of motor profiles and get the best one. Each
* profile is at full step and at a reference exposure. Use first entry
* by default.
* @param motors motor profile database
* @param motor_type motor id
* @param exposure exposure time
* @return a pointer to a Motor_Profile struct
*/
Motor_Profile *sanei_genesys_get_motor_profile(Motor_Profile *motors, int motor_type, int exposure)
{
unsigned int i;
int idx;
i=0;
idx=-1;
while(motors[i].exposure!=0)
{
/* exact match */
if(motors[i].motor_type==motor_type && motors[i].exposure==exposure)
{
return &(motors[i]);
}
/* closest match */
if(motors[i].motor_type==motor_type)
{
/* if profile exposure is higher than the required one,
* the entry is a candidate for the closest match */
if(motors[i].exposure>=exposure)
{
if(idx<0)
{
/* no match found yet */
idx=i;
}
else
{
/* test for better match */
if(motors[i].exposure<motors[idx].exposure)
{
idx=i;
}
}
}
}
i++;
}
/* default fallback */
if(idx<0)
{
DBG (DBG_warn,"%s: using default motor profile\n",__func__);
idx=0;
}
return &(motors[idx]);
}
/**@brief compute motor step type to use
* compute the step type (full, half, quarter, ...) to use based
* on target resolution
* @param motors motor profile database
* @param motor_type motor id
* @param exposure sensor exposure
* @return 0 for full step
* 1 for half step
* 2 for quarter step
* 3 for eighth step
*/
int sanei_genesys_compute_step_type(Motor_Profile *motors,
int motor_type,
int exposure)
{
Motor_Profile *profile;
profile=sanei_genesys_get_motor_profile(motors, motor_type, exposure);
return profile->step_type;
}
/** @brief generate slope table
* Generate the slope table to use for the scan using a reference slope
* table.
* @param slope pointer to the slope table to fill
* @param steps pointer to return used step number
* @param dpi desired motor resolution
* @param exposure exposure used
* @param base_dpi base resolution of the motor
* @param step_type step type used for scan
* @param factor shrink factor for the slope
* @param motor_type motor id
* @param motors motor profile database
*/
int sanei_genesys_slope_table(std::vector<uint16_t>& slope,
int* steps, int dpi, int exposure, int base_dpi, int step_type,
int factor, int motor_type, Motor_Profile* motors)
{
int sum, i;
uint16_t target,current;
Motor_Profile *profile;
slope.clear();
/* required speed */
target=((exposure * dpi) / base_dpi)>>step_type;
DBG (DBG_io2, "%s: exposure=%d, dpi=%d, target=%d\n", __func__, exposure, dpi, target);
/* fill result with target speed */
slope.resize(SLOPE_TABLE_SIZE, target);
profile=sanei_genesys_get_motor_profile(motors, motor_type, exposure);
/* use profile to build table */
i=0;
sum=0;
/* first step is always used unmodified */
current=profile->table[0];
/* loop on profile copying and apply step type */
while(profile->table[i]!=0 && current>=target)
{
slope[i]=current;
sum+=slope[i];
i++;
current=profile->table[i]>>step_type;
}
/* ensure last step is required speed in case profile doesn't contain it */
if(current!=0 && current<target)
{
slope[i]=target;
sum+=slope[i];
i++;
}
/* range checking */
if(profile->table[i]==0 && DBG_LEVEL >= DBG_warn && current>target)
{
DBG (DBG_warn,"%s: short slope table, failed to reach %d. target too low ?\n",__func__,target);
}
if(i<3 && DBG_LEVEL >= DBG_warn)
{
DBG (DBG_warn,"%s: short slope table, failed to reach %d. target too high ?\n",__func__,target);
}
/* align on factor */
while(i%factor!=0)
{
slope[i+1]=slope[i];
sum+=slope[i];
i++;
}
/* ensure minimal slope size */
while(i<2*factor)
{
slope[i+1]=slope[i];
sum+=slope[i];
i++;
}
// return used steps and taken time
*steps=i/factor;
return sum;
}
/** @brief returns the lowest possible ydpi for the device
* Parses device entry to find lowest motor dpi.
* @param dev device description
* @return lowest motor resolution
*/
int sanei_genesys_get_lowest_ydpi(Genesys_Device *dev)
{
return *std::min_element(dev->model->ydpi_values.begin(), dev->model->ydpi_values.end());
}
/** @brief returns the lowest possible dpi for the device
* Parses device entry to find lowest motor or sensor dpi.
* @param dev device description
* @return lowest motor resolution
*/
int sanei_genesys_get_lowest_dpi(Genesys_Device *dev)
{
return std::min(*std::min_element(dev->model->xdpi_values.begin(),
dev->model->xdpi_values.end()),
*std::min_element(dev->model->ydpi_values.begin(),
dev->model->ydpi_values.end()));
}
/** @brief check is a cache entry may be used
* Compares current settings with the cache entry and return
* SANE_TRUE if they are compatible.
* A calibration cache is compatible if color mode and x dpi match the user
* requested scan. In the case of CIS scanners, dpi isn't a criteria.
* flatbed cache entries are considred too old and then expires if they
* are older than the expiration time option, forcing calibration at least once
* then given time. */
bool sanei_genesys_is_compatible_calibration(Genesys_Device * dev, const Genesys_Sensor& sensor,
Genesys_Calibration_Cache * cache, int for_overwrite)
{
DBG_HELPER(dbg);
#ifdef HAVE_SYS_TIME_H
struct timeval time;
#endif
int compatible = 1;
if(dev->cmd_set->calculate_current_setup == nullptr) {
DBG (DBG_proc, "%s: no calculate_setup, non compatible cache\n", __func__);
return false;
}
dev->cmd_set->calculate_current_setup(dev, sensor);
DBG (DBG_proc, "%s: checking\n", __func__);
/* a calibration cache is compatible if color mode and x dpi match the user
* requested scan. In the case of CIS scanners, dpi isn't a criteria */
if (dev->model->is_cis == SANE_FALSE)
{
compatible = (dev->settings.xres == ((int) cache->used_setup.xres));
}
else
{
compatible = (sensor.get_register_hwdpi(dev->settings.xres) ==
sensor.get_register_hwdpi(cache->used_setup.xres));
}
DBG (DBG_io, "%s: after resolution check current compatible=%d\n", __func__, compatible);
if (dev->current_setup.ccd_size_divisor != cache->used_setup.ccd_size_divisor)
{
DBG (DBG_io, "%s: ccd_size_divisor=%d, used=%d\n", __func__,
dev->current_setup.ccd_size_divisor, cache->used_setup.ccd_size_divisor);
compatible = 0;
}
if (dev->session.params.scan_method != cache->params.scan_method)
{
DBG (DBG_io, "%s: current method=%d, used=%d\n", __func__,
static_cast<unsigned>(dev->session.params.scan_method),
static_cast<unsigned>(cache->params.scan_method));
compatible = 0;
}
if (!compatible)
{
DBG (DBG_proc, "%s: completed, non compatible cache\n", __func__);
return false;
}
/* a cache entry expires after afetr expiration time for non sheetfed scanners */
/* this is not taken into account when overwriting cache entries */
#ifdef HAVE_SYS_TIME_H
if(for_overwrite == SANE_FALSE && dev->settings.expiration_time >=0)
{
gettimeofday (&time, NULL);
if ((time.tv_sec - cache->last_calibration > dev->settings.expiration_time*60)
&& (dev->model->is_sheetfed == SANE_FALSE)
&& (dev->settings.scan_method == ScanMethod::FLATBED))
{
DBG (DBG_proc, "%s: expired entry, non compatible cache\n", __func__);
return false;
}
}
#endif
return true;
}
/** @brief compute maximum line distance shift
* compute maximum line distance shift for the motor and sensor
* combination. Line distance shift is the distance between different
* color component of CCD sensors. Since these components aren't at
* the same physical place, they scan diffrent lines. Software must
* take this into account to accurately mix color data.
* @param dev device session to compute max_shift for
* @param channels number of color channels for the scan
* @param yres motor resolution used for the scan
* @param flags scan flags
* @return 0 or line distance shift
*/
int sanei_genesys_compute_max_shift(Genesys_Device *dev,
int channels,
int yres,
int flags)
{
int max_shift;
max_shift=0;
if (channels > 1 && !(flags & SCAN_FLAG_IGNORE_LINE_DISTANCE))
{
max_shift = dev->ld_shift_r;
if (dev->ld_shift_b > max_shift)
max_shift = dev->ld_shift_b;
if (dev->ld_shift_g > max_shift)
max_shift = dev->ld_shift_g;
max_shift = (max_shift * yres) / dev->motor.base_ydpi;
}
return max_shift;
}
/** @brief build lookup table for digital enhancements
* Function to build a lookup table (LUT), often
used by scanners to implement brightness/contrast/gamma
or by backends to speed binarization/thresholding
offset and slope inputs are -127 to +127
slope rotates line around central input/output val,
0 makes horizontal line
pos zero neg
. x . . x
. x . . x
out . x .xxxxxxxxxxx . x
. x . . x
....x....... ............ .......x....
in in in
offset moves line vertically, and clamps to output range
0 keeps the line crossing the center of the table
high low
. xxxxxxxx .
. x .
out x . x
. . x
............ xxxxxxxx....
in in
out_min/max provide bounds on output values,
useful when building thresholding lut.
0 and 255 are good defaults otherwise.
* @param lut pointer where to store the generated lut
* @param in_bits number of bits for in values
* @param out_bits number of bits of out values
* @param out_min minimal out value
* @param out_max maximal out value
* @param slope slope of the generated data
* @param offset offset of the generated data
*/
void sanei_genesys_load_lut(unsigned char* lut,
int in_bits, int out_bits,
int out_min, int out_max,
int slope, int offset)
{
DBG_HELPER(dbg);
int i, j;
double shift, rise;
int max_in_val = (1 << in_bits) - 1;
int max_out_val = (1 << out_bits) - 1;
uint8_t *lut_p8 = lut;
uint16_t *lut_p16 = (uint16_t *) lut;
/* slope is converted to rise per unit run:
* first [-127,127] to [-.999,.999]
* then to [-PI/4,PI/4] then [0,PI/2]
* then take the tangent (T.O.A)
* then multiply by the normal linear slope
* because the table may not be square, i.e. 1024x256*/
rise = tan ((double) slope / 128 * M_PI_4 + M_PI_4) * max_out_val / max_in_val;
/* line must stay vertically centered, so figure
* out vertical offset at central input value */
shift = (double) max_out_val / 2 - (rise * max_in_val / 2);
/* convert the user offset setting to scale of output
* first [-127,127] to [-1,1]
* then to [-max_out_val/2,max_out_val/2]*/
shift += (double) offset / 127 * max_out_val / 2;
for (i = 0; i <= max_in_val; i++)
{
j = rise * i + shift;
/* cap data to required range */
if (j < out_min)
{
j = out_min;
}
else if (j > out_max)
{
j = out_max;
}
/* copy result according to bit depth */
if (out_bits <= 8)
{
*lut_p8 = j;
lut_p8++;
}
else
{
*lut_p16 = j;
lut_p16++;
}
}
}
void sanei_genesys_usleep(unsigned int useconds)
{
if (sanei_usb_is_replay_mode_enabled() == SANE_TRUE)
return;
usleep(useconds);
}
void sanei_genesys_sleep_ms(unsigned int milliseconds)
{
sanei_genesys_usleep(milliseconds * 1000);
}
static std::unique_ptr<std::vector<std::function<void()>>> s_functions_run_at_backend_exit;
void add_function_to_run_at_backend_exit(std::function<void()> function)
{
if (!s_functions_run_at_backend_exit)
s_functions_run_at_backend_exit.reset(new std::vector<std::function<void()>>());
s_functions_run_at_backend_exit->push_back(std::move(function));
}
void run_functions_at_backend_exit()
{
for (auto it = s_functions_run_at_backend_exit->rbegin();
it != s_functions_run_at_backend_exit->rend(); ++it)
{
(*it)();
}
s_functions_run_at_backend_exit.release();
}
void debug_dump(unsigned level, const Genesys_Settings& settings)
{
DBG(level, "settings:\n"
"Resolution X/Y : %u / %u dpi\n"
"Lines : %u\n"
"Pixels per line : %u\n"
"Pixels per line (requested) : %u\n"
"Depth : %u\n"
"Start position X/Y : %.3f/%.3f\n"
"Scan mode : %d\n\n",
settings.xres, settings.yres,
settings.lines, settings.pixels, settings.requested_pixels, settings.depth,
settings.tl_x, settings.tl_y,
static_cast<unsigned>(settings.scan_mode));
}
void debug_dump(unsigned level, const SetupParams& params)
{
DBG(level, "settings:\n"
"Resolution X/Y : %u / %u dpi\n"
"Lines : %u\n"
"Pixels per line : %u\n"
"Pixels per line (requested) : %u\n"
"Depth : %u\n"
"Channels : %u\n"
"Start position X/Y : %g / %g\n"
"Scan mode : %d\n"
"Color filter : %d\n"
"Flags : %x\n",
params.xres, params.yres,
params.lines, params.pixels, params.requested_pixels,
params.depth, params.channels,
params.startx, params.starty,
static_cast<unsigned>(params.scan_mode),
static_cast<unsigned>(params.color_filter),
params.flags);
}
void debug_dump(unsigned level, const ScanSession& session)
{
DBG(level, "session:\n");
DBG(level, " computed : %d\n", session.computed);
DBG(level, " hwdpi_divisor : %d\n", session.hwdpi_divisor);
DBG(level, " ccd_size_divisor : %d\n", session.ccd_size_divisor);
DBG(level, " optical_resolution : %d\n", session.optical_resolution);
DBG(level, " optical_pixels : %d\n", session.optical_pixels);
DBG(level, " optical_line_bytes : %d\n", session.optical_line_bytes);
DBG(level, " output_resolution : %d\n", session.output_resolution);
DBG(level, " output_pixels : %d\n", session.output_pixels);
DBG(level, " output_line_bytes : %d\n", session.output_line_bytes);
DBG(level, " output_line_count : %d\n", session.output_line_count);
DBG(level, " num_staggered_lines : %d\n", session.num_staggered_lines);
DBG(level, " max_color_shift_lines : %d\n", session.max_color_shift_lines);
DBG(level, " enable_ledadd : %d\n", session.enable_ledadd);
DBG(level, " pixel_startx : %d\n", session.pixel_startx);
DBG(level, " pixel_endx : %d\n", session.pixel_endx);
debug_dump(level, session.params);
}
void debug_dump(unsigned level, const Genesys_Current_Setup& setup)
{
DBG(level, "current_setup:\n"
"Pixels: %d\n"
"Lines: %d\n"
"exposure_time: %d\n"
"Resolution X: %g\n"
"ccd_size_divisor: %d\n"
"stagger: %d\n"
"max_shift: %d\n",
setup.pixels,
setup.lines,
setup.exposure_time,
setup.xres,
setup.ccd_size_divisor,
setup.stagger,
setup.max_shift);
}
void debug_dump(unsigned level, const Genesys_Register_Set& regs)
{
DBG(level, "register_set:\n");
for (const auto& reg : regs) {
DBG(level, " %04x=%02x\n", reg.address, reg.value);
}
}
void debug_dump(unsigned level, const GenesysRegisterSettingSet& regs)
{
DBG(level, "register_setting_set:\n");
for (const auto& reg : regs) {
DBG(level, " %04x=%02x & %02x\n", reg.address, reg.value, reg.mask);
}
}
void debug_dump(unsigned level, const Genesys_Sensor& sensor)
{
DBG(level, "sensor:\n");
DBG(level, " sensor_id : %d\n", sensor.sensor_id);
DBG(level, " optical_res : %d\n", sensor.optical_res);
DBG(level, " resolutions :");
if (sensor.resolutions.matches_any()) {
DBG(level, " ANY\n");
} else {
for (unsigned resolution : sensor.resolutions.resolutions()) {
DBG(level, " %d", resolution);
}
DBG(level, "\n");
}
DBG(level, " method : %d\n", static_cast<unsigned>(sensor.method));
DBG(level, " ccd_size_divisor : %d\n", sensor.ccd_size_divisor);
DBG(level, " black_pixels : %d\n", sensor.black_pixels);
DBG(level, " dummy_pixel : %d\n", sensor.dummy_pixel);
DBG(level, " CCD_start_xoffset : %d\n", sensor.CCD_start_xoffset);
DBG(level, " sensor_pixels : %d\n", sensor.sensor_pixels);
DBG(level, " fau_gain_white_ref : %d\n", sensor.fau_gain_white_ref);
DBG(level, " gain_white_ref : %d\n", sensor.gain_white_ref);
DBG(level, " exposure.red : %d\n", sensor.exposure.red);
DBG(level, " exposure.green : %d\n", sensor.exposure.green);
DBG(level, " exposure.blue : %d\n", sensor.exposure.blue);
DBG(level, " exposure_lperiod : %d\n", sensor.exposure_lperiod);
DBG(level, " custom_regs\n");
debug_dump(level, sensor.custom_regs);
DBG(level, " custom_fe_regs\n");
debug_dump(level, sensor.custom_fe_regs);
DBG(level, " gamma.red : %f\n", sensor.gamma[0]);
DBG(level, " gamma.green : %f\n", sensor.gamma[1]);
DBG(level, " gamma.blue : %f\n", sensor.gamma[2]);
}