kopia lustrzana https://gitlab.com/sane-project/backends
1657 wiersze
45 KiB
C
1657 wiersze
45 KiB
C
/*
|
|
(c) 2001,2002 Nathan Rutman nathan@gordian.com 10/17/01
|
|
|
|
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.
|
|
*/
|
|
|
|
/*
|
|
Communication, calibration, and scanning with the Canon CanoScan FB630U
|
|
flatbed scanner under linux.
|
|
|
|
Reworked into SANE-compatible format.
|
|
|
|
The usb-parallel port interface chip is GL640usb, on the far side of
|
|
which is an LM9830 parallel-port scanner-on-a-chip.
|
|
|
|
This code has not been tested on anything other than Linux/i386.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h> /* open */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h> /* usleep */
|
|
#include <time.h>
|
|
#include <math.h> /* exp() */
|
|
#ifdef HAVE_OS2_H
|
|
#include <sys/types.h> /* mode_t */
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#include "lm9830.h"
|
|
|
|
#define USB_TYPE_VENDOR (0x02 << 5)
|
|
#define USB_RECIP_DEVICE 0x00
|
|
#define USB_DIR_OUT 0
|
|
#define USB_DIR_IN 0x80
|
|
|
|
/* Assign status and verify a good return code */
|
|
#define CHK(A) {if( (status = A) != SANE_STATUS_GOOD ) { \
|
|
DBG( 1, "Failure on line of %s: %d\n", __FILE__, \
|
|
__LINE__ ); return A; }}
|
|
|
|
|
|
typedef SANE_Byte byte;
|
|
|
|
|
|
/*****************************************************
|
|
GL640 communication primitives
|
|
Provides I/O routines to Genesys Logic GL640USB USB-IEEE1284 parallel
|
|
port bridge. Used in HP3300c, Canon FB630u.
|
|
******************************************************/
|
|
|
|
/* Register codes for the bridge. These are NOT the registers for the
|
|
scanner chip on the other side of the bridge. */
|
|
typedef enum
|
|
{
|
|
GL640_BULK_SETUP = 0x82,
|
|
GL640_EPP_ADDR = 0x83,
|
|
GL640_EPP_DATA_READ = 0x84,
|
|
GL640_EPP_DATA_WRITE = 0x85,
|
|
GL640_SPP_STATUS = 0x86,
|
|
GL640_SPP_CONTROL = 0x87,
|
|
GL640_SPP_DATA = 0x88,
|
|
GL640_GPIO_OE = 0x89,
|
|
GL640_GPIO_READ = 0x8a,
|
|
GL640_GPIO_WRITE = 0x8b
|
|
}
|
|
GL640_Request;
|
|
|
|
/* Write to the usb-parallel port bridge. */
|
|
static SANE_Status
|
|
gl640WriteControl (int fd, GL640_Request req, byte * data, unsigned int size)
|
|
{
|
|
SANE_Status status;
|
|
status = sanei_usb_control_msg (fd,
|
|
/* rqttype */ USB_TYPE_VENDOR |
|
|
USB_RECIP_DEVICE | USB_DIR_OUT /*0x40? */ ,
|
|
/* rqt */ (size > 1) ? 0x04 : 0x0C,
|
|
/* val */ (SANE_Int) req,
|
|
/* ind */ 0,
|
|
/* len */ size,
|
|
/* dat */ data);
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG (1, "gl640WriteControl error\n");
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Read from the usb-parallel port bridge. */
|
|
static SANE_Status
|
|
gl640ReadControl (int fd, GL640_Request req, byte * data, unsigned int size)
|
|
{
|
|
SANE_Status status;
|
|
status = sanei_usb_control_msg (fd,
|
|
/* rqttype */ USB_TYPE_VENDOR |
|
|
USB_RECIP_DEVICE | USB_DIR_IN /*0xc0? */ ,
|
|
/* rqt */ (size > 1) ? 0x04 : 0x0C,
|
|
/* val */ (SANE_Int) req,
|
|
/* ind */ 0,
|
|
/* len */ size,
|
|
/* dat */ data);
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG (1, "gl640ReadControl error\n");
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Wrappers to read or write a single byte to the bridge */
|
|
static inline SANE_Status
|
|
gl640WriteReq (int fd, GL640_Request req, byte data)
|
|
{
|
|
return gl640WriteControl (fd, req, &data, 1);
|
|
}
|
|
|
|
static inline SANE_Status
|
|
gl640ReadReq (int fd, GL640_Request req, byte * data)
|
|
{
|
|
return gl640ReadControl (fd, req, data, 1);
|
|
}
|
|
|
|
|
|
/* Write USB bulk data
|
|
setup is an apparently scanner-specific sequence:
|
|
{(0=read, 1=write), 0x00, 0x00, 0x00, sizelo, sizehi, 0x00, 0x00}
|
|
hp3400: setup[1] = 0x01
|
|
fb630u: setup[2] = 0x80
|
|
*/
|
|
static SANE_Status
|
|
gl640WriteBulk (int fd, byte * setup, byte * data, size_t size)
|
|
{
|
|
SANE_Status status;
|
|
setup[0] = 1;
|
|
setup[4] = (size) & 0xFF;
|
|
setup[5] = (size >> 8) & 0xFF;
|
|
|
|
CHK (gl640WriteControl (fd, GL640_BULK_SETUP, setup, 8));
|
|
|
|
status = sanei_usb_write_bulk (fd, data, &size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG (1, "gl640WriteBulk error\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Read USB bulk data
|
|
setup is an apparently scanner-specific sequence:
|
|
{(0=read, 1=write), 0x00, 0x00, 0x00, sizelo, sizehi, 0x00, 0x00}
|
|
fb630u: setup[2] = 0x80
|
|
*/
|
|
static SANE_Status
|
|
gl640ReadBulk (int fd, byte * setup, byte * data, size_t size)
|
|
{
|
|
SANE_Status status;
|
|
setup[0] = 0;
|
|
setup[4] = (size) & 0xFF;
|
|
setup[5] = (size >> 8) & 0xFF;
|
|
|
|
CHK (gl640WriteControl (fd, GL640_BULK_SETUP, setup, 8));
|
|
|
|
status = sanei_usb_read_bulk (fd, data, &size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG (1, "gl640ReadBulk error\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*****************************************************
|
|
LM9830 communication primitives
|
|
parallel-port scanner-on-a-chip.
|
|
******************************************************/
|
|
|
|
/* write 1 byte to a LM9830 register address */
|
|
static SANE_Status
|
|
write_byte (int fd, byte addr, byte val)
|
|
{
|
|
SANE_Status status;
|
|
DBG (14, "write_byte(fd, 0x%02x, 0x%02x);\n", addr, val);
|
|
CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
|
|
CHK (gl640WriteReq (fd, GL640_EPP_DATA_WRITE, val));
|
|
return status;
|
|
}
|
|
|
|
|
|
/* read 1 byte from a LM9830 register address */
|
|
static SANE_Status
|
|
read_byte (int fd, byte addr, byte * val)
|
|
{
|
|
SANE_Status status;
|
|
CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
|
|
CHK (gl640ReadReq (fd, GL640_EPP_DATA_READ, val));
|
|
DBG (14, "read_byte(fd, 0x%02x, &result); /* got %02x */\n", addr, *val);
|
|
return status;
|
|
}
|
|
|
|
|
|
static byte bulk_setup_data[] = { 0, 0, 0x80, 0, 0, 0, 0, 0 };
|
|
|
|
/* Bulk write */
|
|
static SANE_Status
|
|
write_bulk (int fd, unsigned int addr, void *src, size_t count)
|
|
{
|
|
SANE_Status status;
|
|
|
|
DBG (13, "write_bulk(fd, 0x%02x, buf, 0x%04x);\n", addr, count);
|
|
|
|
if (!src)
|
|
{
|
|
DBG (1, "write_bulk: bad src\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
/* destination address */
|
|
CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
|
|
/* write */
|
|
CHK (gl640WriteBulk (fd, bulk_setup_data, src, count));
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Bulk read */
|
|
static SANE_Status
|
|
read_bulk (int fd, unsigned int addr, void *dst, size_t count)
|
|
{
|
|
SANE_Status status;
|
|
|
|
DBG (13, "read_bulk(fd, 0x%02x, buf, 0x%04x);\n", addr, count);
|
|
|
|
if (!dst)
|
|
{
|
|
DBG (1, "read_bulk: bad dest\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
/* destination address */
|
|
CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
|
|
/* read */
|
|
CHK (gl640ReadBulk (fd, bulk_setup_data, dst, count));
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************
|
|
useful macro routines
|
|
******************************************************/
|
|
|
|
/* write a 16-bit int to two sequential registers */
|
|
static SANE_Status
|
|
write_word (int fd, unsigned int addr, unsigned int data)
|
|
{
|
|
SANE_Status status;
|
|
/* MSB */
|
|
CHK (write_byte (fd, addr, (data >> 8) & 0xff));
|
|
/* LSB */
|
|
CHK (write_byte (fd, addr + 1, data & 0xff));
|
|
return status;
|
|
}
|
|
|
|
|
|
/* write multiple bytes, one at a time (non-bulk) */
|
|
static SANE_Status
|
|
write_many (int fd, unsigned int addr, void *src, size_t count)
|
|
{
|
|
SANE_Status status;
|
|
size_t i;
|
|
|
|
DBG (14, "multi write %d\n", count);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
DBG (15, " %04x:%02x", addr + i, ((byte *) src)[i]);
|
|
status = write_byte (fd, addr + i, ((byte *) src)[i]);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (15, "\n");
|
|
return status;
|
|
}
|
|
}
|
|
DBG (15, "\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
/* read multiple bytes, one at a time (non-bulk) */
|
|
static SANE_Status
|
|
read_many (int fd, unsigned int addr, void *dst, size_t count)
|
|
{
|
|
SANE_Status status;
|
|
size_t i;
|
|
byte val;
|
|
|
|
DBG (14, "multi read %d\n", count);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
status = read_byte (fd, addr + i, &val);
|
|
((byte *) dst)[i] = val;
|
|
DBG (15, " %04x:%02x", addr + i, ((byte *) dst)[i]);
|
|
/* on err, return number of success */
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (15, "\n");
|
|
return status;
|
|
}
|
|
}
|
|
DBG (15, "\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
/* Poll addr until result & mask = val */
|
|
static int
|
|
read_poll_flag (int fd,
|
|
unsigned int addr, unsigned int mask, unsigned int val)
|
|
{
|
|
SANE_Status status;
|
|
byte result = 0;
|
|
time_t start_time = time (NULL);
|
|
|
|
DBG (12, "read_poll_flag...\n");
|
|
do
|
|
{
|
|
status = read_byte (fd, addr, &result);
|
|
if (status != SANE_STATUS_GOOD)
|
|
return -1;
|
|
/* Give it a minute */
|
|
if ((time (NULL) - start_time) > 60)
|
|
{
|
|
DBG (1, "read_poll_flag: timed out (%d)\n", result);
|
|
return -1;
|
|
}
|
|
usleep (100000);
|
|
}
|
|
while ((result & mask) != val);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Keep reading addr until results >= min */
|
|
static int
|
|
read_poll_min (int fd, unsigned int addr, unsigned int min)
|
|
{
|
|
SANE_Status status;
|
|
byte result;
|
|
time_t start_time = time (NULL);
|
|
|
|
DBG (12, "waiting...\n");
|
|
do
|
|
{
|
|
status = read_byte (fd, addr, &result);
|
|
if (status != SANE_STATUS_GOOD)
|
|
return -1;
|
|
/* Give it a minute */
|
|
if ((time (NULL) - start_time) > 60)
|
|
{
|
|
DBG (1, "read_poll_min: timed out (%d < %d)\n", result, min);
|
|
return -1;
|
|
}
|
|
/* no sleep here, or calibration gets unhappy. */
|
|
}
|
|
while (result < min);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Bulk read "ks" kilobytes + "remainder" bytes of data, to a buffer if the
|
|
buffer is valid. */
|
|
static int
|
|
read_bulk_size (int fd, int ks, int remainder, byte * dest, int destsize)
|
|
{
|
|
byte *buf;
|
|
int bytes = (ks - 1) * 1024 + remainder;
|
|
int dropdata = ((dest == 0) || (destsize < bytes));
|
|
|
|
if (bytes < 0)
|
|
{
|
|
DBG (1, "read_bulk_size: invalid size %02x (%d)\n", ks, bytes);
|
|
return -1;
|
|
}
|
|
if (destsize && (destsize < bytes))
|
|
{
|
|
DBG (3, "read_bulk_size: more data than buffer (%d/%d)\n",
|
|
destsize, bytes);
|
|
bytes = destsize;
|
|
}
|
|
|
|
if (bytes == 0)
|
|
return 0;
|
|
|
|
if (dropdata)
|
|
{
|
|
buf = malloc (bytes);
|
|
DBG (3, " ignoring data ");
|
|
}
|
|
else
|
|
buf = dest;
|
|
|
|
read_bulk (fd, 0x00, buf, bytes);
|
|
|
|
if (dropdata)
|
|
free (buf);
|
|
return bytes;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************
|
|
|
|
fb630u calibration and scan
|
|
|
|
******************************************************/
|
|
|
|
/* data structures and constants */
|
|
|
|
typedef struct CANON_Handle
|
|
{
|
|
int fd; /* scanner fd */
|
|
int x1, x2, y1, y2; /* in pixels, 600 dpi */
|
|
int width, height; /* at scan resolution */
|
|
int resolution; /* dpi */
|
|
char *fname; /* output file name */
|
|
FILE *fp; /* output file pointer (for reading) */
|
|
char *buf, *ptr; /* data buffer */
|
|
unsigned char gain; /* static analog gain, 0 - 31 */
|
|
double gamma; /* gamma correction */
|
|
int flags;
|
|
#define FLG_GRAY 0x01 /* grayscale */
|
|
#define FLG_FORCE_CAL 0x02 /* force calibration */
|
|
#define FLG_BUF 0x04 /* save scan to buffer instead of file */
|
|
#define FLG_NO_INTERLEAVE 0x08 /* don't interleave r,g,b pixels; leave them
|
|
in row format */
|
|
#define FLG_PPM_HEADER 0x10 /* include PPM header in scan file */
|
|
}
|
|
CANON_Handle;
|
|
|
|
|
|
/* offset/gain calibration file name */
|
|
#define CAL_FILE_OGN "/tmp/canon.cal"
|
|
|
|
/* at 600 dpi */
|
|
#define CANON_MAX_WIDTH 5100 /* 8.5in */
|
|
/* this may not be right */
|
|
#define CANON_MAX_HEIGHT 7000 /* 11.66in */
|
|
|
|
/* scanline end-of-line data byte, returned after each r,g,b segment,
|
|
specific to the FB630u */
|
|
#define SCANLINE_END 0x0c
|
|
|
|
|
|
static const byte seq002[] =
|
|
{ /*r08 */ 0x04, /*300 dpi */ 0x1a, 0x00, 0x0d, 0x4c, 0x2f, 0x00, 0x01,
|
|
/*r10 */ 0x07, 0x04, 0x05, 0x06, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x25, 0x00,
|
|
0x4b, /*r20 */ 0x15, 0xe0, /*data px start */ 0x00, 0x4b, /*data px end */ 0x14, 0x37, 0x15, 0x00 };
|
|
|
|
static const byte seq003[] =
|
|
{ 0x02, 0x00, 0x00, /*lights out */ 0x03, 0xff, 0x00, 0x01, 0x03, 0xff,
|
|
0x00, 0x01, 0x03, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06,
|
|
0x1d, 0x00, 0x13, 0x04, 0x1a, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x3c, 0x35, 0x94,
|
|
0x00, 0x10, 0x08, 0x3f, 0x2b, 0x91, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00 };
|
|
|
|
|
|
/* Scanner init, called at calibration and scan time. Returns 1 if this
|
|
was the first time the scanner was plugged in, 0 afterward, and
|
|
-1 on error. */
|
|
static int
|
|
init (int fd)
|
|
{
|
|
byte result, rv;
|
|
|
|
if (gl640WriteReq (fd, GL640_GPIO_OE, 0x71) != SANE_STATUS_GOOD) {
|
|
DBG(1, "Initial write request failed.\n");
|
|
return -1;
|
|
}
|
|
/* Gets 0x04 or 0x05 first run, gets 0x64 subsequent runs. */
|
|
if (gl640ReadReq (fd, GL640_GPIO_READ, &rv) != SANE_STATUS_GOOD) {
|
|
DBG(1, "Initial read request failed.\n");
|
|
return -1;
|
|
}
|
|
gl640WriteReq (fd, GL640_GPIO_OE, 0x70);
|
|
|
|
DBG (2, "init query: %x\n", rv);
|
|
if (rv != 0x64)
|
|
{
|
|
gl640WriteReq (fd, GL640_GPIO_WRITE, 0x00);
|
|
gl640WriteReq (fd, GL640_GPIO_WRITE, 0x40);
|
|
}
|
|
|
|
gl640WriteReq (fd, GL640_SPP_DATA, 0x99);
|
|
gl640WriteReq (fd, GL640_SPP_DATA, 0x66);
|
|
gl640WriteReq (fd, GL640_SPP_DATA, 0xcc);
|
|
gl640WriteReq (fd, GL640_SPP_DATA, 0x33);
|
|
/* parallel port setting */
|
|
write_byte (fd, PARALLEL_PORT, 0x06);
|
|
/* sensor control settings */
|
|
write_byte (fd, 0x0b, 0x0d);
|
|
write_byte (fd, 0x0c, 0x4c);
|
|
write_byte (fd, 0x0d, 0x2f);
|
|
read_byte (fd, 0x0b, &result); /* wants 0d */
|
|
read_byte (fd, 0x0c, &result); /* wants 4c */
|
|
read_byte (fd, 0x0d, &result); /* wants 2f */
|
|
/* parallel port noise filter */
|
|
write_byte (fd, 0x70, 0x73);
|
|
|
|
DBG (2, "init post-reset: %x\n", rv);
|
|
/* Returns 1 if this was the first time the scanner was plugged in. */
|
|
return (rv != 0x64);
|
|
}
|
|
|
|
|
|
/* Turn off the lamps */
|
|
static void
|
|
lights_out (int fd)
|
|
{
|
|
write_word (fd, LAMP_R_ON, 0x3fff);
|
|
write_word (fd, LAMP_R_OFF, 0x0001);
|
|
write_word (fd, LAMP_G_ON, 0x3fff);
|
|
write_word (fd, LAMP_G_OFF, 0x0001);
|
|
write_word (fd, LAMP_B_ON, 0x3fff);
|
|
write_word (fd, LAMP_B_OFF, 0x0001);
|
|
}
|
|
|
|
|
|
/* Do the scan and save the resulting image as r,g,b interleaved PPM
|
|
file. */
|
|
static SANE_Status
|
|
do_scan (CANON_Handle * s)
|
|
{
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
int numbytes, datasize, level = 0, line = 0, pixel = 0;
|
|
byte *buf, *ptr, *redptr;
|
|
FILE *fp;
|
|
|
|
#define BUFSIZE 0xf000
|
|
buf = malloc (BUFSIZE);
|
|
if (!buf)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
if (s->flags & FLG_BUF)
|
|
{
|
|
/* read the whole thing into buf */
|
|
if (!s->buf)
|
|
return SANE_STATUS_NO_MEM;
|
|
s->ptr = s->buf;
|
|
fp = NULL;
|
|
}
|
|
else
|
|
{
|
|
fp = fopen (s->fname, "w");
|
|
if (!fp)
|
|
{
|
|
free (buf);
|
|
DBG (1, "err:%s when opening %s\n", strerror (errno), s->fname);
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
}
|
|
if (fp && (s->flags & FLG_PPM_HEADER))
|
|
/* PPM format header */
|
|
fprintf (fp, "P6\n%d %d\n255\n", s->width, s->height);
|
|
|
|
/* lights off */
|
|
write_byte (s->fd, COMMAND, 0x08);
|
|
/* lights on */
|
|
write_byte (s->fd, COMMAND, 0x00);
|
|
/* begin scan */
|
|
write_byte (s->fd, COMMAND, 0x03);
|
|
|
|
ptr = redptr = buf;
|
|
while (line < s->height)
|
|
{
|
|
datasize = read_poll_min (s->fd, IMAGE_DATA_AVAIL, 2);
|
|
if (datasize < 0)
|
|
{
|
|
DBG (1, "no data\n");
|
|
break;
|
|
}
|
|
DBG (12, "scan line %d %dk\n", line, datasize - 1);
|
|
/* Read may cause scan head to move */
|
|
numbytes = read_bulk_size (s->fd, datasize, 0, ptr, BUFSIZE - level);
|
|
if (numbytes < 0)
|
|
{
|
|
status = SANE_STATUS_INVAL;
|
|
break;
|
|
}
|
|
/* Data coming back is "width" bytes Red data followed by 0x0c,
|
|
width bytes Green, 0x0c, width bytes Blue, 0x0c, repeat for
|
|
"height" lines. */
|
|
if (s->flags & FLG_NO_INTERLEAVE)
|
|
{
|
|
/* number of full lines */
|
|
line += (numbytes + level) / (s->width * 3);
|
|
/* remainder (partial line) */
|
|
level = (numbytes + level) % (s->width * 3);
|
|
/* but if last line, don't store extra */
|
|
if (line >= s->height)
|
|
numbytes -= (line - s->height) * s->width * 3 + level;
|
|
if (fp)
|
|
fwrite (buf, 1, numbytes, fp);
|
|
else
|
|
{
|
|
memcpy (s->ptr, buf, numbytes);
|
|
s->ptr += numbytes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Contorsions to convert data from line-by-line RGB to
|
|
byte-by-byte RGB, without reading in the whole buffer first.
|
|
We use the sliding window redptr with the temp buffer buf. */
|
|
ptr += numbytes; /* point to the end of data */
|
|
/* while we have RGB triple data */
|
|
while (redptr + s->width + s->width <= ptr)
|
|
{
|
|
if (*redptr == SCANLINE_END)
|
|
DBG (13, "-%d- ", pixel);
|
|
if (fp)
|
|
{
|
|
/* for PPM binary (P6), 3-byte RGB pixel */
|
|
fwrite (redptr, 1, 1, fp); /* Red */
|
|
fwrite (redptr + s->width, 1, 1, fp); /* Green */
|
|
fwrite (redptr + s->width + s->width, 1, 1, fp); /* Blue */
|
|
/* for PPM ascii (P3)
|
|
fprintf(fp, "%3d %3d %3d\n", *redptr,
|
|
*(redptr + s->width),
|
|
*(redptr + s->width + s->width));
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
/* R */ *s->ptr = *redptr;
|
|
s->ptr++;
|
|
/* G */ *s->ptr = *(redptr + s->width);
|
|
s->ptr++;
|
|
/* B */ *s->ptr = *(redptr + s->width + s->width);
|
|
s->ptr++;
|
|
}
|
|
redptr++;
|
|
pixel++;
|
|
if (pixel && !(pixel % s->width))
|
|
{
|
|
/* end of a line, move redptr to the next Red section */
|
|
line++;
|
|
redptr += s->width + s->width;
|
|
#if 0
|
|
/* progress */
|
|
printf ("%2d%%\r", line * 100 / s->height);
|
|
fflush (stdout);
|
|
#endif
|
|
/* don't record any extra */
|
|
if (line >= s->height)
|
|
break;
|
|
}
|
|
}
|
|
/* keep the extra around for next time */
|
|
level = ptr - redptr;
|
|
if (level < 0)
|
|
level = 0;
|
|
memmove (buf, redptr, level);
|
|
ptr = buf + level;
|
|
redptr = buf;
|
|
}
|
|
}
|
|
|
|
if (fp)
|
|
{
|
|
fclose (fp);
|
|
DBG (6, "created scan file %s\n", s->fname);
|
|
}
|
|
free (buf);
|
|
DBG (6, "%d lines, %d pixels, %d extra bytes\n", line, pixel, level);
|
|
|
|
/* motor off */
|
|
write_byte (s->fd, COMMAND, 0x00);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static int
|
|
wait_for_return (int fd)
|
|
{
|
|
return read_poll_flag (fd, STATUS, STATUS_HOME, STATUS_HOME);
|
|
}
|
|
|
|
|
|
static SANE_Status compute_ogn (char *calfilename);
|
|
|
|
|
|
/* This is the calibration rountine Win2k goes through when the scanner is
|
|
first plugged in.
|
|
Original usb trace from Win2k with USBSnoopy ("usb sniffer for w2k"
|
|
http://benoit.papillault.free.fr/speedtouch/sniff-2000.en.php3)
|
|
*/
|
|
static int
|
|
plugin_cal (CANON_Handle * s)
|
|
{
|
|
SANE_Status status;
|
|
unsigned int temp;
|
|
byte result;
|
|
byte *buf;
|
|
int fd = s->fd;
|
|
|
|
DBG (6, "Calibrating\n");
|
|
|
|
/* reserved? */
|
|
read_byte (fd, 0x69, &result); /* wants 02 */
|
|
|
|
/* parallel port setting */
|
|
write_byte (fd, PARALLEL_PORT, 0x06);
|
|
|
|
write_many (fd, 0x08, (byte *) seq002, sizeof (seq002));
|
|
/* addr 0x28 isn't written */
|
|
write_many (fd, 0x29, (byte *) seq003, sizeof (seq003));
|
|
/* Verification */
|
|
buf = malloc (0x400);
|
|
read_many (fd, 0x08, buf, sizeof (seq002));
|
|
if (memcmp (seq002, buf, sizeof (seq002)))
|
|
DBG (1, "seq002 verification error\n");
|
|
/* addr 0x28 isn't read */
|
|
read_many (fd, 0x29, buf, sizeof (seq003));
|
|
if (memcmp (seq003, buf, sizeof (seq003)))
|
|
DBG (1, "seq003 verification error\n");
|
|
|
|
/* parallel port noise filter */
|
|
write_byte (fd, 0x70, 0x73);
|
|
|
|
lights_out (fd);
|
|
|
|
/* Home motor */
|
|
read_byte (fd, STATUS, &result); /* wants 2f or 2d */
|
|
if (!(result & STATUS_HOME) /*0x2d */ )
|
|
write_byte (fd, COMMAND, 0x02);
|
|
|
|
wait_for_return (fd);
|
|
|
|
/* Motor forward */
|
|
write_byte (fd, COMMAND, 0x01);
|
|
usleep (600000);
|
|
read_byte (fd, STATUS, &result); /* wants 0c or 2c */
|
|
read_byte (fd, STATUS, &result); /* wants 0c */
|
|
/* Return home */
|
|
write_byte (fd, COMMAND, 0x02);
|
|
|
|
/* Gamma tables */
|
|
/* Linear gamma */
|
|
for (temp = 0; temp < 0x0400; temp++)
|
|
buf[temp] = temp / 4;
|
|
/* Gamma Red */
|
|
write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 0x0400);
|
|
/* Gamma Green */
|
|
write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 0x0400);
|
|
/* Gamma Blue */
|
|
write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 0x0400);
|
|
|
|
/* Read back gamma tables. I suppose I should check results... */
|
|
/* Gamma Red */
|
|
write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_READ);
|
|
read_bulk (fd, DATAPORT, buf, 0x0400);
|
|
/* Gamma Green */
|
|
write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_READ);
|
|
read_bulk (fd, DATAPORT, buf, 0x0400);
|
|
/* Gamma Blue */
|
|
write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_READ);
|
|
read_bulk (fd, DATAPORT, buf, 0x0400);
|
|
free (buf);
|
|
|
|
/* Make sure STATUS_HOME */
|
|
read_byte (fd, STATUS, &result); /* wants 0e */
|
|
/* stepper forward */
|
|
write_byte (fd, COMMAND, 0x01);
|
|
read_byte (fd, STATUS, &result); /* wants 0c */
|
|
/* not sure if this rigid read/write pattern is required */
|
|
read_byte (fd, CLOCK_DIV, &result); /* wants 04 */
|
|
write_byte (fd, CLOCK_DIV, 0x04);
|
|
read_byte (fd, STEP_SIZE, &result); /* wants 04 */
|
|
write_byte (fd, STEP_SIZE, 0x3f);
|
|
read_byte (fd, 0x47, &result); /* wants 1a */
|
|
write_byte (fd, 0x47, 0xff);
|
|
read_byte (fd, FAST_STEP, &result); /* wants 01 */
|
|
write_byte (fd, FAST_STEP, 0x01);
|
|
read_byte (fd, 0x49, &result); /* wants 04 */
|
|
write_byte (fd, 0x49, 0x04);
|
|
read_byte (fd, SKIP_STEPS, &result); /* wants 00 */
|
|
write_byte (fd, SKIP_STEPS, 0x00);
|
|
read_byte (fd, 0x4b, &result); /* wants 00 */
|
|
write_byte (fd, 0x4b, 0xc8);
|
|
read_byte (fd, BUFFER_LIMIT, &result); /* wants 57 */
|
|
write_byte (fd, BUFFER_LIMIT, 0x04);
|
|
read_byte (fd, BUFFER_RESUME, &result); /* wants 02 */
|
|
write_byte (fd, BUFFER_RESUME, 0x02);
|
|
read_byte (fd, REVERSE_STEPS, &result); /* wants 00 */
|
|
write_byte (fd, REVERSE_STEPS, 0x00);
|
|
write_byte (fd, STEP_PWM, 0x1f);
|
|
|
|
/* Reset motor */
|
|
write_byte (fd, COMMAND, 0x08);
|
|
write_byte (fd, COMMAND, 0x00);
|
|
/* Scan */
|
|
write_byte (fd, COMMAND, 0x03);
|
|
/* Wants 02 or 03, gets a bunch of 0's first */
|
|
read_poll_min (fd, IMAGE_DATA_AVAIL, 2);
|
|
write_byte (fd, COMMAND, 0x00);
|
|
|
|
write_byte (fd, STEP_PWM, 0x3f);
|
|
write_byte (fd, CLOCK_DIV, 0x04);
|
|
/* 300 dpi */
|
|
write_word (fd, STEP_SIZE, 0x041a);
|
|
write_word (fd, FAST_STEP, 0x0104);
|
|
/* Don't skip the black/white calibration area at the bottom of the
|
|
scanner. */
|
|
write_word (fd, SKIP_STEPS, 0x0000);
|
|
write_byte (fd, BUFFER_LIMIT, 0x57);
|
|
write_byte (fd, BUFFER_RESUME, 0x02);
|
|
write_byte (fd, REVERSE_STEPS, 0x00);
|
|
write_byte (fd, BUFFER_LIMIT, 0x09);
|
|
write_byte (fd, STEP_PWM, 0x1f);
|
|
read_byte (fd, MICROSTEP, &result); /* wants 13, active */
|
|
write_byte (fd, MICROSTEP, 0x03 /* tristate */ );
|
|
|
|
/* Calibration data taken under 3 different lighting conditions */
|
|
/* dark */
|
|
write_word (fd, LAMP_R_ON, 0x0017);
|
|
write_word (fd, LAMP_R_OFF, 0x0100);
|
|
write_word (fd, LAMP_G_ON, 0x0017);
|
|
write_word (fd, LAMP_G_OFF, 0x0100);
|
|
write_word (fd, LAMP_B_ON, 0x0017);
|
|
write_word (fd, LAMP_B_OFF, 0x0100);
|
|
/* coming in, we've got 300dpi,
|
|
data px start : 0x004b
|
|
data px end : 0x1437 for a total of 5100(13ec) 600-dpi pixels,
|
|
(8.5 inches) or 2550 300-dpi pixels (7653 bytes).
|
|
Interestingly, the scan head never moves, no matter how many rows
|
|
are read. */
|
|
s->width = 2551;
|
|
s->height = 1;
|
|
s->flags = FLG_BUF | FLG_NO_INTERLEAVE;
|
|
s->buf = malloc (s->width * s->height * 3);
|
|
/* FIXME do something with this data */
|
|
CHK (do_scan (s));
|
|
|
|
/* Lighting */
|
|
/* medium */
|
|
write_word (fd, LAMP_R_ON, 0x0017);
|
|
write_word (fd, LAMP_R_OFF, 0x0200);
|
|
write_word (fd, LAMP_G_ON, 0x0017);
|
|
write_word (fd, LAMP_G_OFF, 0x01d7 /* also 01db */ );
|
|
write_word (fd, LAMP_B_ON, 0x0017);
|
|
write_word (fd, LAMP_B_OFF, 0x01af /* also 01b2 */ );
|
|
/* FIXME do something with this data */
|
|
CHK (do_scan (s));
|
|
|
|
/* Lighting */
|
|
/* bright */
|
|
write_word (fd, LAMP_R_ON, 0x0017);
|
|
write_word (fd, LAMP_R_OFF, 0x0e8e /* also 1040 */ );
|
|
write_word (fd, LAMP_G_ON, 0x0017);
|
|
write_word (fd, LAMP_G_OFF, 0x0753 /* also 0718 */ );
|
|
write_word (fd, LAMP_B_ON, 0x0017);
|
|
write_word (fd, LAMP_B_OFF, 0x03f8 /* also 040d */ );
|
|
/* FIXME do something with this data */
|
|
CHK (do_scan (s));
|
|
free (s->buf);
|
|
s->buf = NULL;
|
|
|
|
/* The trace gets a little iffy from here on out since the log files
|
|
are missing different urb's. This is kind of a puzzled-out
|
|
compilation. */
|
|
|
|
write_byte (fd, MICROSTEP, 0x13 /* pins active */ );
|
|
write_byte (fd, STEP_PWM, 0x3f);
|
|
read_byte (fd, STATUS, &result); /* wants 0c */
|
|
|
|
/* Stepper home */
|
|
write_byte (fd, COMMAND, 0x02);
|
|
/* Step size */
|
|
write_word (fd, STEP_SIZE, 0x041a /* 300 dpi */ );
|
|
/* Skip steps */
|
|
write_word (fd, SKIP_STEPS, 0x0000);
|
|
/* Pause buffer levels */
|
|
write_byte (fd, BUFFER_LIMIT, 0x57);
|
|
/* Resume buffer levels */
|
|
write_byte (fd, BUFFER_RESUME, 0x02);
|
|
|
|
wait_for_return (fd);
|
|
/* stepper forward small */
|
|
write_byte (fd, COMMAND, 0x01);
|
|
read_byte (fd, STATUS, &result); /* wants 0c */
|
|
usleep (200000);
|
|
write_byte (fd, STEP_PWM, 0x1f);
|
|
|
|
/* Read in cal strip at bottom of scanner (to adjust gain/offset
|
|
tables. Note that this isn't the brightest lighting condition.)
|
|
At 300 dpi: black rows 0-25; white rows 30-75; beginning
|
|
of glass 90.
|
|
This produces 574k of data, so save it to a temp file. */
|
|
if (!s->fname)
|
|
{
|
|
DBG (1, "No temp filename!\n");
|
|
s->fname = strdup ("/tmp/cal.XXXXXX");
|
|
mktemp (s->fname);
|
|
}
|
|
s->width = 2551;
|
|
s->height = 75;
|
|
s->flags = FLG_PPM_HEADER | FLG_NO_INTERLEAVE;
|
|
CHK (do_scan (s));
|
|
compute_ogn (s->fname);
|
|
unlink (s->fname);
|
|
|
|
write_byte (fd, STEP_PWM, 0x3f);
|
|
/* stepper home */
|
|
write_byte (fd, COMMAND, 0x02);
|
|
|
|
/* discard the remaining data */
|
|
read_byte (fd, IMAGE_DATA_AVAIL, &result); /* wants 42,4c */
|
|
if (result > 1)
|
|
{
|
|
read_bulk_size (fd, result, 0, 0, 0);
|
|
DBG (11, "read %dk extra\n", result);
|
|
}
|
|
read_byte (fd, 0x69, &result); /* wants 02 */
|
|
write_byte (fd, 0x69, 0x0a);
|
|
|
|
lights_out (fd);
|
|
init (fd);
|
|
|
|
#if 0
|
|
/* Repeatedly send this every 1 second. Button scan? FIXME */
|
|
gl640ReadReq (fd, GL640_GPIO_READ, &result); /* wants 00 */
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* The number of regions in the calibration strip (black & white). */
|
|
#define NREGIONS 2
|
|
|
|
/* Compute the offset/gain table from the calibration strip. This is
|
|
somewhat more complicated than necessary because I don't hard-code the
|
|
strip widths; I try to figure out the regions based on the scan data.
|
|
Theoretically, the region-finder should work for any number of distinct
|
|
regions (but there are only 2 on this scanner.)
|
|
This produces the CAL_FILE_OGN file, the final offset/gain table. */
|
|
static SANE_Status
|
|
compute_ogn (char *calfilename)
|
|
{
|
|
byte *linebuf, *oldline, *newline;
|
|
mode_t oldmask;
|
|
FILE *fp;
|
|
int width, height, nlines = 0, region = -1, i, transition = 1, badcnt;
|
|
int pct;
|
|
int reglines[NREGIONS];
|
|
float *avg;
|
|
float max_range[3], tmp1, tmp2;
|
|
|
|
fp = fopen (calfilename, "r");
|
|
if (!fp)
|
|
{
|
|
DBG (1, "open %s\n", calfilename);
|
|
return SANE_STATUS_EOF;
|
|
}
|
|
fscanf (fp, "P6 %d %d %*d ", &width, &height);
|
|
DBG (12, "cal file %s %dx%d\n", calfilename, width, height);
|
|
width = width * 3; /* 1 byte each of r, g, b */
|
|
/* make a buffer holding 2 lines of data */
|
|
linebuf = calloc (width * 2, sizeof (linebuf[0]));
|
|
/* first line is data read buffer */
|
|
newline = linebuf;
|
|
/* second line is a temporary holding spot in case the next line read
|
|
is the black/white transition, in which case we'll disregard this
|
|
one. */
|
|
oldline = linebuf + width;
|
|
/* column averages per region */
|
|
avg = calloc (width * NREGIONS, sizeof (avg[0]));
|
|
|
|
while (nlines < height)
|
|
{
|
|
if (fread (newline, 1, width, fp) != (size_t) width)
|
|
break;
|
|
nlines++;
|
|
/* Check if new line is majorly different than old.
|
|
Criteria is 10 pixels differing by more than 10%. */
|
|
badcnt = 0;
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
pct = newline[i] - oldline[i];
|
|
/* Fix by M.Reinelt <reinelt@eunet.at>
|
|
* do NOT use 10% (think of a dark area with
|
|
* oldline=4 and newline=5, which is a change of 20% !!
|
|
* Use an absolute difference of 10 as criteria
|
|
*/
|
|
if (pct < -10 || pct > 10)
|
|
{
|
|
badcnt++;
|
|
DBG (16, "pix%d[%d/%d] ", i, newline[i], oldline[i]);
|
|
}
|
|
}
|
|
DBG (13, "line %d changed %d\n", nlines, badcnt);
|
|
if ((badcnt > 10) || (nlines == height))
|
|
{
|
|
/* End of region. Lines are different or end of data. */
|
|
transition++;
|
|
if (transition == 1)
|
|
DBG (12, "Region %d lines %d-%d\n",
|
|
region, nlines - reglines[region], nlines - 1);
|
|
}
|
|
else
|
|
{
|
|
/* Lines are similar, so still in region. */
|
|
if (transition)
|
|
{
|
|
/* There was just a transition, so this is the start of a
|
|
new region. */
|
|
region++;
|
|
if (region >= NREGIONS)
|
|
/* Too many regions detected. Err below. */
|
|
break;
|
|
transition = 0;
|
|
reglines[region] = 0;
|
|
}
|
|
/* Add oldline to the current region's average */
|
|
for (i = 0; i < width; i++)
|
|
avg[i + region * width] += oldline[i];
|
|
reglines[region]++;
|
|
}
|
|
/* And newline becomes old */
|
|
memcpy (oldline, newline, width);
|
|
}
|
|
fclose (fp);
|
|
free (linebuf);
|
|
region++; /* now call it number of regions instead of index */
|
|
DBG (11, "read %d lines as %d regions\n", nlines, region);
|
|
|
|
/* Check to see if we screwed up */
|
|
if (region != NREGIONS)
|
|
{
|
|
DBG (1, "Warning: gain/offset compute failed.\n"
|
|
"Found %d regions instead of %d.\n", region, NREGIONS);
|
|
for (i = 0; i < region; i++)
|
|
DBG (1, " Region %d: %d lines\n",
|
|
i, (i >= NREGIONS) ? -1 : reglines[i]);
|
|
free (avg);
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
/* Now we've got regions and sums. Find averages and range. */
|
|
max_range[0] = max_range[1] = max_range[2] = 0.0;
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
/* Convert sums to averages */
|
|
/* black region */
|
|
tmp1 = avg[i] /= reglines[0];
|
|
/* white region */
|
|
tmp2 = avg[i + width] /= reglines[1];
|
|
/* Track largest range for each color.
|
|
If image is interleaved, use 'i%3', if not, 'i/(width/3)' */
|
|
if ((tmp2 - tmp1) > max_range[i / (width / 3)])
|
|
{
|
|
max_range[i / (width / 3)] = tmp2 - tmp1;
|
|
DBG (14, "max %d@%d %f-%f=%f\n",
|
|
i / (width / 3), i, tmp2, tmp1, tmp2 - tmp1);
|
|
}
|
|
}
|
|
DBG (13, "max range r %f\n", max_range[0]);
|
|
DBG (13, "max range g %f\n", max_range[1]);
|
|
DBG (13, "max range b %f\n", max_range[2]);
|
|
|
|
/* Set umask to world r/w so other users can overwrite common cal... */
|
|
oldmask = umask (0);
|
|
fp = fopen (CAL_FILE_OGN, "w");
|
|
/* ... and set it back. */
|
|
umask (oldmask);
|
|
if (!fp)
|
|
{
|
|
DBG (1, "open " CAL_FILE_OGN);
|
|
free (avg);
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/* Finally, compute offset and gain */
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
int gain, offset;
|
|
byte ogn[2];
|
|
|
|
/* skip line termination flags */
|
|
if (!((i + 1) % (width / 3)))
|
|
{
|
|
DBG (13, "skip scanline EOL %d/%d\n", i, width);
|
|
continue;
|
|
}
|
|
|
|
/* Gain multiplier:
|
|
255 : 1.5 times brighter
|
|
511 : 2 times brighter
|
|
1023: 3 times brighter */
|
|
#if 1
|
|
/* Original gain/offset */
|
|
gain = 512 * ((max_range[i / (width / 3)] /
|
|
(avg[i + width] - avg[i])) - 1);
|
|
offset = avg[i];
|
|
#else
|
|
/* This doesn't work for some people. For instance, a negative
|
|
offset would be bad. */
|
|
|
|
/* Enhanced offset and gain calculation by M.Reinelt <reinelt@eunet.at>
|
|
* These expressions were found by an iterative calibration process,
|
|
* by changing gain and offset values for every pixel until the desired
|
|
* values for black and white were reached, and finding an approximation
|
|
* formula.
|
|
* Note that offset is linear, but gain isn't!
|
|
*/
|
|
offset = (double)3.53 * avg[i] - 125;
|
|
gain = (double)3861.0 * exp(-0.0168 * (avg[i + width] - avg[i]));
|
|
#endif
|
|
|
|
DBG (14, "%d wht=%f blk=%f diff=%f gain=%d offset=%d\n", i,
|
|
avg[i + width], avg[i], avg[i + width] - avg[i], gain, offset);
|
|
/* 10-bit gain, 6-bit offset (subtractor) in two bytes */
|
|
ogn[0] = (byte) (((offset << 2) + (gain >> 8)) & 0xFF);
|
|
ogn[1] = (byte) (gain & 0xFF);
|
|
fwrite (ogn, sizeof (byte), 2, fp);
|
|
/* Annoyingly, we seem to use ogn data at 600dpi, while we scanned
|
|
at 300, so double our file. Much easier than doubling at the
|
|
read. */
|
|
fwrite (ogn, sizeof (byte), 2, fp);
|
|
}
|
|
|
|
fclose (fp);
|
|
free (avg);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static int
|
|
check_ogn_file (void)
|
|
{
|
|
FILE *fp;
|
|
fp = fopen (CAL_FILE_OGN, "r");
|
|
if (fp)
|
|
{
|
|
fclose (fp);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Load or fake the offset/gain table */
|
|
static void
|
|
install_ogn (int fd)
|
|
{
|
|
int temp;
|
|
byte *buf;
|
|
FILE *fp;
|
|
|
|
/* 8.5in at 600dpi = 5104 pixels in scan head
|
|
10-bit gain + 6-bit offset = 2 bytes per pixel, so 10208 bytes */
|
|
buf = malloc (10208);
|
|
|
|
fp = fopen (CAL_FILE_OGN, "r");
|
|
if (fp)
|
|
{
|
|
fread (buf, 2, 5100, fp);
|
|
/* screw the last 4 pixels */
|
|
}
|
|
else
|
|
{
|
|
/* Make up the gain/offset data. */
|
|
#define GAIN 256 /* 1.5x */
|
|
#define OFFSET 0
|
|
for (temp = 0; temp < 10208; temp += 2)
|
|
{
|
|
buf[temp] = (byte) ((OFFSET << 2) + (GAIN >> 8));
|
|
buf[temp + 1] = (byte) (GAIN & 0xFF);
|
|
}
|
|
}
|
|
/* Gain/offset table (r,g,b) */
|
|
write_byte (fd, DATAPORT_TARGET, DP_R | DP_OFFSET);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 10208);
|
|
if (fp)
|
|
fread (buf, 2, 5100, fp);
|
|
write_byte (fd, DATAPORT_TARGET, DP_G | DP_OFFSET);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 10208);
|
|
if (fp)
|
|
{
|
|
fread (buf, 2, 5100, fp);
|
|
fclose (fp);
|
|
}
|
|
write_byte (fd, DATAPORT_TARGET, DP_B | DP_OFFSET);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 10208);
|
|
|
|
free (buf);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Scan sequence */
|
|
/* resolution is 75,150,300,600,1200
|
|
scan coordinates in 600-dpi pixels */
|
|
static SANE_Status
|
|
scan (CANON_Handle * opt)
|
|
{
|
|
SANE_Status status;
|
|
const int left_edge = 0x004b; /* Just for my scanner, or is this
|
|
universal? Calibrate? */
|
|
int temp;
|
|
int fd = opt->fd;
|
|
byte result;
|
|
byte *buf;
|
|
|
|
/* Check status. (not in w2k driver) */
|
|
read_byte (fd, STATUS, &result); /* wants 2f or 2d */
|
|
if (!(result & STATUS_HOME) /*0x2d */ )
|
|
return SANE_STATUS_DEVICE_BUSY;
|
|
/* or force it to return?
|
|
write_byte(fd, COMMAND, 0x02);
|
|
wait_for_return(fd);
|
|
*/
|
|
|
|
/* reserved? */
|
|
read_byte (fd, 0x69, &result); /* wants 0a */
|
|
|
|
read_byte (fd, STATUS, &result); /* wants 0e */
|
|
read_byte (fd, PAPER_SENSOR, &result); /* wants 2b */
|
|
write_byte (fd, PAPER_SENSOR, 0x2b);
|
|
/* Color mode:
|
|
1-Channel Line Rate Color 0x15.
|
|
1-Channel Grayscale 0x14 (and we skip some of these tables) */
|
|
write_byte (fd, COLOR_MODE, 0x15);
|
|
|
|
/* install the offset/gain table */
|
|
install_ogn (fd);
|
|
|
|
read_byte (fd, STATUS, &result); /* wants 0e */
|
|
/* move forward to "glass 0" */
|
|
write_byte (fd, COMMAND, 0x01);
|
|
read_byte (fd, STATUS, &result); /* wants 0c */
|
|
|
|
/* create gamma table */
|
|
buf = malloc (0x400);
|
|
for (temp = 0; temp < 0x0400; temp++)
|
|
/* gamma calculation by M.Reinelt <reinelt@eunet.at> */
|
|
buf[temp] = (double) 255.0 * exp(log((temp + 0.5) / 1023.0) / opt->gamma)
|
|
+ 0.5;
|
|
|
|
/* Gamma R, write and verify */
|
|
write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 0x0400);
|
|
write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_READ);
|
|
read_bulk (fd, DATAPORT, buf, 0x0400);
|
|
/* Gamma G */
|
|
write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 0x0400);
|
|
write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_READ);
|
|
read_bulk (fd, DATAPORT, buf, 0x0400);
|
|
/* Gamma B */
|
|
write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_WRITE);
|
|
write_bulk (fd, DATAPORT, buf, 0x0400);
|
|
write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
|
|
write_word (fd, DATAPORT_ADDR, DP_READ);
|
|
read_bulk (fd, DATAPORT, buf, 0x0400);
|
|
free (buf);
|
|
|
|
write_byte (fd, CLOCK_DIV, 0x04);
|
|
/* Resolution: dpi 75(ie) 100,150(1c) 200,300(1a) 600,1200(18) */
|
|
switch (opt->resolution)
|
|
{
|
|
case 150:
|
|
write_byte (fd, 0x09, 0x1c);
|
|
break;
|
|
case 300:
|
|
write_byte (fd, 0x09, 0x1a);
|
|
break;
|
|
case 600:
|
|
case 1200:
|
|
/* actually 600 dpi horiz max */
|
|
write_byte (fd, 0x09, 0x18);
|
|
break;
|
|
default: /* 75 */
|
|
write_byte (fd, 0x09, 0x1e);
|
|
opt->resolution = 75;
|
|
}
|
|
|
|
write_word (fd, ACTIVE_PX_START, left_edge);
|
|
/* Data pixel start. Measured at 600dpi regardless of
|
|
scan resolution. 0-position is 0x004b */
|
|
write_word (fd, DATA_PX_START, left_edge + opt->x1);
|
|
/* Data pixel end. Measured at 600dpi regardless of scan
|
|
resolution. */
|
|
write_word (fd, DATA_PX_END, left_edge + opt->x2);
|
|
/* greyscale has 14,03, different lights */
|
|
write_byte (fd, COLOR_MODE, 0x15);
|
|
write_byte (fd, 0x29, 0x02);
|
|
/* Lights */
|
|
write_word (fd, LAMP_R_ON, 0x0017);
|
|
/* "Hi-low color" selection from windows driver. low(1437) hi(1481) */
|
|
write_word (fd, LAMP_R_OFF, 0x1437);
|
|
write_word (fd, LAMP_G_ON, 0x0017);
|
|
write_word (fd, LAMP_G_OFF, 0x094e);
|
|
write_word (fd, LAMP_B_ON, 0x0017);
|
|
write_word (fd, LAMP_B_OFF, 0x0543);
|
|
|
|
/* Analog static offset R,G,B. Greyscale has 0,0,0 */
|
|
write_byte (fd, 0x38, 0x3f);
|
|
write_byte (fd, 0x39, 0x3f);
|
|
write_byte (fd, 0x3a, 0x3f);
|
|
/* Analog static gain R,G,B (normally 0x01) */
|
|
write_byte (fd, 0x3b, opt->gain);
|
|
write_byte (fd, 0x3c, opt->gain);
|
|
write_byte (fd, 0x3d, opt->gain);
|
|
/* Digital gain/offset settings. Greyscale has 0 */
|
|
write_byte (fd, 0x3e, 0x1a);
|
|
|
|
{
|
|
/* Stepper motion setup. */
|
|
int stepsize, faststep = 0x0104, reverse = 0x28, phase, pwm = 0x1f;
|
|
switch (opt->resolution)
|
|
{
|
|
case 75:
|
|
stepsize = 0x0106;
|
|
faststep = 0x0106;
|
|
reverse = 0;
|
|
phase = 0x39a8;
|
|
pwm = 0x3f;
|
|
break;
|
|
case 150:
|
|
stepsize = 0x020d;
|
|
phase = 0x3198;
|
|
break;
|
|
case 300:
|
|
stepsize = 0x041a;
|
|
phase = 0x2184;
|
|
break;
|
|
case 600:
|
|
stepsize = 0x0835;
|
|
phase = 0x0074;
|
|
break;
|
|
case 1200:
|
|
/* 1200 dpi y only, x is 600 dpi */
|
|
stepsize = 0x106b;
|
|
phase = 0x41ac;
|
|
break;
|
|
default:
|
|
DBG (1, "BAD RESOLUTION");
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
write_word (fd, STEP_SIZE, stepsize);
|
|
write_word (fd, FAST_STEP, faststep);
|
|
/* There sounds like a weird step disjoint at the end of skipsteps
|
|
at 75dpi, so I think that's why skipsteps=0 at 75dpi in the
|
|
Windows driver. It still works at the normal 0x017a though. */
|
|
/* cal strip is 0x17a steps, plus 2 300dpi microsteps per pixel */
|
|
write_word (fd, SKIP_STEPS, 0x017a /* cal strip */ + opt->y1 * 2);
|
|
/* FIXME could be 0x57, why not? */
|
|
write_byte (fd, BUFFER_LIMIT, 0x20);
|
|
write_byte (fd, BUFFER_RESUME, 0x02);
|
|
write_byte (fd, REVERSE_STEPS, reverse);
|
|
/* motor resume phasing */
|
|
write_word (fd, 0x52, phase);
|
|
write_byte (fd, STEP_PWM, pwm);
|
|
}
|
|
|
|
read_byte (fd, PAPER_SENSOR, &result); /* wants 2b */
|
|
write_byte (fd, PAPER_SENSOR, 0x0b);
|
|
|
|
opt->width = (opt->x2 - opt->x1) * opt->resolution / 600 + 1;
|
|
opt->height = (opt->y2 - opt->y1) * opt->resolution / 600;
|
|
opt->flags = 0;
|
|
DBG (1, "width=%d height=%d dpi=%d\n", opt->width, opt->height,
|
|
opt->resolution);
|
|
CHK (do_scan (opt));
|
|
|
|
read_byte (fd, PAPER_SENSOR, &result); /* wants 0b */
|
|
write_byte (fd, PAPER_SENSOR, 0x2b);
|
|
write_byte (fd, STEP_PWM, 0x3f);
|
|
|
|
lights_out (fd);
|
|
/* home */
|
|
write_byte (fd, COMMAND, 0x02);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
CANON_set_scan_parameters (CANON_Handle * scan,
|
|
const int forceCal,
|
|
const int gray,
|
|
const int left,
|
|
const int top,
|
|
const int right,
|
|
const int bottom,
|
|
const int res,
|
|
const int gain,
|
|
const double gamma)
|
|
{
|
|
DBG (2, "CANON_set_scan_parameters:\n");
|
|
DBG (2, "cal = %d\n", forceCal);
|
|
DBG (2, "gray = %d (ignored)\n", gray);
|
|
DBG (2, "res = %d\n", res);
|
|
DBG (2, "gain = %d\n", gain);
|
|
DBG (2, "gamma = %f\n", gamma);
|
|
DBG (2, "in 600dpi pixels:\n");
|
|
DBG (2, "left = %d, top = %d\n", left, top);
|
|
DBG (2, "right = %d, bottom = %d\n", right, bottom);
|
|
|
|
/* Validate the input parameters */
|
|
if ((left < 0) || (right > CANON_MAX_WIDTH))
|
|
return SANE_STATUS_INVAL;
|
|
|
|
if ((top < 0) || (bottom > CANON_MAX_HEIGHT))
|
|
return SANE_STATUS_INVAL;
|
|
|
|
if (((right - left) < 10) || ((bottom - top) < 10))
|
|
return SANE_STATUS_INVAL;
|
|
|
|
if ((res != 75) && (res != 150) && (res != 300)
|
|
&& (res != 600) && (res != 1200))
|
|
return SANE_STATUS_INVAL;
|
|
|
|
if ((gain < 0) || (gain > 64))
|
|
return SANE_STATUS_INVAL;
|
|
|
|
if (gamma <= 0.0)
|
|
return SANE_STATUS_INVAL;
|
|
|
|
/* Store params */
|
|
scan->resolution = res;
|
|
scan->x1 = left;
|
|
scan->x2 = right - /* subtract 1 pixel */ 600 / scan->resolution;
|
|
scan->y1 = top;
|
|
scan->y2 = bottom;
|
|
scan->gain = gain;
|
|
scan->gamma = gamma;
|
|
scan->flags = forceCal ? FLG_FORCE_CAL : 0;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
CANON_close_device (CANON_Handle * scan)
|
|
{
|
|
DBG (3, "CANON_close_device:\n");
|
|
sanei_usb_close (scan->fd);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
CANON_open_device (CANON_Handle * scan, const char *dev)
|
|
{
|
|
SANE_Word vendor;
|
|
SANE_Word product;
|
|
SANE_Status res;
|
|
|
|
DBG (3, "CANON_open_device: `%s'\n", dev);
|
|
|
|
scan->fname = NULL;
|
|
scan->fp = NULL;
|
|
scan->flags = 0;
|
|
|
|
res = sanei_usb_open (dev, &scan->fd);
|
|
if (res != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (1, "CANON_open_device: couldn't open device `%s': %s\n", dev,
|
|
sane_strstatus (res));
|
|
return res;
|
|
}
|
|
|
|
#ifndef NO_AUTODETECT
|
|
/* We have opened the device. Check that it is a USB scanner. */
|
|
if (sanei_usb_get_vendor_product (scan->fd, &vendor, &product) !=
|
|
SANE_STATUS_GOOD)
|
|
{
|
|
DBG (1, "CANON_open_device: sanei_usb_get_vendor_product failed\n");
|
|
/* This is not a USB scanner, or SANE or the OS doesn't support it. */
|
|
sanei_usb_close (scan->fd);
|
|
scan->fd = -1;
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
/* Make sure we have a CANON scanner */
|
|
if ((vendor != 0x04a9) || (product != 0x2204))
|
|
{
|
|
DBG (1, "CANON_open_device: incorrect vendor/product (0x%x/0x%x)\n",
|
|
vendor, product);
|
|
sanei_usb_close (scan->fd);
|
|
scan->fd = -1;
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
#endif
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
static const char *
|
|
CANON_get_device_name (CANON_Handle * scanner)
|
|
{
|
|
scanner = scanner; /* Eliminate warning about unused parameters */
|
|
return "Canoscan FB630U";
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
CANON_finish_scan (CANON_Handle * scanner)
|
|
{
|
|
DBG (3, "CANON_finish_scan:\n");
|
|
if (scanner->fp)
|
|
fclose (scanner->fp);
|
|
scanner->fp = NULL;
|
|
|
|
/* remove temp file */
|
|
if (scanner->fname)
|
|
{
|
|
DBG (4, "removing temp file %s\n", scanner->fname);
|
|
unlink (scanner->fname);
|
|
free (scanner->fname);
|
|
}
|
|
scanner->fname = NULL;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
CANON_start_scan (CANON_Handle * scanner)
|
|
{
|
|
int rv;
|
|
SANE_Status status;
|
|
DBG (3, "CANON_start_scan called\n");
|
|
|
|
/* choose a temp file name for scan data */
|
|
scanner->fname = strdup ("/tmp/scan.XXXXXX");
|
|
if (!mktemp (scanner->fname))
|
|
return SANE_STATUS_IO_ERROR;
|
|
|
|
/* calibrate if needed */
|
|
rv = init (scanner->fd);
|
|
if (rv < 0) {
|
|
DBG(1, "Can't talk on USB.\n");
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
if ((rv == 1)
|
|
|| !check_ogn_file ()
|
|
|| (scanner->flags & FLG_FORCE_CAL)) {
|
|
plugin_cal (scanner);
|
|
wait_for_return (scanner->fd);
|
|
}
|
|
|
|
/* scan */
|
|
if ((status = scan (scanner)) != SANE_STATUS_GOOD)
|
|
{
|
|
CANON_finish_scan (scanner);
|
|
return status;
|
|
}
|
|
|
|
/* read the temp file back out */
|
|
scanner->fp = fopen (scanner->fname, "r");
|
|
DBG (4, "reading %s\n", scanner->fname);
|
|
if (!scanner->fp)
|
|
{
|
|
DBG (1, "open %s", scanner->fname);
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
CANON_read (CANON_Handle * scanner, SANE_Byte * data,
|
|
SANE_Int max_length, SANE_Int * length)
|
|
{
|
|
SANE_Status status;
|
|
int red_len;
|
|
|
|
DBG (5, "CANON_read called\n");
|
|
if (!scanner->fp)
|
|
return SANE_STATUS_INVAL;
|
|
red_len = fread (data, 1, max_length, scanner->fp);
|
|
/* return some data */
|
|
if (red_len > 0)
|
|
{
|
|
*length = red_len;
|
|
DBG (5, "CANON_read returned (%d/%d)\n", *length, max_length);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* EOF or file err */
|
|
*length = 0;
|
|
if (feof (scanner->fp))
|
|
{
|
|
DBG (4, "EOF\n");
|
|
status = SANE_STATUS_EOF;
|
|
}
|
|
else
|
|
{
|
|
DBG (4, "IO ERR\n");
|
|
status = SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
CANON_finish_scan (scanner);
|
|
DBG (5, "CANON_read returned (%d/%d)\n", *length, max_length);
|
|
return status;
|
|
}
|