summaryrefslogtreecommitdiff
path: root/backend/canon630u-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/canon630u-common.c')
-rw-r--r--backend/canon630u-common.c1656
1 files changed, 1656 insertions, 0 deletions
diff --git a/backend/canon630u-common.c b/backend/canon630u-common.c
new file mode 100644
index 0000000..4417141
--- /dev/null
+++ b/backend/canon630u-common.c
@@ -0,0 +1,1656 @@
+/*
+ (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 0x00
+#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%04lx);\n", addr, (u_long) 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%04lx);\n", addr, (u_long) 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 %lu\n", (u_long) count);
+ for (i = 0; i < count; i++)
+ {
+ DBG (15, " %04lx:%02x", (u_long) (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 %lu\n", (u_long) count);
+ for (i = 0; i < count; i++)
+ {
+ status = read_byte (fd, addr + i, &val);
+ ((byte *) dst)[i] = val;
+ DBG (15, " %04lx:%02x", (u_long) (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;
+}