sane-project-backends/backend/p5_device.c

1612 wiersze
38 KiB
C

/**
* Description of the Primax PagePartner model
*/
static P5_Model pagepartner_model = {
"Primax PagePartner",
"Primax",
"PagePartner",
SANE_I18N ("sheetfed scanner"),
{300, 200, 150, 100, 0},
/* 500 seems also possible */
{600, 400, 300, 200, 150, 100, 0},
300,
600,
100,
100,
16,
SANE_FIX (0.0),
SANE_FIX (0.0),
SANE_FIX (215.9),
SANE_FIX (300.0),
};
#ifdef HAVE_LINUX_PPDEV_H
static char *
addr_name (uint16_t addr)
{
switch (addr)
{
case DATA:
return "DATA";
break;
case STATUS:
return "STATUS";
break;
case CONTROL:
return "CONTROL";
break;
case EPPADR:
return "EPPADR";
break;
case EPPDATA:
return "EPPDATA";
break;
default:
return "*ERROR*";
}
}
#endif
/** @brief low level hardware access functions
* @{
*/
static uint8_t
p5_inb (int fd, uint16_t addr)
{
#ifdef HAVE_LINUX_PPDEV_H
uint8_t val = 0xff;
int rc, mode = 0xff;
switch (addr)
{
case DATA:
rc = ioctl (fd, PPRDATA, &val);
break;
case STATUS:
rc = ioctl (fd, PPRSTATUS, &val);
break;
case CONTROL:
rc = ioctl (fd, PPRCONTROL, &val);
break;
case EPPDATA:
mode = 1; /* data_reverse */
rc = ioctl (fd, PPDATADIR, &mode);
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
rc = ioctl (fd, PPSETMODE, &mode);
#ifdef PPSETFLAGS
mode = PP_FASTREAD;
rc = ioctl (fd, PPSETFLAGS, &mode);
#endif
rc = read (fd, &val, 1);
break;
default:
DBG (DBG_error, "p5_inb(%s) escaped ppdev\n", addr_name (addr));
return 0xFF;
}
if (rc < 0)
{
DBG (DBG_error, "ppdev ioctl returned <%s>\n", strerror (errno));
}
return val;
#else
if(fd && addr)
return 0;
return 0;
#endif
}
static void
p5_outb (int fd, uint16_t addr, uint8_t value)
{
#ifdef HAVE_LINUX_PPDEV_H
int rc = 0, mode = 0xff;
switch (addr)
{
case DATA:
rc = ioctl (fd, PPWDATA, &value);
break;
case CONTROL:
mode = value & 0x20;
rc = ioctl (fd, PPDATADIR, &mode);
if (!rc)
{
value = value & 0xDF;
rc = ioctl (fd, PPWCONTROL, &value);
}
break;
case EPPDATA:
mode = 0; /* data forward */
rc = ioctl (fd, PPDATADIR, &mode);
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
rc = ioctl (fd, PPSETMODE, &mode);
rc = write (fd, &value, 1);
break;
case EPPADR:
mode = 0; /* data forward */
rc = ioctl (fd, PPDATADIR, &mode);
mode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
rc = ioctl (fd, PPSETMODE, &mode);
rc = write (fd, &value, 1);
break;
default:
DBG (DBG_error, "p5_outb(%s,0x%02x) escaped ppdev\n", addr_name (addr),
value);
break;
}
if (rc < 0)
{
DBG (DBG_error, "ppdev ioctl returned <%s>\n", strerror (errno));
}
#else
if(fd && addr && value)
return;
#endif /* HAVE_LINUX_PPDEV_H */
}
static void
write_reg (int fd, uint8_t index, uint8_t value)
{
uint8_t idx;
/* both nibbles hold the same value */
idx = index & 0x0F;
DBG (DBG_io2, "write_reg(REG%X,0x%x)\n", idx, value);
idx = idx << 4 | idx;
p5_outb (fd, EPPADR, idx);
p5_outb (fd, EPPDATA, value);
}
static uint8_t
read_reg (int fd, uint8_t index)
{
uint8_t idx;
/* both nibbles hold the same value */
idx = index & 0x0F;
idx = idx << 4 | idx;
p5_outb (fd, EPPADR, idx);
return p5_inb (fd, EPPDATA);
}
#ifdef HAVE_LINUX_PPDEV_H
static int
read_data (int fd, uint8_t * data, int length)
{
int mode, rc, nb;
unsigned char bval;
bval = REG8;
mode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
rc = ioctl (fd, PPSETMODE, &mode);
rc = write (fd, &bval, 1);
mode = 1; /* data_reverse */
rc = ioctl (fd, PPDATADIR, &mode);
#ifdef PPSETFLAGS
mode = PP_FASTREAD;
rc = ioctl (fd, PPSETFLAGS, &mode);
#endif
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
rc = ioctl (fd, PPSETMODE, &mode);
nb = 0;
while (nb < length)
{
rc = read (fd, data + nb, length - nb);
if (rc < 0)
{
DBG (DBG_error, "memtest: error reading data back!\n");
return 0;
}
else
{
nb += rc;
}
}
return 1;
}
static void
index_write_data (int fd, uint8_t index, uint8_t * data, int length)
{
int mode;
unsigned char bval;
bval = index;
mode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
ioctl (fd, PPSETMODE, &mode);
write (fd, &bval, 1);
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
ioctl (fd, PPSETMODE, &mode);
mode = 0; /* data forward */
ioctl (fd, PPDATADIR, &mode);
write (fd, data, length);
return;
}
static void
write_data (int fd, uint8_t * data, int length)
{
index_write_data (fd, REG8, data, length);
}
static void
write_reg2 (int fd, uint8_t index, uint16_t value)
{
uint8_t data2[2];
data2[0] = value & 0xff;
data2[1] = value >> 8;
index_write_data (fd, index, data2, 2);
}
#else
static int
read_data (int fd, uint8_t * data, int length)
{
if(fd && data && length)
return -1;
return -1;
}
static void
write_data (int fd, uint8_t * data, int length)
{
if(fd && data && length)
return;
}
static void
write_reg2 (int fd, uint8_t index, uint16_t value)
{
if(fd && index && value)
return;
}
#endif
/**
* @}
*/
/** @brief This function checks a memory buffer.
* This function writes at the given memory address then read it back
* to check the scanner is correctly working.
* @param fd file descriptor used to access hardware
* @param addr address where to write and read
* @return SANE_TRUE on success, SANE_FALSE otherwise
*/
static int
memtest (int fd, uint16_t addr)
{
uint8_t sent[256];
uint8_t back[256];
int i;
write_reg2 (fd, REG1, addr);
for (i = 0; i < 256; i++)
{
sent[i] = (uint8_t) i;
back[i] = 0;
}
write_data (fd, sent, 256);
read_data (fd, back, 256);
/* check if data read back is the same that the one sent */
for (i = 0; i < 256; i++)
{
if (back[i] != sent[i])
{
return SANE_FALSE;
}
}
return SANE_TRUE;
}
#define P5_INB(k,y,z) val=p5_inb(k,y); if(val!=z) { DBG(DBG_error,"expected 0x%02x, got 0x%02x\n",z, val); return SANE_FALSE; }
/** @brief connect to scanner
* This function sends the connect sequence for the scanner.
* @param fd filedescriptor of the parallel port communication channel
* @return SANE_TRUE in case of success, SANE_FALSE otherwise
*/
static int
connect (int fd)
{
uint8_t val;
p5_inb (fd, CONTROL);
p5_outb (fd, CONTROL, 0x04);
p5_outb (fd, DATA, 0x02);
P5_INB (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x03);
P5_INB (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x03);
p5_outb (fd, DATA, 0x83);
P5_INB (fd, DATA, 0x83);
p5_outb (fd, DATA, 0x82);
P5_INB (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
p5_outb (fd, DATA, 0x02);
p5_outb (fd, DATA, 0x82);
p5_outb (fd, DATA, 0xFF);
DBG (DBG_info, "connect() OK...\n");
return SANE_TRUE;
}
static int
disconnect (int fd)
{
uint8_t val;
p5_outb (fd, CONTROL, 0x04);
p5_outb (fd, DATA, 0x00);
P5_INB (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x01);
P5_INB (fd, DATA, 0x01);
p5_outb (fd, DATA, 0x01);
p5_outb (fd, DATA, 0x81);
p5_outb (fd, DATA, 0x01);
p5_outb (fd, DATA, 0x81);
P5_INB (fd, DATA, 0x81);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x81);
P5_INB (fd, DATA, 0x81);
p5_outb (fd, DATA, 0x01);
p5_outb (fd, DATA, 0x81);
p5_outb (fd, DATA, 0x01);
p5_outb (fd, DATA, 0x81);
P5_INB (fd, DATA, 0x81);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
P5_INB (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_outb (fd, DATA, 0x00);
p5_outb (fd, DATA, 0x80);
p5_inb (fd, CONTROL);
p5_outb (fd, CONTROL, 0x0C);
return SANE_STATUS_GOOD;
}
static void
setadresses (int fd, uint16_t start, uint16_t end)
{
write_reg (fd, REG3, start & 0xff);
write_reg (fd, REG4, start >> 8);
write_reg (fd, REG5, end & 0xff);
write_reg (fd, REG6, end >> 8);
DBG (DBG_io, "setadresses(0x%x,0x%x); OK...\n", start, end);
}
#ifdef HAVE_LINUX_PPDEV_H
/** @brief open parallel port device
* opens parallel port's low level device in EPP mode
* @param devicename nam of the real device or the special value 'auto'
* @return file descriptor in cas of successn -1 otherwise
*/
static int
open_pp (const char *devicename)
{
int fd, mode = 0;
char *name;
DBG (DBG_proc, "open_pp: start, devicename=%s\n", devicename);
/* TODO improve auto device finding */
if (strncmp (devicename, "auto", 4) == 0)
{
name = strdup("/dev/parport0");
}
else
{
name = strdup(devicename);
}
/* open device */
fd = open (name, O_RDWR);
if (fd < 0)
{
switch (errno)
{
case ENOENT:
#ifdef ENIO
case ENXIO:
#endif
#ifdef ENODEV
case ENODEV:
#endif
DBG (DBG_error, "open_pp: no %s device ...\n", name);
break;
case EACCES:
DBG (DBG_error,
"open_pp: current user cannot use existing %s device ...\n",
name);
break;
default:
DBG (DBG_error, "open_pp: %s while opening %s\n", strerror (errno),
name);
}
return -1;
}
free(name);
/* claim device and set it to EPP */
ioctl (fd, PPCLAIM);
ioctl (fd, PPGETMODES, &mode);
if (mode & PARPORT_MODE_PCSPP)
DBG (DBG_io, "PARPORT_MODE_PCSPP\n");
if (mode & PARPORT_MODE_TRISTATE)
DBG (DBG_io, "PARPORT_MODE_TRISTATE\n");
if (mode & PARPORT_MODE_EPP)
DBG (DBG_io, "PARPORT_MODE_EPP\n");
if (mode & PARPORT_MODE_ECP)
DBG (DBG_io, "PARPORT_MODE_ECP\n");
if (mode & PARPORT_MODE_COMPAT)
DBG (DBG_io, "PARPORT_MODE_COMPAT\n");
if (mode & PARPORT_MODE_DMA)
DBG (DBG_io, "PARPORT_MODE_DMA\n");
if (mode & PARPORT_MODE_EPP)
{
mode = IEEE1284_MODE_EPP;
}
else
{
/*
if (mode & PARPORT_MODE_ECP)
{
mode = IEEE1284_MODE_ECP;
}
else
*/
{
mode = -1;
}
}
if (mode == -1)
{
DBG (DBG_error, "open_pp: no EPP mode, giving up ...\n");
ioctl (fd, PPRELEASE);
close (fd);
return -1;
}
ioctl (fd, PPNEGOT, &mode);
ioctl (fd, PPSETMODE, &mode);
DBG (DBG_proc, "open_pp: exit\n");
return fd;
}
/** close low level device
* release and close low level hardware device
*/
static void
close_pp (int fd)
{
int mode = IEEE1284_MODE_COMPAT;
if (fd > 2)
{
ioctl (fd, PPNEGOT, &mode);
ioctl (fd, PPRELEASE);
close (fd);
}
}
#else /* HAVE_LINUX_PPDEV_H */
static int
open_pp (const char *devicename)
{
if(devicename)
return -1;
return -1;
}
static void
close_pp (int fd)
{
if(fd)
return;
}
#endif /* HAVE_LINUX_PPDEV_H */
/** @brief test if a document is inserted
* Test if a document is inserted by reading register E
* @param fd file descriptor to access scanner
* @return SANE_STATUS_NO_DOCS if no document or SANE_STATUS_GOOD
* if something is present.
*/
static SANE_Status
test_document (int fd)
{
int detector;
/* check for document presence 0xC6: present, 0xC3 no document */
detector = read_reg (fd, REGE);
DBG (DBG_io, "test_document: detector=0x%02X\n", detector);
/* document inserted */
if (detector & 0x04)
return SANE_STATUS_GOOD;
return SANE_STATUS_NO_DOCS;
}
/**
* return the amount of scanned data available
* @param fd file descriptor to access scanner
* @return available byte number
*/
static int
available_bytes (int fd)
{
int counter;
/* read the number of 256 bytes block of scanned data */
counter = read_reg (fd, REG9);
DBG (DBG_io, "available_bytes: available_bytes=0x%02X\n", counter);
return 256 * counter;
}
static SANE_Status
build_correction (P5_Device * dev, unsigned int dpi, unsigned int mode,
unsigned int start, unsigned int width)
{
unsigned int i, j, shift, step;
DBG (DBG_proc, "build_correction: start=%d, width=%d\n", start, width);
DBG (DBG_trace, "build_correction: dpi=%d, mode=%d\n", dpi, mode);
/* loop on calibration data to find the matching one */
j = 0;
while (dev->calibration_data[j]->dpi != dpi)
{
j++;
if (j > MAX_RESOLUTIONS)
{
DBG (DBG_error, "build_correction: couldn't find calibration!\n");
return SANE_STATUS_INVAL;
}
}
if (dev->gain != NULL)
{
free (dev->gain);
dev->gain = NULL;
}
if (dev->offset != NULL)
{
free (dev->offset);
dev->offset = NULL;
}
dev->gain = (float *) malloc (width * sizeof (float));
if (dev->gain == NULL)
{
DBG (DBG_error,
"build_correction: failed to allocate memory for gain!\n");
return SANE_STATUS_NO_MEM;
}
dev->offset = (uint8_t *) malloc (width);
if (dev->offset == NULL)
{
DBG (DBG_error,
"build_correction: failed to allocate memory for offset!\n");
return SANE_STATUS_NO_MEM;
}
/* compute starting point of calibration data to use */
shift = start;
step = 1;
if (mode == MODE_GRAY)
{
/* we use green data */
shift += 1;
step = 3;
}
for (i = 0; i < width; i += step)
{
if (dev->calibration_data[j]->white_data[shift + i] -
dev->calibration_data[0]->black_data[shift + i] > BLACK_LEVEL)
{
dev->gain[i] =
WHITE_TARGET /
((float)
(dev->calibration_data[j]->white_data[shift + i] -
dev->calibration_data[j]->black_data[shift + i]));
dev->offset[i] = dev->calibration_data[j]->black_data[shift + i];
}
else
{
dev->gain[i] = 1.0;
dev->offset[i] = 0;
}
}
return SANE_STATUS_GOOD;
DBG (DBG_proc, "build_correction: end\n");
}
/** @brief start up a real scan
* This function starts the scan with the given parameters.
* @param dev device describing hardware
* @param mode color, gray level or lineart.
* @param dpi desired scan resolution.
* @param startx coordinate of the first pixel to scan in
* scan's resolution coordinate
* @param width width of the scanned area
* scanner's physical scan aread.
* @return SANE_STATUS_GOOD if scan is successfully started
*/
static SANE_Status
start_scan (P5_Device * dev, int mode, unsigned int dpi, unsigned int startx,
unsigned int width)
{
uint8_t reg0=0;
uint8_t reg2=0;
uint8_t regF=0;
uint16_t addr=0;
uint16_t start, end;
unsigned int xdpi;
DBG (DBG_proc, "start_scan: start \n");
DBG (DBG_io, "start_scan: startx=%d, width=%d, dpi=%d\n", startx, width,
dpi);
/** @brief register values
* - reg2 : reg2 seems related to x dpi and provides only 100,
* 150, 200 and 300 resolutions.
* - regF : lower nibble gives y dpi resolution ranging from 150
* to 1200 dpi.
*/
xdpi = dpi;
switch (dpi)
{
case 100:
reg2 = 0x90;
regF = 0xA2;
break;
case 150:
reg2 = 0x10;
regF = 0xA4;
break;
case 200:
reg2 = 0x80;
regF = 0xA6;
break;
case 300:
reg2 = 0x00;
regF = 0xA8;
break;
case 400:
reg2 = 0x80; /* xdpi=200 */
regF = 0xAA;
xdpi = 200;
break;
case 500:
reg2 = 0x00;
regF = 0xAC;
xdpi = 300;
break;
case 600:
reg2 = 0x00;
regF = 0xAE;
xdpi = 300;
break;
}
switch (mode)
{
case MODE_COLOR:
reg0 = 0x00;
addr = 0x0100;
break;
case MODE_GRAY:
/* green channel only */
reg0 = 0x20;
addr = 0x0100;
break;
case MODE_LINEART:
reg0 = 0x40;
addr = 0x0908;
break;
}
write_reg (dev->fd, REG1, 0x01);
write_reg (dev->fd, REG7, 0x00);
write_reg (dev->fd, REG0, reg0);
write_reg (dev->fd, REG1, 0x00);
write_reg (dev->fd, REGF, regF);
/* the memory addr used to test need not to be related
* to resolution, 0x0100 could be always used */
/* TODO get rid of it */
memtest (dev->fd, addr);
/* handle case where dpi>xdpi */
start = startx;
if (dpi > xdpi)
{
width = (width * xdpi) / dpi;
start = (startx * xdpi) / dpi;
}
/* compute and set start addr */
if (mode == MODE_COLOR)
{
start = start * 3;
width = width * 3;
}
end = start + width + 1;
/* build calibration data for the scan */
if (dev->calibrated)
{
build_correction (dev, xdpi, mode, start, width);
}
setadresses (dev->fd, start, end);
write_reg (dev->fd, REG1, addr >> 8);
write_reg (dev->fd, REG2, reg2);
regF = (regF & 0x0F) | 0x80;
write_reg (dev->fd, REGF, regF);
write_reg (dev->fd, REG0, reg0);
if (mode == MODE_LINEART)
{
write_reg (dev->fd, 0x07, 0x04);
}
else
{
write_reg (dev->fd, 0x07, 0x00);
}
write_reg (dev->fd, REG1, addr >> 8);
write_reg2 (dev->fd, REG1, addr);
write_reg (dev->fd, REGF, regF | 0x01);
write_reg (dev->fd, REG0, reg0 | 0x0C);
if (mode == MODE_LINEART)
{
write_reg (dev->fd, REG1, 0x19);
}
else
{
write_reg (dev->fd, REG1, 0x11);
}
DBG (DBG_proc, "start_scan: exit\n");
return SANE_STATUS_GOOD;
}
/** read a line of scan data
* @param dev device to read
* @param data pointer where to store data
* @param length total bytes to read on one line
* @param ltr total number of lines to read
* @param retry signals that the function must read as much lines it can
* @param x2 tells that lines must be enlarged by a 2 factor
* @param mode COLOR_MODE if color mode
* @returns number of data lines read, -1 in case of error
*/
static int
read_line (P5_Device * dev, uint8_t * data, size_t length, int ltr,
SANE_Bool retry, SANE_Bool x2, int mode, SANE_Bool correction)
{
uint8_t counter, read, cnt;
uint8_t inbuffer[MAX_SENSOR_PIXELS * 2 * 3 + 2];
unsigned int i, factor;
float val;
DBG (DBG_proc, "read_line: trying to read %d lines of %lu bytes\n", ltr,
(unsigned long)length);
counter = read_reg (dev->fd, REG9);
DBG (DBG_io, "read_line: %d bytes available\n", counter * 256);
read = 0;
if (x2 == SANE_FALSE)
{
factor = 1;
}
else
{
factor = 2;
}
/* in retry mode we read until not enough data, but in no retry
* read only one line , counter give us 256 bytes block available
* and we want an number multiple of color channels */
cnt = (255 + length / factor) / 256;
while ((counter > cnt && retry == 1) || (counter > cnt && read == 0))
{
/* read data from scanner, first and last byte aren't picture data */
read_data (dev->fd, inbuffer, length / factor + 2);
/* image correction */
if (correction == SANE_TRUE)
{
for (i = 0; i < length / factor; i++)
{
val = inbuffer[i + 1] - dev->offset[i];
if (val > 0)
{
val = val * dev->gain[i];
if (val < 255)
inbuffer[i + 1] = val;
else
inbuffer[i + 1] = 255;
}
else
{
inbuffer[i + 1] = 0;
}
}
}
/* handle horizontal data doubling */
if (x2 == SANE_FALSE)
{
memcpy (data + read * length, inbuffer + 1, length);
}
else
{
if (mode == MODE_COLOR)
{
for (i = 0; i < length / factor; i += 3)
{
data[read * length + i * factor] = inbuffer[i + 1];
data[read * length + i * factor + 1] = inbuffer[i + 2];
data[read * length + i * factor + 2] = inbuffer[i + 3];
data[read * length + i * factor + 3] = inbuffer[i + 1];
data[read * length + i * factor + 4] = inbuffer[i + 2];
data[read * length + i * factor + 5] = inbuffer[i + 3];
}
}
else
{
for (i = 0; i < length / factor; i++)
{
data[read * length + i * factor] = inbuffer[i + 1];
data[read * length + i * factor + 1] = inbuffer[i + 1];
}
}
}
read++;
if (retry == SANE_TRUE)
{
read_reg (dev->fd, REGF);
read_reg (dev->fd, REGA);
read_reg (dev->fd, REG9);
counter = read_reg (dev->fd, REG9);
read_reg (dev->fd, REGA);
if (read >= ltr)
{
DBG (DBG_io, "read_line returning %d lines\n", read);
return read;
}
counter = read_reg (dev->fd, REG9);
}
}
read_reg (dev->fd, REGF);
read_reg (dev->fd, REGA);
read_reg (dev->fd, REG9);
counter = read_reg (dev->fd, REG9);
read_reg (dev->fd, REGA);
DBG (DBG_io, "read_line returning %d lines\n", read);
return read;
}
static SANE_Status
eject (int fd)
{
int detector;
DBG (DBG_proc, "eject: start ...\n");
do
{
write_reg2 (fd, REG1, 0x1110);
detector = read_reg (fd, REGE);
detector = read_reg (fd, REGE);
}
while ((detector & 0x04) != 0);
write_reg (fd, REG0, 0x00);
write_reg (fd, REG1, 0x00);
write_reg (fd, REGF, 0x82);
write_reg (fd, REG7, 0x00);
DBG (DBG_proc, "eject: end.\n");
return SANE_STATUS_GOOD;
}
/** @brief wait for document to be present in feeder
* Polls document sensor until something is present. Give up after 20 seconds
* @param fd file descriptor of the physical device
*/
/* static int
wait_document (int fd, uint8_t detector)
{
int count = 0;
uint8_t val;
write_reg (fd, REG1, 0x00);
write_reg (fd, REG7, 0x00);
detector = read_reg (fd, REGE);
while (detector == 0xc3 && count < 20)
{
sleep (1);
count++;
detector = read_reg (fd, REGE);
}
setadresses (fd, 0x002d, 0x09c7);
write_reg (fd, REG1, 0x00);
write_reg (fd, REG2, 0x90);
write_reg (fd, REGF, 0x82);
write_reg (fd, REG0, 0x00);
val = p5_inb (fd, STATUS) & 0xf8;
if (val != 0xf8)
{
DBG (DBG_error, "wait_document: unexpected STATUS value 0x%02x instead of 0xf8", val);
}
if (count >= 20)
{
DBG (DBG_error, "wait_document: failed to detect document!\n");
return 0;
}
return 1;
} */
/** @brief move at 150 dpi
* move the paper at 150 dpi motor speed by the amount specified
* @params dev pointer to the device structure
*/
static SANE_Status
move (P5_Device * dev)
{
int skip, done, read, count;
SANE_Status status = SANE_STATUS_GOOD;
unsigned char buffer[256];
DBG (DBG_proc, "move: start\n");
/* compute number of lines to skip */
skip = dev->ystart;
/* works, but remains to be explained ... */
if (dev->ydpi > 300)
skip = skip / 2;
DBG (DBG_io, "move: skipping %d lines at %d dpi\n", skip, dev->ydpi);
/* we do a real scan of small width, discarding data */
done = 0;
status = start_scan (dev, MODE_GRAY, dev->ydpi, 0, 256);
if (status != SANE_STATUS_GOOD)
{
DBG (DBG_error, "move: failed to start scan\n");
return SANE_STATUS_INVAL;
}
do
{
/* test if document left the feeder */
status = test_document (dev->fd);
if (status == SANE_STATUS_NO_DOCS)
{
DBG (DBG_info,
"move: document was shorter than the required move\n");
return SANE_STATUS_INVAL;
}
/* test if data is available */
count = available_bytes (dev->fd);
if (count)
{
read =
read_line (dev, buffer, 256, 1, SANE_FALSE, SANE_FALSE,
MODE_GRAY, SANE_FALSE);
if (read == -1)
{
DBG (DBG_error, "move: failed to read data\n");
return SANE_STATUS_INVAL;
}
done += read;
}
}
while (done < skip);
/* reset scanner */
write_reg2 (dev->fd, REG1, 0x1110);
count = read_reg (dev->fd, REGE);
count = read_reg (dev->fd, REGE);
write_reg (dev->fd, REG0, 0x00);
write_reg (dev->fd, REG1, 0x00);
write_reg (dev->fd, REGF, 0x82);
write_reg (dev->fd, REG7, 0x00);
DBG (DBG_proc, "move: exit\n");
return status;
}
/** clean up calibration data
* @param dev device to clean up
*/
static void
cleanup_calibration (P5_Device * dev)
{
int i;
for (i = 0; i < MAX_RESOLUTIONS * 2; i++)
{
if (dev->calibration_data[i] != NULL)
{
free (dev->calibration_data[i]);
dev->calibration_data[i] = NULL;
}
}
dev->calibrated = SANE_FALSE;
}
/** detect a black scan line
* parses the given buffer and return SANE_TRUE if the line is an
* acceptable black line for calibration
* @param buffer data line to parse
* @param pixels number of pixels
* @param mode MODE_COLOR or MODE_GRAY
* @returns SANE_TRUE if it is considered as a white line
*/
static SANE_Bool
is_black_line (uint8_t * buffer, unsigned int pixels, int mode)
{
unsigned int i, start, end, count, width;
/* compute width in bytes */
if (mode == MODE_COLOR)
{
width = pixels * 3;
}
else
{
width = pixels;
}
/* we allow the calibration target to be narrower than full width, ie
* black margin at both ends of the line */
start = (5 * width) / 100;
end = (95 * width) / 100;
count = 0;
/* count number of black bytes */
for (i = start; i < end; i++)
{
if (buffer[i] > BLACK_LEVEL)
{
count++;
}
}
/* we allow 3% black pixels maximum */
if (count > (3 * width) / 100)
{
DBG (DBG_io, "is_black_line=SANE_FALSE\n");
return SANE_FALSE;
}
DBG (DBG_io, "is_black_line=SANE_TRUE\n");
return SANE_TRUE;
}
/** detect a white scan line
* parses the given buffer and return SANE_TRUE if the line is an
* acceptable white line for calibration
* @param buffer data line to parse
* @param pixels number of pixels
* @param mode MODE_COLOR or MODE_GRAY
* @returns SANE_TRUE if it is considered as a white line
*/
static SANE_Bool
is_white_line (uint8_t * buffer, unsigned int pixels, int mode)
{
unsigned int i, start, end, count, width;
/* compute width in bytes */
if (mode == MODE_COLOR)
{
width = pixels * 3;
}
else
{
width = pixels;
}
/* we allow the calibration target to be narrower than full width, ie
* black margin at both ends of the line */
start = (5 * width) / 100;
end = (95 * width) / 100;
count = 0;
/* count number of black bytes */
for (i = start; i < end; i++)
{
if (buffer[i] < BLACK_LEVEL)
{
count++;
}
}
/* we allow 3% black pixels maximum */
if (count > (3 * width) / 100)
{
DBG (DBG_io, "is_white_line=SANE_FALSE\n");
return SANE_FALSE;
}
DBG (DBG_io, "is_white_line=SANE_TRUE\n");
return SANE_TRUE;
}
/* ------------------------------------------------------------------------- */
/* writes gray data to a pnm file */
/*
static void
write_gray_data (unsigned char *image, char *name, SANE_Int width,
SANE_Int height)
{
FILE *fdbg = NULL;
fdbg = fopen (name, "wb");
if (fdbg == NULL)
return;
fprintf (fdbg, "P5\n%d %d\n255\n", width, height);
fwrite (image, width, height, fdbg);
fclose (fdbg);
}
*/
/* ------------------------------------------------------------------------- */
/* writes rgb data to a pnm file */
static void
write_rgb_data (char *name, unsigned char *image, SANE_Int width,
SANE_Int height)
{
FILE *fdbg = NULL;
fdbg = fopen (name, "wb");
if (fdbg == NULL)
return;
fprintf (fdbg, "P6\n%d %d\n255\n", width, height);
fwrite (image, width * 3, height, fdbg);
fclose (fdbg);
}
/** give calibration file name
* computes the calibration file name to use based on the
* backend name and device
*/
static char *
calibration_file (const char *devicename)
{
char *ptr = NULL;
char tmp_str[PATH_MAX];
ptr = getenv ("HOME");
if (ptr != NULL)
{
sprintf (tmp_str, "%s/.sane/p5-%s.cal", ptr, devicename);
}
else
{
ptr = getenv ("TMPDIR");
if (ptr != NULL)
{
sprintf (tmp_str, "%s/p5-%s.cal", ptr, devicename);
}
else
{
sprintf (tmp_str, "/tmp/p5-%s.cal", devicename);
}
}
DBG (DBG_trace, "calibration_file: using >%s< for calibration file name\n",
tmp_str);
return strdup (tmp_str);
}
/** restore calibration data
* restore calibration data by loading previously saved calibration data
* @param dev device to restore
* @return SANE_STATUS_GOOD on success, otherwise error code
*/
static SANE_Status
restore_calibration (P5_Device * dev)
{
SANE_Status status = SANE_STATUS_GOOD;
char *fname = NULL;
FILE *fcalib = NULL;
size_t size;
int i;
DBG (DBG_proc, "restore_calibration: start\n");
cleanup_calibration (dev);
fname = calibration_file (dev->model->name);
fcalib = fopen (fname, "rb");
if (fcalib == NULL)
{
DBG (DBG_error, "restore_calibration: failed to open %s!\n", fname);
free (fname);
return SANE_STATUS_IO_ERROR;
}
/* loop filling calibration data until EOF reached */
i = 0;
while (!feof (fcalib) && (i < 2 * MAX_RESOLUTIONS))
{
dev->calibration_data[i] = malloc (sizeof (P5_Calibration_Data));
if (dev->calibration_data[i] == NULL)
{
cleanup_calibration (dev);
free (fname);
fclose (fcalib);
DBG (DBG_error,
"restore_calibration: failed to allocate memory for calibration\n");
return SANE_STATUS_NO_MEM;
}
size =
fread (dev->calibration_data[i], 1, sizeof (P5_Calibration_Data),
fcalib);
if (feof (fcalib))
{
free (dev->calibration_data[i]);
dev->calibration_data[i] = NULL;
}
else if (size != sizeof (P5_Calibration_Data))
{
cleanup_calibration (dev);
free (fname);
fclose (fcalib);
DBG (DBG_error, "restore_calibration: failed to read from file\n");
return SANE_STATUS_IO_ERROR;
}
DBG (DBG_trace,
"restore_calibration: read 1 calibration structure from file\n");
i++;
}
dev->calibrated = SANE_TRUE;
fclose (fcalib);
free (fname);
DBG (DBG_proc, "restore_calibration: end\n");
return status;
}
/** save calibration data
* save calibration data from memory to file
* @param dev device calibration to save
* @return SANE_STATUS_GOOD on success, otherwise error code
*/
static SANE_Status
save_calibration (P5_Device * dev)
{
SANE_Status status = SANE_STATUS_GOOD;
char *fname = NULL;
FILE *fcalib = NULL;
int i;
size_t size;
DBG (DBG_proc, "save_calibration: start\n");
fname = calibration_file (dev->model->name);
fcalib = fopen (fname, "wb");
if (fcalib == NULL)
{
DBG (DBG_error, "save_calibration: failed to open %s!\n", fname);
free (fname);
return SANE_STATUS_IO_ERROR;
}
/* loop filling calibration data until EOF reached */
i = 0;
while (dev->calibration_data[i] != NULL && (i < 2 * MAX_RESOLUTIONS))
{
size =
fwrite (dev->calibration_data[i], sizeof (P5_Calibration_Data), 1,
fcalib);
if (size != sizeof (P5_Calibration_Data))
{
free (fname);
fclose (fcalib);
DBG (DBG_error, "save_calibration: failed to write to file\n");
return SANE_STATUS_IO_ERROR;
}
DBG (DBG_trace,
"save_calibration: wrote 1 calibration structure to file\n");
i++;
}
fclose (fcalib);
free (fname);
DBG (DBG_proc, "save_calibration: end\n");
return status;
}
/** calibrate scanner
* calibrates scanner by scanning a white sheet to get
* reference data. The black reference data is extracted from the lines
* that precede the physical document.
* Calibration is done at 300 color, then data is built for other modes
* and resolutions.
* @param dev device to calibrate
*/
static SANE_Status
sheetfed_calibration (P5_Device * dev)
{
uint8_t buffer[MAX_SENSOR_PIXELS * 3];
uint16_t white_data[MAX_SENSOR_PIXELS * 3];
uint16_t black_data[MAX_SENSOR_PIXELS * 3];
unsigned int i, j, k, dpi, pixels, read, black, white;
float coeff;
unsigned int red, green, blue;
int line;
SANE_Status status;
char title[40];
FILE *dbg = fopen ("debug.pnm", "wb");
fprintf (dbg, "P6\n%d %d\n255\n", MAX_SENSOR_PIXELS,
CALIBRATION_SKIP_LINES * 4);
DBG (DBG_proc, "sheetfed_calibration: start\n");
/* check calibration target has been loaded in ADF */
status = test_document (dev->fd);
if (status == SANE_STATUS_NO_DOCS)
{
DBG (DBG_error,
"sheetfed_calibration: no calibration target present!\n");
return SANE_STATUS_NO_DOCS;
}
/* clean up calibration data */
cleanup_calibration (dev);
/* a RGB scan to get reference data */
/* initialize calibration slot for the resolution */
i = 0;
dpi = dev->model->max_xdpi;
pixels = MAX_SENSOR_PIXELS;
dev->calibration_data[i] =
(P5_Calibration_Data *) malloc (sizeof (P5_Calibration_Data));
if (dev->calibration_data[i] == NULL)
{
cleanup_calibration (dev);
DBG (DBG_error,
"sheetfed_calibration: failed to allocate memory for calibration\n");
return SANE_STATUS_NO_MEM;
}
dev->calibration_data[i]->dpi = dpi;
/* start scan */
status = start_scan (dev, MODE_COLOR, dpi, 0, pixels);
if (status != SANE_STATUS_GOOD)
{
cleanup_calibration (dev);
DBG (DBG_error,
"sheetfed_calibration: failed to start scan at %d dpi\n", dpi);
return SANE_STATUS_INVAL;
}
white = 0;
black = 0;
read = 0;
for (j = 0; j < pixels * 3; j++)
{
black_data[j] = 0;
white_data[j] = 0;
}
/* read lines and gather black and white ones until enough for sensor's
* native resolution */
do
{
status = test_document (dev->fd);
if (status == SANE_STATUS_NO_DOCS && (white < 10 || black < 10))
{
cleanup_calibration (dev);
DBG (DBG_error,
"sheetfed_calibration: calibration sheet too short!\n");
return SANE_STATUS_INVAL;
}
memset (buffer, 0x00, MAX_SENSOR_PIXELS * 3);
line =
read_line (dev, buffer, pixels * 3, 1, SANE_FALSE, SANE_FALSE,
MODE_COLOR, SANE_FALSE);
if (line == -1)
{
DBG (DBG_error, "sheetfed_calibration: failed to read data\n");
return SANE_STATUS_INVAL;
}
/* if a data line has been read, add it to reference data */
if (line)
{
read++;
fwrite (buffer, pixels * 3, 1, dbg);
if (is_white_line (buffer, pixels, MODE_COLOR) && white < 256)
{
white++;
/* first calibration lines are skipped */
for (j = 0; j < pixels * 3 && read > CALIBRATION_SKIP_LINES;
j++)
{
white_data[j] += buffer[j];
}
}
if (is_black_line (buffer, pixels, MODE_COLOR) && black < 256)
{
black++;
for (j = 0; j < pixels * 3; j++)
{
black_data[j] += buffer[j];
}
}
}
}
while (test_document (dev->fd) != SANE_STATUS_NO_DOCS);
DBG (DBG_trace, "sheetfed_calibration: white lines=%d, black lines=%d\n",
white, black);
/* average pixels and store in per dpi calibration data */
for (j = 0; j < pixels * 3; j++)
{
dev->calibration_data[i]->white_data[j] = white_data[j] / white;
dev->calibration_data[i]->black_data[j] = black_data[j] / black;
}
/* we average red, green and blue offset on the full sensor */
red = 0;
green = 0;
blue = 0;
for (j = 0; j < pixels * 3; j += 3)
{
red += dev->calibration_data[i]->black_data[j];
green += dev->calibration_data[i]->black_data[j + 1];
blue += dev->calibration_data[i]->black_data[j + 2];
}
for (j = 0; j < pixels * 3; j += 3)
{
dev->calibration_data[i]->black_data[j] = red / pixels;
dev->calibration_data[i]->black_data[j + 1] = green / pixels;
dev->calibration_data[i]->black_data[j + 2] = blue / pixels;
}
/* trace calibration data for debug */
if (DBG_LEVEL > DBG_data)
{
sprintf (title, "calibration-white-%d.pnm",
dev->calibration_data[i]->dpi);
write_rgb_data (title, dev->calibration_data[i]->white_data, pixels, 1);
sprintf (title, "calibration-black-%d.pnm",
dev->calibration_data[i]->dpi);
write_rgb_data (title, dev->calibration_data[i]->black_data, pixels, 1);
}
/* loop on all remaining resolution and compute calibration data from it */
for (i = 1; i < MAX_RESOLUTIONS && dev->model->xdpi_values[i] > 0; i++)
{
dev->calibration_data[i] =
(P5_Calibration_Data *) malloc (sizeof (P5_Calibration_Data));
if (dev->calibration_data[i] == NULL)
{
cleanup_calibration (dev);
DBG (DBG_error,
"sheetfed_calibration: failed to allocate memory for calibration\n");
return SANE_STATUS_INVAL;
}
dev->calibration_data[i]->dpi = dev->model->xdpi_values[i];
coeff = ((float) dev->model->xdpi_values[i]) / (float) dpi;
/* generate data by decimation */
for (j = 0; j < pixels / coeff; j++)
{
k = j * coeff;
dev->calibration_data[i]->white_data[j] =
dev->calibration_data[0]->white_data[k];
dev->calibration_data[i]->white_data[j + 1] =
dev->calibration_data[0]->white_data[k + 1];
dev->calibration_data[i]->white_data[j + 2] =
dev->calibration_data[0]->white_data[k + 2];
dev->calibration_data[i]->black_data[j] =
dev->calibration_data[0]->black_data[k];
dev->calibration_data[i]->black_data[j + 1] =
dev->calibration_data[0]->black_data[k + 1];
dev->calibration_data[i]->black_data[j + 2] =
dev->calibration_data[0]->black_data[k + 2];
}
}
fclose (dbg);
dev->calibrated = SANE_TRUE;
/* eject calibration target */
eject (dev->fd);
DBG (DBG_proc, "sheetfed_calibration: end\n");
return SANE_STATUS_GOOD;
}
/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */