kopia lustrzana https://gitlab.com/sane-project/backends
1899 wiersze
59 KiB
C++
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, ®8);
|
|
|
|
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, ®8);
|
|
|
|
*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]);
|
|
}
|