diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-10-06 14:00:40 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-10-06 14:00:40 +0200 |
commit | 6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch) | |
tree | 2e301d871bbeeb44aa57ff9cc070fcf3be484487 /backend/mustek_pp_cis.c |
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'backend/mustek_pp_cis.c')
-rw-r--r-- | backend/mustek_pp_cis.c | 2854 |
1 files changed, 2854 insertions, 0 deletions
diff --git a/backend/mustek_pp_cis.c b/backend/mustek_pp_cis.c new file mode 100644 index 0000000..026734f --- /dev/null +++ b/backend/mustek_pp_cis.c @@ -0,0 +1,2854 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2001-2003 Eddy De Greef <eddy_de_greef at scarlet dot be> + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + + This file implements a SANE backend for Mustek PP flatbed _CIS_ scanners. +*/ + +/* + Global picture + + Mustek_PP_handle -> Mustek_PP_dev + -> priv = Mustek_PP_CIS_dev -> CIS +*/ + +/* + * This flag determines whether the scanner uses fast skipping at high + * resolutions. It is possible that this fast skipping introduces + * inaccuracies. It if turns out to be a problem, fast skipping can + * be disabled by setting this flag to 0. + */ +#define MUSTEK_PP_CIS_FAST_SKIP 1 +#define MUSTEK_PP_CIS_WAIT_BANK 200 + +/* + * These parameters determine where the scanable area starts at the top. + * If there is a consistent offset error, you can tune it through these + * parameters. Note that an inaccuracy in the order of 1 mm seems to be + * normal for the Mustek 600/1200 CP series. + */ +#define MUSTEK_PP_CIS_600CP_DEFAULT_SKIP 250 +#define MUSTEK_PP_CIS_1200CP_DEFAULT_SKIP 330 + +/* + * Number of scan lines on which the average is taken to determine the + * maximum number of color levels. + */ +#define MUSTEK_PP_CIS_AVERAGE_COUNT 32 + +#define MUSTEK_PP_CIS600 1 +#define MUSTEK_PP_CIS1200 2 +#define MUSTEK_PP_CIS1200PLUS 3 + +#define MUSTEK_PP_CIS_CHANNEL_RED 0 +#define MUSTEK_PP_CIS_CHANNEL_GREEN 1 +#define MUSTEK_PP_CIS_CHANNEL_BLUE 2 +#define MUSTEK_PP_CIS_CHANNEL_GRAY 1 + +#define MUSTEK_PP_CIS_MAX_H_PIXEL 5118 +#define MUSTEK_PP_CIS_MAX_V_PIXEL 7000 + +#define MUSTEK_PP_CIS_MOTOR_REVERSE 0 + +#include "../include/sane/config.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <math.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#include "../include/sane/sane.h" +#include "../include/sane/sanei_pa4s2.h" +#define DEBUG_DECLARE_ONLY +#include "mustek_pp.h" +#include "mustek_pp_decl.h" +#include "mustek_pp_cis.h" + +/****************************************************************************** + ****************************************************************************** + *** MA1015 chipset related functionality *** + ****************************************************************************** + *****************************************************************************/ + +/* + These defines control some debugging functionality + + #define M1015_TRACE_REGS -> trace the status of the internal registers + #define M1015_LOG_HL -> create a high-level log file (register-level) + #define M1015_LOG_LL -> create a low-level log file (byte-level) + + By default, all logging/tracing is turned off. +*/ + +/****************************************************************************** + * Low level logging: logs read and writes at the byte level, similar to + * the sequences produced by tool of Jochen Eisinger + * for analysing the TWAIN driver communication. + * This simplifies comparison of the sequences. + *****************************************************************************/ +#ifdef M1015_LOG_LL + + static FILE* M1015_LOG_1; + + #define M1015_START_LL\ + M1015_LOG_1 = fopen("cis_ll.log", "w"); + + #define M1015_STOP_LL\ + fclose(M1015_LOG_1); + + #define SANEI_PA4S2_WRITEBYTE(fd, reg, val)\ + do\ + {\ + sanei_pa4s2_writebyte (fd, reg, val);\ + fprintf(M1015_LOG_1, "\tsanei_pa4s2_writebyte(fd, %d, 0x%02X);\n", \ + reg, val);\ + } while (0) + + static const char* cis_last_rreg_name; + static int cis_read_count; + + #define SANEI_PA4S2_READBEGIN(fd, reg)\ + do\ + {\ + cis_last_rreg_name = Mustek_PP_1015_reg_r_name(reg);\ + cis_read_count = 0;\ + sanei_pa4s2_readbegin(fd, reg);\ + } while (0) + + #define SANEI_PA4S2_READBYTE(fd, val)\ + do\ + {\ + sanei_pa4s2_readbyte(fd, val);\ + ++cis_read_count;\ + } while (0) + + #define SANEI_PA4S2_READEND(fd)\ + do\ + {\ + sanei_pa4s2_readend(fd);\ + fprintf(M1015_LOG_1, "\tread_reg(%s, %d);\n", \ + cis_last_rreg_name, cis_read_count);\ + } while (0) + + #define M1015_MARK_LL(info)\ + fprintf(M1015_LOG_1, "* %s\n", info); + +#else /* M1015_LOG_LL */ + + #define M1015_START_LL + #define M1015_STOP_LL + + #define SANEI_PA4S2_WRITEBYTE(fd, reg, val)\ + sanei_pa4s2_writebyte (fd, reg, val) + + #define SANEI_PA4S2_READBEGIN(fd, reg)\ + sanei_pa4s2_readbegin(fd, reg) + + #define SANEI_PA4S2_READBYTE(fd, val)\ + sanei_pa4s2_readbyte(fd, val) + + #define SANEI_PA4S2_READEND(fd)\ + sanei_pa4s2_readend(fd) + + #define M1015_MARK_LL(info) + +#endif /* M1015_LOG_LL */ + + +/****************************************************************************** + * High-level logging: traces the flow of the driver in a hierarchical way + * up to the level of register acccesses. + *****************************************************************************/ +#ifdef M1015_LOG_HL + + static FILE* M1015_LOG_2; + static char hl_prev_line[4096], hl_next_line[4096], hl_repeat_count; + + /* + * A few variables for hierarchical log message indentation. + */ + + static const char* cis_indent_start = + " "; + static const char* cis_indent; + static const char* cis_indent_end; + + #define M1015_START_HL\ + M1015_LOG_2 = fopen("cis_hl.log", "w");\ + cis_indent = cis_indent_start + strlen(cis_indent_start);\ + cis_indent_end = cis_indent;\ + hl_prev_line[0] = 0;\ + hl_next_line[0] = 0;\ + hl_repeat_count = 0; + + #define M1015_FLUSH_HL\ + if (strcmp(hl_prev_line, hl_next_line))\ + {\ + fprintf(M1015_LOG_2, &hl_prev_line[0]);\ + strcpy(&hl_prev_line[0], &hl_next_line[0]);\ + if (hl_repeat_count != 0)\ + {\ + fprintf(M1015_LOG_2, "%s [last message repeated %d times]\n",\ + cis_indent, hl_repeat_count+1); \ + }\ + hl_repeat_count = 0;\ + }\ + else\ + {\ + hl_repeat_count += 1;\ + } + + #define M1015_MARK(info)\ + sprintf(&hl_next_line[0], "%s+ %s\n", cis_indent, info);\ + M1015_FLUSH_HL + + #define M1015_STOP_HL\ + hl_next_line[0] = 0;\ + M1015_FLUSH_HL\ + fclose(M1015_LOG_2); + +#else /* M1015_LOG_HL */ + + #define M1015_START_HL + #define M1015_STOP_HL + #define M1015_MARK(info) + #define M1015_FLUSH_HL + +#endif /* M1015_LOG_HL */ + +#ifdef M1015_TRACE_REGS + #define M1015_DISPLAY_REGS(dev, msg) Mustek_PP_1015_display_regs(dev, msg) + #define M1015_DISPLAY_REG(msg, val) Mustek_PP_1015_display_reg(msg, val) +#else + #define M1015_DISPLAY_REGS(dev, msg) + #define M1015_DISPLAY_REG(msg, val) +#endif + + +#if defined (M1015_LOG_HL) || defined (M1015_LOG_LL) +static const char* +Mustek_PP_1015_reg_r_name(Mustek_PP_1015R_reg id) +{ + static const char* names[4] = { "ASIC", "SCAN_VAL", "MOTOR", "BANK_COUNT" }; + return names[id & 0x03]; +} + +static const char* +Mustek_PP_1015_bit_name(Mustek_PP_1015R_bit id) +{ + static const char* names[4] = { "????", "MOTOR_HOME", "????", "MOTOR_BUSY" }; + return names[id & 0x03]; +} + +static const char* +Mustek_PP_1015_reg_w_name(Mustek_PP_1015R_reg id) +{ + static const char* names[4][4] = + { + { "RED_REF", "GREEN_REF", "BLUE_REF", "DPI_CONTROL" }, + { "BYTE_COUNT_HB", "BYTE_COUNT_LB", "SKIP_COUNT", "EXPOSE_TIME" }, + { "SRAM_SOURCE_PC", "MOTOR_CONTROL", "UNKNOWN_42", "UNKNOWN_82" }, + { "POWER_ON_DELAY", "CCD_TIMING", "CCD_TIMING_ADJ", "RIGHT_BOUND" } + }; + return names[(id & 0x30) >> 4][id & 0x03]; +} +#endif + +/****************************************************************************** + * Converts a register value to a hex/dec/bin representation. + *****************************************************************************/ +static const char* +Mustek_PP_1015_show_val(int val) +{ + /* + Since we use a static temporary buffer, we must make sure that the + buffer isn't altered while it is still in use (typically because + more than one value is converted in a printf statement). + Therefore the buffer is organized as a ring buffer. If should contain + at least 21 elements in order to be able to display all registers + with one printf statement. + */ + #define Mustek_PP_1015_RING_BUFFER_SIZE 50 + static char buf[Mustek_PP_1015_RING_BUFFER_SIZE][64]; + static int index = 0; + int i; + char* current = (char*)buf[index++]; + + if (index >= Mustek_PP_1015_RING_BUFFER_SIZE) index = 0; + + if (val < 0) + { + /* The register has not been initialized yet. */ + sprintf(current, "---- (---) --------"); + } + else + { + sprintf(current, "0x%02X (%3d) ", val & 0xFF, val & 0xFF); + for (i=0; i<8; ++i) + { + sprintf(current+11+i, "%d", (val >> (7-i)) & 1); + } + } + return current; +} + +#ifdef M1015_TRACE_REGS +/****************************************************************************** + * Displays the contents of all registers of the scanner on stderr. + *****************************************************************************/ +static void +Mustek_PP_1015_display_regs(Mustek_PP_CIS_dev * dev, const char* info) +{ + /* + * Register naming convention: + * Rx : read-only register no. x + * ByWx : write-only register no. x of bank no. y + */ + + fprintf(stderr, + "\n" + "Register status: %s\n" + "\n" + " R0: %s : ASIC info\n" + " R1: %s : scan value\n" + " R2: %s : CCD/motor info\n" + " R3: %s : bank count\n" + "\n" + " B0W0: %s : red reference\n" + " B0W1: %s : green reference\n" + " B0W2: %s : blue reference\n" + " B0W3: %s : DPI control\n" + "\n" + " B1W0: %s : byte count, high byte\n" + " B1W1: %s : byte count, low byte\n" + " B1W2: %s : skip x32 pixels\n" + " B1W3: %s : expose time (CCDWIDTH)\n" + "\n" + " B2W0: %s : SRAM source PC\n" + " B2W1: %s : motor control\n" + " B2W2: %s : -\n" + " B2W3: %s : -\n" + "\n" + " B3W0: %s : power on delay\n" + " B3W1: %s : CCD timing - always 0x05\n" + " B3W2: %s : CCD timing adjust - always 0x00\n" + " B3W3: %s : right bound (not used)\n" + "\n" + " CHAN: %s : channel [%s]\n" + "\n", + info, + Mustek_PP_1015_show_val (dev->CIS.regs.in_regs[0]), + Mustek_PP_1015_show_val (dev->CIS.regs.in_regs[1]), + Mustek_PP_1015_show_val (dev->CIS.regs.in_regs[2]), + Mustek_PP_1015_show_val (dev->CIS.regs.in_regs[3]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[0][0]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[0][1]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[0][2]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[0][3]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[1][0]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[1][1]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[1][2]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[1][3]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[2][0]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[2][1]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[2][2]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[2][3]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[3][0]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[3][1]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[3][2]), + Mustek_PP_1015_show_val (dev->CIS.regs.out_regs[3][3]), + Mustek_PP_1015_show_val (dev->CIS.regs.channel), + (dev->CIS.regs.channel == 0x80 ? "RED" : + (dev->CIS.regs.channel == 0x40 ? "GREEN" : + (dev->CIS.regs.channel == 0xC0 ? "BLUE" : "unknown"))) + ); +} + +/****************************************************************************** + * Displays a single register value + *****************************************************************************/ +static void +Mustek_PP_1015_display_reg(const char* info, int val) +{ + fprintf (stderr, "%s: %s\n", info, Mustek_PP_1015_show_val(val)); +} + +#endif /* M1015_TRACE_REGS */ + + +/****************************************************************************** + * + * Reads one of the 4 internal registers of the scanner + * + * 0: ASIC identification + * 1: scan values + * 2: CCD info / motor info + * 3: bank count info + * + *****************************************************************************/ +static SANE_Byte +Mustek_PP_1015_read_reg(Mustek_PP_CIS_dev * dev, Mustek_PP_1015R_reg reg) +{ + SANE_Byte tmp; + assert(reg <= 3); + + SANEI_PA4S2_READBEGIN (dev->desc->fd, reg & 0x03); + SANEI_PA4S2_READBYTE (dev->desc->fd, &tmp); + SANEI_PA4S2_READEND (dev->desc->fd); + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s read_reg(%s); [%s]\n", cis_indent, + Mustek_PP_1015_reg_r_name(reg), Mustek_PP_1015_show_val(tmp)); + M1015_FLUSH_HL; +#endif + +#ifdef M1015_TRACE_REGS + dev->CIS.regs.in_regs[reg & 0x03] = tmp; +#endif + + return tmp; +} + +/****************************************************************************** + * + * Waits for a bit of register to become 1 or 0. The period of checking can be + * controlled through the sleep parameter (microseconds). + * + *****************************************************************************/ +static SANE_Bool +Mustek_PP_1015_wait_bit(Mustek_PP_CIS_dev * dev, Mustek_PP_1015R_reg reg, + Mustek_PP_1015R_bit bit, SANE_Bool on, unsigned period) +{ + SANE_Byte tmp; + SANE_Byte mask, val; + int tries = 0; + + assert(reg <= 3); + assert(bit <= 3); + + mask = 1 << bit; + + /* We don't want to wait forever */ + while (dev->desc->state != STATE_CANCELLED) + { +#if defined (M1015_LOG_LL) || defined (M1015_LOG_HL) + ++tries; +#endif + + sanei_pa4s2_readbegin (dev->desc->fd, reg & 0x03); + sanei_pa4s2_readbyte (dev->desc->fd, &tmp); + sanei_pa4s2_readend (dev->desc->fd); + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s wait_bit(%s, %s, %d): %s %s;\n", cis_indent, + Mustek_PP_1015_reg_r_name(reg), Mustek_PP_1015_bit_name(bit), + on?1:0, Mustek_PP_1015_show_val(mask), Mustek_PP_1015_show_val(tmp)); + M1015_FLUSH_HL; +#endif + val = ((on == SANE_TRUE) ? tmp : ~tmp ) & mask; + + if (val != 0) break; + + if (period) usleep(period); + + if (tries > 50000) + { +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s wait_bit(%s, %s, %d): failed;\n", cis_indent, + Mustek_PP_1015_reg_r_name(reg), Mustek_PP_1015_bit_name(bit), on?1:0); + M1015_FLUSH_HL; +#endif + DBG(2, "Mustek_PP_1015_wait_bit: failed (reg %d, bit %d, on: %d)\n", + reg, bit, on?1:0); + return SANE_FALSE; + } + } + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s wait_bit(%s, %s, %d);\n", cis_indent, + Mustek_PP_1015_reg_r_name(reg), Mustek_PP_1015_bit_name(bit), on?1:0); + M1015_FLUSH_HL; +#endif +#ifdef M1015_LOG_LL + fprintf(M1015_LOG_1, "\tread_reg(%s, %d);\n", Mustek_PP_1015_reg_r_name(reg), + tries); +#endif + +#ifdef M1015_TRACE_REGS + dev->CIS.regs.in_regs[reg & 0x03] = tmp; +#endif + return dev->desc->state != STATE_CANCELLED ? SANE_TRUE : SANE_FALSE; +} + +/****************************************************************************** + * + * Writes one out of 4 registers of one of the 4 register banks (I guess) + * + * Bank 0 + * 0: voltage red --+ + * 1: voltage green +-> always set to 0x96 + * 2: voltage blue --+ + * 3: DPI control + * + * Bank 1 + * 0: line adjust (?) - high byte + * 1: line adjust (?) - low byte + * 2: unknown (values seen: 0x00, 0x02, 0x03, 0x1D) + * 3: expose time (?) (values seen: 0xAA, 0xFD, 0xFE, 0xFF) + * + * Bank 2 + * 0: unknown, used to start linear sequence during calibration + * 1: motor control code (forward, return home, ...) + * 2: never used + * 3: never used + * + * Bank 3 + * 0: reduction factor (16bit internal -> 8bit) -> target for calibration + * 1: unknown -> always set to 0x05 + * 2: unknown -> always set to 0x00 + * 3: never used + * + *****************************************************************************/ + +static void +Mustek_PP_1015_write_reg(Mustek_PP_CIS_dev * dev, Mustek_PP_1015W_reg reg, SANE_Byte val) +{ + + SANE_Byte regBank = (reg & 0xF0) >> 4; + SANE_Byte regNo = (reg & 0x0F); + + assert (regNo <= 3); + assert (regBank <= 3); + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (4+regNo))+ regBank); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, regBank); + +#ifdef M1015_TRACE_REGS + dev->CIS.regs.out_regs[regBank][regNo] = val; +#endif + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s write_reg(%s, 0x%02X);\n", cis_indent, + Mustek_PP_1015_reg_w_name(reg), val); + M1015_FLUSH_HL; +#endif +} + +/****************************************************************************** + * + * Writes 2 values to 2 adjecent registers. + * It is probably equivalent to 2 simple write operations (but I'm not sure). + * + * val1 is written to register[regNo] + * val2 is written to register[regNo+1] + * + *****************************************************************************/ +static void +Mustek_PP_1015_write_reg2(Mustek_PP_CIS_dev * dev, Mustek_PP_1015W_reg reg, + SANE_Byte val1, SANE_Byte val2) +{ + SANE_Byte regBank = (reg & 0xF0) >> 4; + SANE_Byte regNo = (reg & 0x0F); + + assert (regNo <= 2); + assert (regBank <= 3); + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (4+regNo))+ regBank); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val1); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (5+regNo))+ regBank); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val2); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, regBank); + +#ifdef M1015_TRACE_REGS + dev->CIS.regs.out_regs[regBank][regNo] = val1; + dev->CIS.regs.out_regs[regBank][regNo+1] = val2; +#endif + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s write_reg2(%s, 0x%02X, 0x%02X);\n", + cis_indent, Mustek_PP_1015_reg_w_name(reg), val1, val2); + M1015_FLUSH_HL; +#endif +} + +/****************************************************************************** + * + * Writes 3 values to 3 adjecent registers. + * It is probably equivalent to 3 simple write operations (but I'm not sure). + * + * val1 is written to register[regNo] + * val2 is written to register[regNo+1] + * val3 is written to register[regNo+2] + * + *****************************************************************************/ +static void +Mustek_PP_1015_write_reg3(Mustek_PP_CIS_dev * dev, Mustek_PP_1015W_reg reg, + SANE_Byte val1, SANE_Byte val2, SANE_Byte val3) +{ + SANE_Byte regBank = (reg & 0xF0) >> 4; + SANE_Byte regNo = (reg & 0x0F); + + assert (regNo <= 1); + assert (regBank <= 3); + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (4+regNo))+ regBank); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val1); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (5+regNo))+ regBank); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val2); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (6+regNo))+ regBank); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val3); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, regBank); + +#ifdef M1015_TRACE_REGS + dev->CIS.regs.out_regs[regBank][regNo ] = val1; + dev->CIS.regs.out_regs[regBank][regNo+1] = val2; + dev->CIS.regs.out_regs[regBank][regNo+2] = val3; +#endif + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s write_reg3(%s, 0x%02X, 0x%02X, 0x%02X);\n", + cis_indent, Mustek_PP_1015_reg_w_name(reg), val1, val2, val3); + M1015_FLUSH_HL; +#endif +} + +/****************************************************************************** + * Opens a register for a (series of) write operation(s). + *****************************************************************************/ +static void +Mustek_PP_1015_write_reg_start(Mustek_PP_CIS_dev * dev, Mustek_PP_1015W_reg reg) +{ + SANE_Byte regBank = (reg & 0xF0) >> 4; + SANE_Byte regNo = (reg & 0x0F); + + assert (regNo <= 3); + assert (regBank <= 3); + + dev->CIS.regs.current_write_reg = reg; + +#ifdef M1015_LOG_HL + dev->CIS.regs.write_count = 0; +#endif + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, (1 << (4+regNo))+ regBank); +} + +/****************************************************************************** + * Writes a value to the currently open register. + *****************************************************************************/ +static void +Mustek_PP_1015_write_reg_val(Mustek_PP_CIS_dev * dev, SANE_Byte val) +{ +#ifdef M1015_TRACE_REGS + SANE_Byte regBank = (dev->CIS.regs.current_write_reg & 0xF0) >> 4; + SANE_Byte regNo = (dev->CIS.regs.current_write_reg & 0x0F); + + assert (regNo <= 3); + assert (regBank <= 3); + + dev->CIS.regs.out_regs[regBank][regNo] = val; +#endif + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 5, val); + +#ifdef M1015_LOG_HL + ++dev->CIS.regs.write_count; +#endif +} + +/****************************************************************************** + * Closes a register after a (series of) write operation(s). + *****************************************************************************/ +static void +Mustek_PP_1015_write_reg_stop(Mustek_PP_CIS_dev * dev) +{ + SANE_Byte regBank = (dev->CIS.regs.current_write_reg & 0xF0) >> 4; +#ifdef M1015_LOG_HL + SANE_Byte regNo = (dev->CIS.regs.current_write_reg & 0x0F); + assert (regNo <= 3); + + sprintf(&hl_next_line[0], "%s write_reg_multi(%s, *%d);\n", cis_indent, + Mustek_PP_1015_reg_w_name(dev->CIS.regs.current_write_reg), + dev->CIS.regs.write_count); + M1015_FLUSH_HL; +#endif + assert (regBank <= 3); + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, regBank); +} + +/****************************************************************************** + * + * Sends a command to the scanner. The command should not access one of the + * internal registers, ie., the 3rd bit should not be zero. + * + *****************************************************************************/ +static void +Mustek_PP_1015_send_command(Mustek_PP_CIS_dev * dev, SANE_Byte command) +{ + assert (command & 0x04); + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, command); + +#ifdef M1015_LOG_HL + sprintf(&hl_next_line[0], "%s send_command(0x%02X);\n", cis_indent, command); + M1015_FLUSH_HL; +#endif +} + +/****************************************************************************** + ############################################################################## + ## CIS driver ## + ############################################################################## + *****************************************************************************/ + +/****************************************************************************** + * Resolution conversion functions + *****************************************************************************/ +static int +max2hw_hres(Mustek_PP_CIS_dev *dev, int dist) +{ + return (int)((dist * dev->CIS.hw_hres) / dev->desc->dev->maxres + 0.5); +} + +#ifdef NOT_USED +static int +max2hw_vres(Mustek_PP_CIS_dev *dev, int dist) +{ + return (int)((dist * dev->CIS.hw_vres) / dev->desc->dev->maxres + 0.5); +} +#endif + +static int +max2cis_hres(Mustek_PP_CIS_dev *dev, int dist) +{ + return (int)((dist * dev->CIS.cisRes) / dev->desc->dev->maxres + 0.5); +} + +static int +cis2max_res(Mustek_PP_CIS_dev *dev, int dist) +{ + return (int)((dist * dev->desc->dev->maxres) / dev->CIS.cisRes + 0.5); +} + +#ifdef NOT_USED +static int +hw2max_vres(Mustek_PP_CIS_dev *dev, int dist) +{ + return (int)((dist * dev->desc->dev->maxres) / dev->CIS.hw_vres + 0.5); +} +#endif + +/****************************************************************************** + * Attempts to extract the current bank no. + *****************************************************************************/ +static void +cis_get_bank_count(Mustek_PP_CIS_dev *dev) +{ + dev->bank_count = (Mustek_PP_1015_read_reg(dev, MA1015R_BANK_COUNT) & 0x7); + if (dev->CIS.use8KBank) dev->bank_count >>= 1; +} + +/****************************************************************************** + * Triggers a bank switch (I assume). + *****************************************************************************/ +static void +cis_set_sti(Mustek_PP_CIS_dev *dev) +{ + SANEI_PA4S2_WRITEBYTE(dev->desc->fd, 3, 0xFF); + dev->bank_count++; + dev->bank_count &= (dev->CIS.use8KBank == SANE_TRUE) ? 3 : 7; +} + +/****************************************************************************** + * Wait till the bank with a given number becomes available. + *****************************************************************************/ +static SANE_Bool +cis_wait_bank_change (Mustek_PP_CIS_dev * dev, int bankcount) +{ + struct timeval start, end; + unsigned long diff; + int firsttime = 1; + + gettimeofday (&start, NULL); + + do + { + if (1 /*niceload*/) + { + if (firsttime) + firsttime = 0; + else + usleep (10); /* for a little nicer load */ + } + cis_get_bank_count (dev); + + gettimeofday (&end, NULL); + diff = (end.tv_sec * 1000 + end.tv_usec / 1000) - + (start.tv_sec * 1000 + start.tv_usec / 1000); + + } + while ((dev->bank_count != bankcount) && (diff < MUSTEK_PP_CIS_WAIT_BANK)); + + if (dev->bank_count != bankcount && dev->desc->state != STATE_CANCELLED) + { + u_char tmp; + tmp = Mustek_PP_1015_read_reg(dev, 3); + DBG(2, "cis_wait_bank_change: Missed a bank: got %d [%s], " + "wanted %d, waited %d msec\n", + dev->bank_count, Mustek_PP_1015_show_val(tmp), bankcount, + MUSTEK_PP_CIS_WAIT_BANK); + } + + return dev->bank_count == bankcount ? SANE_TRUE : SANE_FALSE; +} + +/****************************************************************************** + * Configure the CIS for a given resolution. + * + * CIS scanners seem to have 2 modes: + * + * low resolution (50-300 DPI) and + * high resolution (300-600 DPI). + * + * Depending on the resolution requested by the user, the scanner is used + * in high or low resolution mode. In high resolution mode, the motor step + * sizes are also reduced by a factor of two. + * + *****************************************************************************/ +static void +cis_set_dpi_value (Mustek_PP_CIS_dev * dev) +{ + u_char val = 0; + + if (dev->model == MUSTEK_PP_CIS1200PLUS) + { + /* Toshiba CIS: only 600 DPI + decimation */ + switch (dev->CIS.hw_hres) + { + case 75: + val = 0x48; /* 1/8 */ + break; + case 100: + val = 0x08; /* 1/6 */ + break; + case 200: + val = 0x00; /* 1/3 */ + break; + case 300: + val = 0x50; /* 2/4 */ + break; + case 400: + val = 0x10; /* 2/3 */ + break; + case 600: + val = 0x20; /* 3/3 */ + break; + default: + assert (0); + } + } + else + { + /* Canon CIS: sensor can use 300 or 600 DPI */ + switch (dev->CIS.hw_hres) + { + case 50: + val = 0x08; /* 1/6 */ + break; + case 100: + val = 0x00; /* 1/3 */ + break; + case 200: + val = 0x10; /* 2/3 */ + break; + case 300: + val = 0x20; /* 3/3 */ + break; + case 400: + val = 0x10; /* 2/3 */ + break; + case 600: + val = 0x20; /* 3/3 */ + break; + default: + assert (0); + } + } + + Mustek_PP_1015_write_reg(dev, MA1015W_DPI_CONTROL, val | 0x04); + + DBG (4, "cis_set_dpi_value: dpi: %d -> value 0x%02x\n", dev->CIS.hw_hres, val); +} + +static void +cis_set_ccd_channel (Mustek_PP_CIS_dev * dev) +{ + + SANE_Byte codes[] = { 0x84, 0x44, 0xC4 }; + SANE_Byte chancode; + + assert (dev->CIS.channel < 3); + + chancode = codes[dev->CIS.channel]; + + /* + The TWAIN driver sets an extra bit in lineart mode. + When I do this too, I don't see any effect on the image. + Moreover, for 1 resolution, namely 400 dpi, the bank counter seems + to behave strangely, and the synchronization get completely lost. + I guess the software conversion from gray to lineart is good enough, + so I'll leave it like that. + + if (dev->CIS.setParameters) + { + chancode |= (dev->desc->mode == MODE_BW) ? 0x20: 0; + } + */ + + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, chancode); + +#ifdef M1015_TRACE_REGS + dev->CIS.regs.channel = chancode; +#endif +} + +static void +cis_config_ccd (Mustek_PP_CIS_dev * dev) +{ + SANE_Int skipCount, byteCount; + + if (dev->CIS.res != 0) + dev->CIS.hres_step = + SANE_FIX ((float) dev->CIS.hw_hres / (float) dev->CIS.res); + + /* CIS: <= 300 dpi -> 0x86 + > 300 dpi -> 0x96 */ + + if (dev->CIS.cisRes == 600) + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, 0x96); + else + SANEI_PA4S2_WRITEBYTE (dev->desc->fd, 6, 0x86); + + cis_set_dpi_value(dev); + + if (dev->CIS.setParameters) + { + dev->CIS.channel = dev->desc->mode == MODE_COLOR ? + MUSTEK_PP_CIS_CHANNEL_RED : MUSTEK_PP_CIS_CHANNEL_GRAY; + } + else + { + dev->CIS.channel = MUSTEK_PP_CIS_CHANNEL_GRAY; + } + + cis_set_ccd_channel (dev); + + Mustek_PP_1015_write_reg (dev, MA1015W_POWER_ON_DELAY, 0xAA); + Mustek_PP_1015_write_reg (dev, MA1015W_CCD_TIMING, 0x05); + Mustek_PP_1015_write_reg (dev, MA1015W_CCD_TIMING_ADJ, 0x00); + + Mustek_PP_1015_send_command (dev, 0x45); /* or 0x05 for no 8kbank */ + + /* + * Unknown sequence. + * Seems to be always the same during configuration, independent of the + * mode and the resolution. + */ + CIS_CLEAR_FULLFLAG(dev); + CIS_INC_READ(dev); + CIS_CLEAR_READ_BANK(dev); + CIS_CLEAR_WRITE_ADDR(dev); + CIS_CLEAR_WRITE_BANK(dev); + CIS_CLEAR_TOGGLE(dev); + + /* + # SkipImage = expressed in max resolution (600 DPI) + # + # Formulas + # + # <= 300 DPI: + # + # Skip = 67 + skipimage/2 + # + # Skip1 = Skip / 32 + # Skip2 = Skip % 32 + # + # Bytes = Skip2 * hw_hres/300 + (imagebytes * hw_hres/res) + 2 + # + # > 300 DPI + # + # Skip = 67 + skipimage + # + # Skip1 = Skip / 32 + # Skip2 = Skip % 32 + # + # Bytes = Skip2*hw_hres/600 + (imagebytes * hw_hres/res) + 2 + # + */ + + skipCount = 67; /* Hardware parameter - fixed */ + + if (dev->CIS.setParameters == SANE_TRUE) + { + /* + * It seems that the TWAIN driver always adds 2 mm extra. When I do the + * inverse calculation from the parameters that driver sends, I always + * get a difference of exactly 2mm, at every resolution and for + * different positions of the scan area. Moreover, when I don't add this + * offset, the resulting scan seems to start 2mm to soon. + * I can't find this back in the backend of the TWAIN driver, but I + * assume that this 2mm offset is taken care off at the higher levels. + */ + DBG(4, "cis_config_ccd: Skip count: %d\n", skipCount); + skipCount += max2cis_hres(dev, dev->CIS.skipimagebytes); + DBG(4, "cis_config_ccd: Skip count: %d (cis res: %d)\n", skipCount, + dev->CIS.cisRes); + skipCount += (int)(2.0/25.4*dev->CIS.cisRes); + DBG(4, "cis_config_ccd: Skip count: %d\n", skipCount); + + Mustek_PP_1015_write_reg (dev, MA1015W_SKIP_COUNT, skipCount / 32); + DBG(4, "cis_config_ccd: Skip count: %d (x32)\n", skipCount / 32); + } + else + { + Mustek_PP_1015_write_reg (dev, MA1015W_SKIP_COUNT, 0); + DBG(4, "cis_config_ccd: Skip count: 67 (x32)\n"); + } + + skipCount %= 32; + skipCount = cis2max_res(dev, skipCount); /* Back to max res */ + + Mustek_PP_1015_write_reg(dev, MA1015W_EXPOSE_TIME, dev->CIS.exposeTime); + + DBG(4, "cis_config_ccd: skipcount: %d imagebytes: %d\n", skipCount, dev->CIS.imagebytes); + /* set_initial_skip_1015 (dev); */ + if (dev->CIS.setParameters == SANE_TRUE) + { + Mustek_PP_1015_write_reg(dev, MA1015W_EXPOSE_TIME, dev->CIS.exposeTime); + Mustek_PP_1015_write_reg(dev, MA1015W_POWER_ON_DELAY, 0xAA); + /* The TWAIN drivers always sends the same value: 0x96 */ + Mustek_PP_1015_write_reg3(dev, MA1015W_RED_REF, 0x96, 0x96, 0x96); + dev->CIS.adjustskip = max2hw_hres(dev, skipCount); + byteCount = max2hw_hres(dev, skipCount + dev->CIS.imagebytes) + 2; + dev->CIS.setParameters = SANE_FALSE; + } + else + { + dev->CIS.adjustskip = 0; + byteCount = max2hw_hres(dev, skipCount); + } + DBG(4, "cis_config_ccd: adjust skip: %d bytecount: %d\n", + dev->CIS.adjustskip, byteCount); + + Mustek_PP_1015_write_reg2(dev, MA1015W_BYTE_COUNT_HB, + byteCount >> 8, byteCount & 0xFF); + + cis_get_bank_count (dev); + DBG(5, "cis_config_ccd: done\n"); +} + +static SANE_Bool +cis_wait_motor_stable (Mustek_PP_CIS_dev * dev) +{ + static struct timeval timeoutVal; + SANE_Bool ret = + Mustek_PP_1015_wait_bit (dev, MA1015R_MOTOR, MA1015B_MOTOR_STABLE, + SANE_FALSE, 0); +#ifdef HAVE_SYS_SELECT_H + if (dev->engine_delay > 0) + { + timeoutVal.tv_sec = 0; + timeoutVal.tv_usec = dev->engine_delay*1000; + select(0, NULL, NULL, NULL, &timeoutVal); + } +#endif + return ret; +} + +static void +cis_motor_forward (Mustek_PP_CIS_dev * dev) +{ + SANE_Byte control; + + if (dev->model == MUSTEK_PP_CIS600) + { + switch (dev->CIS.hw_vres) + { + case 150: + control = 0x7B; + break; + case 300: + control = 0x73; + break; + case 600: + control = 0x13; + break; + default: + exit(1); + } + } + else + { + switch (dev->CIS.hw_vres) + { + case 300: + control = 0x7B; + break; + case 600: + control = 0x73; + break; + case 1200: + control = 0x13; + break; + default: + exit(1); + } + } + +#if MUSTEK_PP_CIS_MOTOR_REVERSE == 1 + control ^= 0x10; +#endif + + DBG(4, "cis_motor_forward: @%d dpi: 0x%02X.\n", dev->CIS.hw_vres, control); + if (!cis_wait_motor_stable (dev)) + return; + + Mustek_PP_1015_write_reg(dev, MA1015W_MOTOR_CONTROL, control); +} + +static void +cis_move_motor (Mustek_PP_CIS_dev * dev, SANE_Int steps) /* steps @ maxres */ +{ + /* Note: steps is expressed at maximum resolution */ + SANE_Byte fullStep = 0x13, biStep = 0x73, quadStep = 0x7B; + SANE_Int fullSteps, biSteps, quadSteps; + /* + * During a multi-step feed, the expose time is fixed. The value depends + * on the type of the motor (600/1200 CP) + */ + SANE_Byte savedExposeTime = dev->CIS.exposeTime; + dev->CIS.exposeTime = 85; + + DBG(4, "cis_move_motor: Moving motor %d steps.\n", steps); + + /* Just in case ... */ + if (steps < 0) + { + DBG(1, "cis_move_motor: trying to move negative steps: %d\n", steps); + steps = 0; /* We must go through the configuration procedure */ + } + + /* + * Using the parameter settings for the 600 CP on a 1200 CP scanner + * doesn't work: the engine doesn't move and makes a sharp noise, which + * doesn't sound too healthy. It could be harmful to the motor ! + * Apparently, the same happens on a real 600 CP (reported by Disma + * Goggia), so it's probably better to always use the 1200 CP settings. + */ + dev->CIS.exposeTime <<= 1; + cis_config_ccd(dev); + dev->CIS.exposeTime = savedExposeTime; + + /* + * This is a minor speed optimization: when we are using the high + * resolution mode, long feeds (eg, to move to a scan area at the bottom + * of the page) can be made almost twice as fast by using double motor + * steps as much as possible. + * It is possible, though, that fast skipping (which is the default) is + * not very accurate on some scanners. Therefore, the user can disable + * this through the configuration file. + */ + + fullSteps = steps & 1; + biSteps = steps >> 1; + if (dev->fast_skip) { + quadSteps = biSteps >> 1; + biSteps &= 1; + } + else { + quadSteps = 0; + } + + M1015_DISPLAY_REGS(dev, "Before move"); + +#if MUSTEK_PP_CIS_MOTOR_REVERSE == 1 + fullStep ^= 0x10; + biStep ^= 0x10; + quadStep ^= 0x10; +#endif + + DBG(4, "cis_move_motor: 4x%d 2x%d 1x%d\n", quadSteps, biSteps, fullSteps); + /* Note: the TWAIN driver opens the motor control register only + once before the loop, and closes it after the loop. I've tried this + too, but it resulted in inaccurate skip distances; therefore, the + motor control register is now opened and closed for each step. */ + + while (quadSteps-- > 0 && dev->desc->state != STATE_CANCELLED) + { + cis_wait_motor_stable (dev); + Mustek_PP_1015_write_reg(dev, MA1015W_MOTOR_CONTROL, quadStep); + } + + while (biSteps-- > 0 && dev->desc->state != STATE_CANCELLED) + { + cis_wait_motor_stable (dev); + Mustek_PP_1015_write_reg(dev, MA1015W_MOTOR_CONTROL, biStep); + } + + while (fullSteps-- > 0 && dev->desc->state != STATE_CANCELLED) + { + cis_wait_motor_stable (dev); + Mustek_PP_1015_write_reg(dev, MA1015W_MOTOR_CONTROL, fullStep); + } +} + +static void +cis_set_et_pd_sti (Mustek_PP_CIS_dev * dev) +{ + Mustek_PP_1015_write_reg(dev, MA1015W_EXPOSE_TIME, + dev->CIS.exposeTime); + Mustek_PP_1015_write_reg(dev, MA1015W_POWER_ON_DELAY, + dev->CIS.powerOnDelay[dev->CIS.channel]); + cis_set_ccd_channel (dev); + cis_set_sti (dev); +} + +/* + * Prepare the scanner for catching the next channel and, if necessary, + * move the head one step further. + */ +static SANE_Bool +cis_wait_next_channel (Mustek_PP_CIS_dev * dev) +{ + int moveAtChannel = dev->desc->mode == MODE_COLOR ? + MUSTEK_PP_CIS_CHANNEL_BLUE : MUSTEK_PP_CIS_CHANNEL_GRAY; + + if (!cis_wait_bank_change (dev, dev->bank_count)) + { + DBG(2, "cis_wait_next_channel: Could not get next bank.\n"); + return SANE_FALSE; + } + + moveAtChannel = (dev->desc->mode == MODE_COLOR) ? + MUSTEK_PP_CIS_CHANNEL_BLUE : MUSTEK_PP_CIS_CHANNEL_GRAY; + + if (dev->CIS.channel == moveAtChannel && !dev->CIS.dontMove) + { + cis_motor_forward (dev); + } + + cis_set_et_pd_sti (dev); + + if (dev->desc->mode == MODE_COLOR) + { + ++dev->CIS.channel; + dev->CIS.channel %= 3; + } + + return SANE_TRUE; +} + +/* + * Wait for the device to be ready for scanning. Cycles through the different + * channels and sets the parameters (only green channel in gray/lineart). + */ +static SANE_Bool +cis_wait_read_ready (Mustek_PP_CIS_dev * dev) +{ + int channel; + dev->CIS.dontIncRead = SANE_TRUE; + + dev->CIS.channel = dev->desc->mode == MODE_COLOR ? + MUSTEK_PP_CIS_CHANNEL_RED : MUSTEK_PP_CIS_CHANNEL_GRAY; + + for (channel = 0; channel < 3; ++channel) + { + if (!cis_wait_next_channel(dev)) return SANE_FALSE; + } + return SANE_TRUE; +} + +static int +delay_read (int delay) +{ + /* + * A (very) smart compiler may complete optimize the delay loop away. By + * adding some difficult data dependencies, we can try to prevent this. + */ + static int prevent_removal, i; + for (i = 0; i<delay; ++i) + { + prevent_removal = sqrt(prevent_removal+1.); /* Just waste some cycles */ + } + return prevent_removal; /* another data dependency */ +} + +/* +** Reads one line of pixels +*/ +static void +cis_read_line_low_level (Mustek_PP_CIS_dev * dev, SANE_Byte * buf, + SANE_Int pixel, SANE_Byte * calib_low, + SANE_Byte * calib_hi, SANE_Int * gamma) +{ + u_char color; + int ctr, skips = dev->CIS.adjustskip, cval; + int bpos = 0; + SANE_Byte low_val = 0, hi_val = 255; + + if (pixel <= 0) + return; + + SANEI_PA4S2_READBEGIN (dev->desc->fd, 1); + + while(skips-- >= 0) + { + if (dev->CIS.delay) delay_read(dev->CIS.delay); + SANEI_PA4S2_READBYTE (dev->desc->fd, &color); + } + + if (dev->CIS.hw_hres == dev->CIS.res) + { + /* One-to one mapping */ + DBG (6, "cis_read_line_low_level: one-to-one\n"); + for (ctr = 0; ctr < pixel; ctr++) + { + if (dev->CIS.delay) delay_read(dev->CIS.delay); + SANEI_PA4S2_READBYTE (dev->desc->fd, &color); + + cval = color; + + if (calib_low) { + low_val = calib_low[ctr] ; + } + + if (calib_hi) { + hi_val = calib_hi[ctr] ; + } + + cval -= low_val ; + cval <<= 8 ; + cval /= hi_val-low_val ; + + if (cval < 0) cval = 0; + else if (cval > 255) cval = 255; + + if (gamma) + cval = gamma[cval]; + + buf[ctr] = cval; + } + } + else if (dev->CIS.hw_hres > dev->CIS.res) + { + /* Sub-sampling */ + + int pos = 0; + DBG (6, "cis_read_line_low_level: sub-sampling\n"); + ctr = 0; + do + { + if (dev->CIS.delay) delay_read(dev->CIS.delay); + SANEI_PA4S2_READBYTE (dev->desc->fd, &color); + + cval = color; + if (ctr < (pos >> SANE_FIXED_SCALE_SHIFT)) + { + ctr++; + continue; + } + + ctr++; + pos += dev->CIS.hres_step; + + if (calib_low) { + low_val = calib_low[bpos] ; + } + + if (calib_hi) { + hi_val = calib_hi[bpos] ; + } + cval -= low_val ; + cval <<= 8 ; + cval /= hi_val-low_val ; + + if (cval < 0) cval = 0 ; + else if (cval > 255) cval = 255 ; + + if (gamma) cval = gamma[cval]; + + buf[bpos++] = cval; + } + while (bpos < pixel); + } + else + { + int calctr = 0; + SANE_Int pos = 0, nextPos = 1; + /* Step: eg: 600 DPI -> 700 DPI -> hres_step = 6/7 -> step = 1/7 */ + SANE_Int step = SANE_FIX(1) - dev->CIS.hres_step; + + /* Super-sampling */ + DBG (6, "cis_read_line_low_level: super-sampling\n"); + do + { + if (dev->CIS.delay) delay_read(dev->CIS.delay); + SANEI_PA4S2_READBYTE (dev->desc->fd, &color); + + cval = color; + + if (calib_low) { + low_val = calib_low[calctr] ; + } + + if (calib_hi) { + hi_val = calib_hi[calctr] ; + } + + if (++calctr >= dev->calib_pixels) { + /* Avoid array boundary violations due to rounding errors + (due to the incremental calculation, the current position + may be inaccurate to up to two pixels, so we may need to + read a few extra bytes -> use the last calibration value) */ + calctr = dev->calib_pixels - 1; + DBG (3, "cis_read_line_low_level: calibration overshoot\n"); + } + + cval -= low_val ; + cval <<= 8 ; + cval /= hi_val-low_val ; + + if (cval < 0) cval = 0 ; + else if (cval > 255) cval = 255 ; + + if (gamma) + cval = gamma[cval]; + + pos += step; + + if ((pos >> SANE_FIXED_SCALE_SHIFT) >= nextPos) + { + nextPos++; + + /* Insert an interpolated value */ + buf[bpos] = (buf[bpos-1] + cval)/2; /* Interpolate */ + ++bpos; + + /* Store the plain value, but only if we still need pixels */ + if (bpos < pixel) + buf[bpos++] = cval; + + pos += step; /* Take interpolated value into account for pos */ + } + else + { + buf[bpos++] = cval; + } + } + while (bpos < pixel); + } + + SANEI_PA4S2_READEND (dev->desc->fd); + DBG (6, "cis_read_line_low_level: done\n"); +} + +static SANE_Bool +cis_read_line (Mustek_PP_CIS_dev * dev, SANE_Byte* buf, SANE_Int pixel, + SANE_Bool raw) +{ + if (!dev->CIS.dontIncRead) + CIS_INC_READ(dev); + else + dev->CIS.dontIncRead = SANE_FALSE; + + + if (raw) + { + /* No color correction; raw data */ + cis_read_line_low_level (dev, buf, pixel, NULL, NULL, NULL); + } + else + { + /* Color correction */ + cis_read_line_low_level (dev, buf, pixel, + dev->calib_low[dev->CIS.channel], + dev->calib_hi[dev->CIS.channel], + (dev->desc->val[OPT_CUSTOM_GAMMA].w ? + dev->desc->gamma_table[dev->CIS.channel] : NULL)); + } + + return cis_wait_next_channel(dev); +} + +static void +cis_get_next_line (Mustek_PP_CIS_dev * dev, SANE_Byte * buf) +{ + SANE_Byte *dest, *tmpbuf = dev->tmpbuf; + int ctr, channel, first, last, stride, ignore, step = dev->CIS.line_step; + SANE_Byte gotline; + + if (dev->desc->mode == MODE_COLOR) + { + first = MUSTEK_PP_CIS_CHANNEL_RED; + last = MUSTEK_PP_CIS_CHANNEL_BLUE; + stride = 3; + ignore = 1; /* 1 * 3 channels */ + } + else + { + first = MUSTEK_PP_CIS_CHANNEL_GRAY; + last = MUSTEK_PP_CIS_CHANNEL_GRAY; + stride = 1; + ignore = 3; /* 3 * 1 channel */ + } + + gotline = SANE_FALSE; + do + { + dev->ccd_line++; + if ((dev->line_diff >> SANE_FIXED_SCALE_SHIFT) != dev->ccd_line) + { + cis_motor_forward (dev); + continue; + } + + dev->line_diff += step; + + for (channel = first; channel <= last; ++channel) + { + if (!cis_read_line(dev, tmpbuf, dev->desc->params.pixels_per_line, + SANE_FALSE)) + return; + + dest = buf + channel - first; + for (ctr = 0; ctr < dev->desc->params.pixels_per_line; ctr++) + { + *dest = tmpbuf[ctr]; + dest += stride; + } + } + gotline = SANE_TRUE; + } + while (!gotline && dev->desc->state != STATE_CANCELLED); +} + +static void +cis_get_grayscale_line (Mustek_PP_CIS_dev * dev, SANE_Byte * buf) +{ + cis_get_next_line(dev, buf); +} + +static void +cis_get_lineart_line (Mustek_PP_CIS_dev * dev, SANE_Byte * buf) +{ + int ctr; + SANE_Byte gbuf[MUSTEK_PP_CIS_MAX_H_PIXEL * 2]; + + cis_get_grayscale_line (dev, gbuf); + memset (buf, 0xFF, dev->desc->params.bytes_per_line); + + for (ctr = 0; ctr < dev->desc->params.pixels_per_line; ctr++) + buf[ctr >> 3] ^= ((gbuf[ctr] > dev->bw_limit) ? (1 << (7 - ctr % 8)) : 0); +} + +static void +cis_get_color_line (Mustek_PP_CIS_dev * dev, SANE_Byte * buf) +{ + cis_get_next_line(dev, buf); +} + + +/****************************************************************************** + * Saves the state of the device during reset and calibration. + *****************************************************************************/ +static void +cis_save_state (Mustek_PP_CIS_dev * dev) +{ + dev->Saved_CIS = dev->CIS; +} + +/****************************************************************************** + * Restores the state of the device after reset and calibration. + *****************************************************************************/ +static void +cis_restore_state (Mustek_PP_CIS_dev * dev) +{ + dev->CIS = dev->Saved_CIS; +} + +#define CIS_TOO_BRIGHT 1 +#define CIS_OK 0 +#define CIS_TOO_DARK -1 + +static int +cis_check_result(SANE_Byte* buffer, int pixel) +{ + int i, maxVal = 0; + + for (i=0;i<pixel;++i) + if (buffer[i] > maxVal) maxVal = buffer[i]; + + if (maxVal > 250) return CIS_TOO_BRIGHT; + if (maxVal < 240) return CIS_TOO_DARK; + return CIS_OK; +} + +static SANE_Bool +cis_maximize_dynamic_range(Mustek_PP_CIS_dev * dev) +{ + /* The device is in its final configuration already. */ + int i, j, pixel, channel, minExposeTime, first, last; + SANE_Byte powerOnDelayLower[3], powerOnDelayUpper[3], exposeTime[3]; + SANE_Byte buf[3][MUSTEK_PP_CIS_MAX_H_PIXEL]; + SANE_Int pixels = dev->calib_pixels; + + DBG(3, "cis_maximize_dynamic_range: starting\n"); + + for (channel = 0; channel < 3; ++channel) + { + exposeTime[channel] = 254; + dev->CIS.powerOnDelay[channel] = 170; + powerOnDelayLower[channel] = 1; + powerOnDelayUpper[channel] = 254; + } + dev->CIS.setParameters = SANE_TRUE; + dev->CIS.exposeTime = exposeTime[MUSTEK_PP_CIS_CHANNEL_GREEN]; + cis_config_ccd(dev); + + M1015_DISPLAY_REGS(dev, "before maximizing dynamic range"); + dev->CIS.dontMove = SANE_TRUE; /* Don't move while calibrating */ + + if (!cis_wait_read_ready(dev) && dev->desc->state != STATE_CANCELLED) + { + DBG(2, "cis_maximize_dynamic_range: DEVICE NOT READY!\n"); + return SANE_FALSE; + } + + if (dev->desc->mode == MODE_COLOR) + { + first = MUSTEK_PP_CIS_CHANNEL_RED; + last = MUSTEK_PP_CIS_CHANNEL_BLUE; + } + else + { + first = MUSTEK_PP_CIS_CHANNEL_GRAY; + last = MUSTEK_PP_CIS_CHANNEL_GRAY; + } + + dev->CIS.channel = first; + + /* Perform a kind of binary search. In the worst case, we should find + the optimal power delay values after 8 iterations */ + for( i=0; i<8; i++) + { + for (channel = first; channel <= last; ++channel) + { + dev->CIS.powerOnDelay[channel] = (powerOnDelayLower[channel] + + powerOnDelayUpper[channel]) / 2; + } + Mustek_PP_1015_write_reg(dev, MA1015W_POWER_ON_DELAY, + dev->CIS.powerOnDelay[1]); /* Green */ + + for (pixel = 0; pixel < pixels; ++pixel) + { + buf[0][pixel] = buf[1][pixel] = buf[2][pixel] = 255; + } + + /* Scan 4 lines, but ignore the first 3 ones. */ + for (j = 0; j < 4; ++j) + { + for (channel = first; channel <= last; ++channel) + { + if (!cis_read_line(dev, &buf[channel][0], pixels, + /* raw = */ SANE_TRUE)) + return SANE_FALSE; + } + } + + for (channel = first; channel <= last; ++channel) + { + switch (cis_check_result(buf[channel], pixels)) + { + case CIS_TOO_BRIGHT: + powerOnDelayLower[channel] = dev->CIS.powerOnDelay[channel]; + break; + + case CIS_TOO_DARK: + powerOnDelayUpper[channel] = dev->CIS.powerOnDelay[channel]; + break; + + default: + break; + } + } + DBG (4, "cis_maximize_dynamic_range: power on delay %3d %3d %3d\n", + dev->CIS.powerOnDelay[0], dev->CIS.powerOnDelay[1], + dev->CIS.powerOnDelay[2]); + } + dev->CIS.dontMove = SANE_FALSE; + + DBG (3, "cis_maximize_dynamic_range: power on delay %3d %3d %3d\n", + dev->CIS.powerOnDelay[0], dev->CIS.powerOnDelay[1], + dev->CIS.powerOnDelay[2]); + + minExposeTime = (dev->CIS.hw_hres <= 300) ? 170 : 253; + + for (channel = first; channel <= last; ++channel) + { + dev->CIS.powerOnDelay[channel] = (powerOnDelayLower[channel] + + powerOnDelayUpper[channel]) / 2; + exposeTime[channel] -= dev->CIS.powerOnDelay[channel] - 1; + dev->CIS.powerOnDelay[channel] = 1; + + if (exposeTime[channel] < minExposeTime) + { + dev->CIS.powerOnDelay[channel] += + minExposeTime - exposeTime[channel]; + exposeTime[channel] = minExposeTime; + } + } + + dev->CIS.exposeTime = exposeTime[MUSTEK_PP_CIS_CHANNEL_GREEN]; + + DBG (3, "cis_maximize_dynamic_range: expose time: %3d\n", exposeTime[1]); + DBG (3, "cis_maximize_dynamic_range: power on delay %3d %3d %3d\n", + dev->CIS.powerOnDelay[0], dev->CIS.powerOnDelay[1], + dev->CIS.powerOnDelay[2]); + + /* + * Short the calibration. Temporary, to find out what is wrong with + * the calibration on a 600 CP. + * + dev->CIS.exposeTime = 170; + dev->CIS.powerOnDelay[0] = 120; + dev->CIS.powerOnDelay[1] = 120; + dev->CIS.powerOnDelay[2] = 120; + */ + return SANE_TRUE; +} + +static SANE_Bool +cis_measure_extremes(Mustek_PP_CIS_dev * dev, SANE_Byte* calib[3], + SANE_Int pixels, SANE_Int first, SANE_Int last) +{ + SANE_Byte buf[3][MUSTEK_PP_CIS_MAX_H_PIXEL]; + SANE_Byte min[3][MUSTEK_PP_CIS_MAX_H_PIXEL]; + SANE_Byte max[3][MUSTEK_PP_CIS_MAX_H_PIXEL]; + SANE_Int sum[3][MUSTEK_PP_CIS_MAX_H_PIXEL]; + int channel, cnt, p; + + memset((void*)&min, 255, 3*MUSTEK_PP_CIS_MAX_H_PIXEL*sizeof(SANE_Byte)); + memset((void*)&max, 0, 3*MUSTEK_PP_CIS_MAX_H_PIXEL*sizeof(SANE_Byte)); + memset((void*)&sum, 0, 3*MUSTEK_PP_CIS_MAX_H_PIXEL*sizeof(SANE_Int)); + + dev->CIS.channel = first; + + /* Purge the banks first (there's always a 3-cycle delay) */ + for (channel = first; channel <= last; ++channel) + { + if (!cis_read_line(dev, &buf[channel%3][0], pixels, + /* raw = */ SANE_TRUE)) + return SANE_FALSE; + } + --dev->CIS.skipsToOrigin; + + for (cnt = 0; cnt < MUSTEK_PP_CIS_AVERAGE_COUNT + 2; ++cnt) + { + for (channel = first; channel <= last; ++channel) + { + DBG(4, "cis_measure_extremes: Reading line %d - channel %d\n", + cnt, channel); + if (!cis_read_line(dev, &buf[channel][0], pixels, + /* raw = */ SANE_TRUE)) + return SANE_FALSE; + + for (p = 0; p < pixels; ++p) + { + SANE_Byte val = buf[channel][p]; + if (val < min[channel][p]) min[channel][p] = val; + if (val > max[channel][p]) max[channel][p] = val; + sum[channel][p] += val; + } + } + --dev->CIS.skipsToOrigin; + } + DBG(4, "cis_measure_extremes: Averaging\n"); + for (channel = first; channel <= last; ++channel) + { + /* Ignore the extreme values and take the average of the others. */ + for (p = 0; p < pixels; ++p) + { + sum[channel][p] -= min[channel][p] + max[channel][p]; + sum[channel][p] /= MUSTEK_PP_CIS_AVERAGE_COUNT; + if (calib[channel]) calib[channel][p] = sum[channel][p]; + } + } + DBG(4, "cis_measure_extremes: Done\n"); + return SANE_TRUE; +} + +static SANE_Bool +cis_normalize_ranges(Mustek_PP_CIS_dev * dev) +{ + SANE_Byte cal_low, cal_hi ; + SANE_Byte powerOnDelay[3] ; + SANE_Int pixels = dev->calib_pixels; + SANE_Int channel, p, first, last; + + if (dev->desc->mode == MODE_COLOR) + { + first = MUSTEK_PP_CIS_CHANNEL_RED; + last = MUSTEK_PP_CIS_CHANNEL_BLUE; + } + else + { + first = MUSTEK_PP_CIS_CHANNEL_GRAY; + last = MUSTEK_PP_CIS_CHANNEL_GRAY; + } + + DBG(3, "cis_normalize_ranges: Measuring high extremes\n"); + /* Measure extremes with normal lighting */ + if (!cis_measure_extremes(dev, dev->calib_hi, pixels, first, last)) { + return SANE_FALSE; + } + + /* Measure extremes without lighting */ + for (channel=first; channel<=last; ++channel) { + powerOnDelay[channel] = dev->CIS.powerOnDelay[channel]; + dev->CIS.powerOnDelay[channel] = dev->CIS.exposeTime; + } + + DBG(3, "cis_normalize_ranges: Measuring low extremes\n"); + if (!cis_measure_extremes(dev, dev->calib_low, pixels, first, last)) { + return SANE_FALSE; + } + + /* Restore settings */ + for (channel=first; channel<=last; ++channel) { + dev->CIS.powerOnDelay[channel] = powerOnDelay[channel]; + } + + /* Make sure calib_hi is greater than calib_low */ + for (channel = first; channel <= last; ++channel) { + for (p = 0; p<pixels; p++) { + if (dev->calib_low[channel]) { + cal_low = dev->calib_low[channel][p]; + } else { + cal_low = 0; + } + if (dev->calib_hi[channel]) { + cal_hi = dev->calib_hi[channel][p]; + } else { + cal_hi = 255; + } + if (cal_hi <= cal_low) { + if(cal_hi<255) { + /* calib_hi exists, else cal_hi would be 255 */ + dev->calib_hi[channel][p] = cal_low+1; + } else { + /* calib_low exists, else cal_low would be 0, < 255 */ + dev->calib_low[channel][p] = cal_hi-1; + } + } + } + } + DBG(3, "cis_normalize_ranges: calibration done\n"); + return SANE_TRUE; +} + +/* + * This routine measures the time that we have to wait between reading + * to pixels from the scanner. Especially at low resolutions, but also + * for narrow-width scans at high resolutions, reading too fast cause + * color stability problems. + * This routine sends a test pattern to the scanner memory banks and tries + * to measure how fast it can be retrieved without errors. + * The same is done by the TWAIN driver (TESTIO.CPP:TestDelay). + */ +static SANE_Bool +cis_measure_delay(Mustek_PP_CIS_dev * dev) +{ + SANE_Byte buf[2][2048]; + unsigned i, j, d; + int saved_res; + SANE_Bool error = SANE_FALSE; + + CIS_CLEAR_FULLFLAG(dev); + CIS_CLEAR_WRITE_ADDR(dev); + CIS_CLEAR_WRITE_BANK(dev); + CIS_INC_READ(dev); + CIS_CLEAR_READ_BANK(dev); + + M1015_DISPLAY_REGS(dev, "Before delay measurement"); + assert(dev->CIS.adjustskip == 0); + + /* Sawtooth */ + for (i=0; i<2048; ++i) + { + buf[0][i] = i % 255; /* Why 255 ? Seems to have no real importance */ + } + + Mustek_PP_1015_write_reg_start(dev, MA1015W_SRAM_SOURCE_PC); + for (i=0; i<2048; ++i) + { + Mustek_PP_1015_write_reg_val(dev, buf[0][i]); + } + Mustek_PP_1015_write_reg_stop(dev); + + /* Bank offset measurement */ + dev->CIS.delay = 0; /* Initialize to zero, measure next */ + + saved_res = dev->CIS.res; + dev->CIS.res = dev->CIS.hw_hres; + + /* + * Note: the TWAIN driver seems to have a fast EPP mode too. That one is + * tried first, and then they try the normal mode. I haven't figured out + * yet how the fast mode works, so I'll only check the normal mode for now. + * Moreover, from the behaviour that I've witnessed from the TWAIN driver, + * I must conclude that the fast mode probably doesn't work on my computer, + * so I can't test it anyhow. + */ + /* Gradually increase the delay till we have no more errors */ + for (d = 0; d < 75 /* 255 */ && dev->desc->state != STATE_CANCELLED; d += 5) + { + dev->CIS.delay = d; + + /* + * We read the line 5 times to make sure that all garbage is flushed. + */ + for (i=0; i<5; ++i) + { + CIS_INC_READ(dev); + CIS_CLEAR_READ_BANK(dev); + cis_read_line_low_level (dev, &buf[1][0], 2048, NULL, NULL, NULL); + if (dev->desc->state == STATE_CANCELLED) return SANE_FALSE; + } + + error = SANE_FALSE; + /* Check 100 times whether we can read without errors. */ + for (i=0; i<100 && !error; ++i) + { + CIS_INC_READ(dev); + CIS_CLEAR_READ_BANK(dev); + cis_read_line_low_level (dev, &buf[1][0], 2048, NULL, NULL, NULL); + if (dev->desc->state == STATE_CANCELLED) return SANE_FALSE; + + for (j=0; j<2048; ++j) + { + if (buf[0][j] != buf[1][j]) + { + error = SANE_TRUE; + break; + } + } + } + + DBG (3, "cis_measure_delay: delay %d\n", dev->CIS.delay); + if (!error) + break; + } + + dev->CIS.res = saved_res; + + if (error) + { + fprintf(stderr, "mustek_pp_cis: failed to measure delay.\n"); + fprintf(stderr, "Buffer contents:\n"); + for (j = 0; j < 20; ++j) + { + fprintf(stderr, "%d ", buf[1][j]); + } + fprintf(stderr, "\n"); + dev->CIS.delay = 0; + } + + DBG (3, "cis_measure_delay: delay %d\n", dev->CIS.delay); + return SANE_TRUE; +} + +static void +cis_motor_control (Mustek_PP_CIS_dev * dev, u_char control) +{ + cis_wait_motor_stable (dev); + Mustek_PP_1015_write_reg(dev, MA1015W_MOTOR_CONTROL, control); +} + +static void +cis_return_home (Mustek_PP_CIS_dev * dev, SANE_Bool nowait) +{ + SANE_Byte savedExposeTime = dev->CIS.exposeTime; + DBG(4, "cis_return_home: returning home; nowait: %d\n", nowait); + /* During a return-home, the expose time is fixed. */ + dev->CIS.exposeTime = 170; + cis_config_ccd(dev); + dev->CIS.exposeTime = savedExposeTime; + + cis_motor_control (dev, 0xEB); + + if (nowait == SANE_FALSE) + Mustek_PP_1015_wait_bit(dev, MA1015R_MOTOR, MA1015B_MOTOR_HOME, + SANE_TRUE, 1000); +} + +/****************************************************************************** + * Does a full reset of the device, ie. configures the CIS to a default + * resolution of 300 DPI (in high or low resolution mode, depending on the + * resolution requested by the user). + *****************************************************************************/ +static void +cis_reset_device (Mustek_PP_CIS_dev * dev) +{ + DBG(4, "cis_reset_device: resetting device\n"); + dev->CIS.adjustskip = 0; + dev->CIS.dontIncRead = SANE_TRUE; + dev->CIS.dontMove = SANE_FALSE; + + cis_save_state(dev); + + dev->CIS.hw_hres = 300; + dev->CIS.channel = MUSTEK_PP_CIS_CHANNEL_GREEN; + dev->CIS.setParameters = SANE_FALSE; + dev->CIS.exposeTime = 0xAA; + + cis_config_ccd (dev); + + cis_restore_state(dev); + +} + +static SANE_Bool +cis_calibrate (Mustek_PP_CIS_dev * dev) +{ + int i, saved_res = dev->CIS.res, saved_vres = dev->CIS.hw_vres; + + /* + * Flow of operation observed from the twain driver + * (it is assumed that the lamp is at the origin, and that the CIS is + * configured for 300 DPI, ie. cis_reset_device has been called.) + * + * - Reset the device and return the lamp to its home position + * + * - Unknown short sequence + * + * - Send a sawtooth-like pattern to one of the memory banks. + * + * - Repetitive read_line of 2048 bytes, interleaved with an unknown + * command. The number varies between 102 and 170 times, but there + * doesn't seem to be any correlation with the current mode of the + * scanner, so I assume that the exact number isn't really relevant. + * The values that are read are the one that were sent to the bank, + * rotated by 1 byte in my case. + * + * + * It seems that the width of the black border is being measured at + * this stage, possibly multiple times till it stabilizes. + * I assume that the buffer is read 100 times to allow the lamp to + * warm up and that the the width of the black border is then being + * measured till it stabilizes. That would explain the minimum number + * of 102 iterations that I've seen. + * + * - reset the device + * + * - move the motor 110 steps forward. The TWAIN driver moves 90 steps, + * and I've used 90 steps for a long time too, but occasionally, + * 90 steps is a fraction to short to reach the start of the + * calibration strip (the motor movements are not very accurate; + * an offset of 1 mm is not unusual). Therefore, I've increased it to + * 110 steps. This gives us an additional 1.6 mm slack, which should + * prevent calibration errors. + * (Note that the MUSTEK_PP_CIS_????CP_DEFAULT_SKIP constants have to + * be adjusted if the number of steps is altered.) + * + * - configure the CIS : actual resolution + set parameters + * + */ + + /* + * We must make sure that we are in the scanning state; otherwise we may + * still be in the canceled state from a previous scan (even if terminated + * normally), and the whole calibration would go wrong. + */ + dev->desc->state = STATE_SCANNING; + + cis_reset_device (dev); + cis_return_home (dev, SANE_FALSE); /* Wait till it's home */ + + /* Use maximum resolution during calibration; otherwise we may calibrate + past the calibration strip. */ + dev->CIS.hw_vres = dev->desc->dev->maxres; + /* This field remembers how many steps we still have to go @ max res */ + dev->CIS.skipsToOrigin = dev->top_skip; /*max2hw_vres(dev, dev->top_skip); */ + + if (!cis_measure_delay(dev)) + return SANE_FALSE; + + cis_reset_device (dev); + + /* Move motor 110 steps @ 300 DPI */ + Mustek_PP_1015_write_reg_start(dev, MA1015W_MOTOR_CONTROL); + for (i=0; i<110; ++i) + { + if (dev->model == MUSTEK_PP_CIS600) + { + Mustek_PP_1015_write_reg_val (dev, 0x73); + } + else + { + Mustek_PP_1015_write_reg_val (dev, 0x7B); + } + cis_wait_motor_stable (dev); + } + Mustek_PP_1015_write_reg_stop(dev); + + /* Next, we maximize the dynamic range of the scanner. During calibration + we don't want to extrapolate, so we limit the resolution if necessary */ + + if (dev->CIS.hw_hres < dev->CIS.res) + dev->CIS.res = dev->CIS.hw_hres; + + if (!cis_maximize_dynamic_range(dev)) + return SANE_FALSE; + + if (!cis_normalize_ranges(dev)) + return SANE_FALSE; + + dev->CIS.res = saved_res; + dev->CIS.hw_vres = saved_vres; + + /* Convert steps back to max res size, which are used during skipping */ +/* dev->CIS.skipsToOrigin = hw2max_vres(dev, dev->CIS.skipsToOrigin); */ + + /* Move to the origin */ + DBG(3, "cis_calibrate: remaining skips to origin @maxres: %d\n", + dev->CIS.skipsToOrigin); + cis_move_motor(dev, dev->CIS.skipsToOrigin); + + if (dev->calib_mode) + { + /* In calibration mode, we scan the interior of the scanner before the + glass plate in order to find the position of the calibration strip + and the start of the glass plate. */ + DBG(3, "cis_calibrate: running in calibration mode. Returning home.\n"); + cis_return_home (dev, SANE_FALSE); /* Wait till it's home */ + } + + return dev->desc->state != STATE_CANCELLED ? SANE_TRUE : SANE_FALSE; + +} + +/****************************************************************************** + ****************************************************************************** + *** Mustek PP interface *** + ****************************************************************************** + *****************************************************************************/ + +/****************************************************************************** +* Init * +******************************************************************************/ + +/* Shared initialization routine */ +static SANE_Status cis_attach(SANE_String_Const port, + SANE_String_Const name, + SANE_Attach_Callback attach, + SANE_Int driverNo, + SANE_Int info) +{ + int fd; + SANE_Status status; + u_char asic; + + status = sanei_pa4s2_open (port, &fd); + + if (status != SANE_STATUS_GOOD) + { + SANE_Status altStatus; + SANE_String_Const altPort; + + DBG (2, "cis_attach: couldn't attach to `%s' (%s)\n", port, + sane_strstatus (status)); + + /* Make migration to libieee1284 painless for users that used + direct io in the past */ + if (strcmp(port, "0x378") == 0) altPort = "parport0"; + else if (strcmp(port, "0x278") == 0) altPort = "parport1"; + else if (strcmp(port, "0x3BC") == 0) altPort = "parport2"; + else return status; + + DBG (2, "cis_attach: trying alternative port name: %s\n", altPort); + + altStatus = sanei_pa4s2_open (altPort, &fd); + if (altStatus != SANE_STATUS_GOOD) + { + DBG (2, "cis_attach: couldn't attach to alternative port `%s' " + "(%s)\n", altPort, sane_strstatus (altStatus)); + return status; /* Return original status, not alternative status */ + } + } + + M1015_START_LL; + M1015_START_HL; + + + sanei_pa4s2_enable (fd, SANE_TRUE); + SANEI_PA4S2_READBEGIN (fd, 0); + SANEI_PA4S2_READBYTE (fd, &asic); + SANEI_PA4S2_READEND (fd); + sanei_pa4s2_enable (fd, SANE_FALSE); + + sanei_pa4s2_close (fd); + + if (asic != 0xA5) /* Identifies the MA1015 chipset */ + { + /* CIS driver only works for MA1015 chipset */ + DBG (2, "cis_attach: asic id (0x%02x) not recognized\n", asic); + return SANE_STATUS_INVAL; + } + + DBG (3, "cis_attach: device %s attached\n", name); + DBG (3, "cis_attach: asic 0x%02x\n", asic); + + return attach(port, name, driverNo, info); +} + +SANE_Status cis600_drv_init(SANE_Int options, SANE_String_Const port, + SANE_String_Const name, SANE_Attach_Callback attach) +{ + if (options != CAP_NOTHING) + return SANE_STATUS_INVAL; + + return cis_attach(port, name, attach, MUSTEK_PP_CIS600, MUSTEK_PP_CIS600); +} + +SANE_Status cis1200_drv_init(SANE_Int options, SANE_String_Const port, + SANE_String_Const name, SANE_Attach_Callback attach) +{ + if (options != CAP_NOTHING) + return SANE_STATUS_INVAL; + + return cis_attach(port, name, attach, MUSTEK_PP_CIS1200, MUSTEK_PP_CIS1200); +} + +SANE_Status cis1200p_drv_init(SANE_Int options, SANE_String_Const port, + SANE_String_Const name, SANE_Attach_Callback attach) +{ + if (options != CAP_NOTHING) + return SANE_STATUS_INVAL; + + return cis_attach(port, name, attach, MUSTEK_PP_CIS1200PLUS, MUSTEK_PP_CIS1200PLUS); +} + +/****************************************************************************** +* Capabilities * +******************************************************************************/ +void cis_drv_capabilities(SANE_Int info, SANE_String *model, + SANE_String *vendor, SANE_String *type, + SANE_Int *maxres, SANE_Int *minres, + SANE_Int *maxhsize, SANE_Int *maxvsize, + SANE_Int *caps) +{ + *vendor = strdup("Mustek"); + *type = strdup("flatbed scanner"); + *caps = CAP_NOTHING; + + switch(info) + { + case MUSTEK_PP_CIS600: + *model = strdup("600CP"); + *maxres = 600; + *minres = 50; + *maxhsize = MUSTEK_PP_CIS_MAX_H_PIXEL; + *maxvsize = MUSTEK_PP_CIS_MAX_V_PIXEL; + break; + case MUSTEK_PP_CIS1200: + *model = strdup("1200CP"); + *maxres = 1200; + *minres = 50; + *maxhsize = MUSTEK_PP_CIS_MAX_H_PIXEL*2; + *maxvsize = MUSTEK_PP_CIS_MAX_V_PIXEL*2; + break; + case MUSTEK_PP_CIS1200PLUS: + *model = strdup("1200CP+"); + *maxres = 1200; + *minres = 50; + *maxhsize = MUSTEK_PP_CIS_MAX_H_PIXEL*2; + *maxvsize = MUSTEK_PP_CIS_MAX_V_PIXEL*2; + break; + } +} + +/****************************************************************************** +* Open * +******************************************************************************/ +SANE_Status cis_drv_open (SANE_String port, SANE_Int caps, SANE_Int *fd) +{ + SANE_Status status; + + if (caps != CAP_NOTHING) + { + DBG (1, "cis_drv_open: called with unknown capabilities (0x%02X)\n", caps); + return SANE_STATUS_INVAL; + } + + DBG (3, "cis_drv_open: called for port %s\n", port); + + status = sanei_pa4s2_open (port, fd); + + if (status != SANE_STATUS_GOOD) + { + SANE_Status altStatus; + SANE_String_Const altPort; + + DBG (2, "cis_attach: couldn't attach to `%s' (%s)\n", port, + sane_strstatus (status)); + + /* Make migration to libieee1284 painless for users that used + direct io in the past */ + if (strcmp(port, "0x378") == 0) altPort = "parport0"; + else if (strcmp(port, "0x278") == 0) altPort = "parport1"; + else if (strcmp(port, "0x3BC") == 0) altPort = "parport2"; + else return status; + + DBG (2, "cis_attach: trying alternative port name: %s\n", altPort); + + altStatus = sanei_pa4s2_open (altPort, fd); + if (altStatus != SANE_STATUS_GOOD) + { + DBG (2, "cis_attach: couldn't attach to alternative port `%s' " + "(%s)\n", altPort, sane_strstatus (altStatus)); + return status; /* Return original status, not alternative status */ + } + } + + return SANE_STATUS_GOOD; +} + +/****************************************************************************** +* Setup * +******************************************************************************/ +void cis_drv_setup (SANE_Handle hndl) +{ + Mustek_pp_Handle *dev = hndl; + Mustek_PP_CIS_dev *cisdev; + cisdev = (Mustek_PP_CIS_dev*)malloc(sizeof(Mustek_PP_CIS_dev)); + if (cisdev == NULL) + { + DBG (2, "cis_drv_setup: not enough memory for device descriptor\n"); + sanei_pa4s2_close (dev->fd); + return; + } + memset(cisdev, 0, sizeof(Mustek_PP_CIS_dev)); + DBG(3, "cis_drv_setup: cis device allocated\n"); + + dev->lamp_on = 0; + dev->priv = cisdev; + + cisdev->desc = dev; + cisdev->model = dev->dev->info; + cisdev->CIS.hw_hres = 300; + cisdev->CIS.cisRes = 300; + cisdev->CIS.hw_vres = 300; + + /* Default values for configurable parameters; configuration file + may override them. */ + cisdev->fast_skip = SANE_TRUE; + cisdev->bw_limit = 127; + cisdev->calib_mode = SANE_FALSE; + cisdev->engine_delay = 0; + if (cisdev->model == MUSTEK_PP_CIS600) + { + cisdev->top_skip = MUSTEK_PP_CIS_600CP_DEFAULT_SKIP; + } + else + { + cisdev->top_skip = MUSTEK_PP_CIS_1200CP_DEFAULT_SKIP; + } +} + +/****************************************************************************** +* Config * +******************************************************************************/ +SANE_Status cis_drv_config(SANE_Handle hndl, SANE_String_Const optname, + SANE_String_Const optval) +{ + Mustek_pp_Handle *dev = hndl; + Mustek_PP_CIS_dev *cisdev = dev->priv; + int value = 0; + double dvalue = 0; + DBG (3, "cis_drv_cfg option: %s=%s\n", optname, optval ? optval : ""); + if (!strcmp(optname, "top_adjust")) + { + if (!optval) + { + DBG (1, "cis_drv_config: missing value for option top_adjust\n"); + return SANE_STATUS_INVAL; + } + dvalue = atof(optval); + + /* An adjustment of +/- 5 mm should be sufficient and safe */ + if (dvalue < -5.0) + { + DBG (1, "cis_drv_config: value for option top_adjust too small: " + "%.2f < -5; limiting to -5 mm\n", dvalue); + dvalue = -5.0; + } + if (dvalue > 5.0) + { + DBG (1, "cis_drv_config: value for option top_adjust too large: " + "%.2f > 5; limiting to 5 mm\n", dvalue); + dvalue = 5.0; + } + /* In practice, there is a lower bound on the value that can be used, + but if the top_skip value is smaller than that value, the only result + will be that the driver tries to move the head a negative number + of steps after calibration. The move routine just ignores negative + steps, so no harm can be done. */ + cisdev->top_skip += MM_TO_PIXEL(dvalue, dev->dev->maxres); + DBG (3, "cis_drv_config: setting top skip value to %d\n", + cisdev->top_skip); + + /* Just to be cautious; we don't want the head to hit the bottom */ + if (cisdev->top_skip > 600) cisdev->top_skip = 600; + if (cisdev->top_skip < -600) cisdev->top_skip = -600; + } + else if (!strcmp(optname, "slow_skip")) + { + if (optval) + { + DBG (1, "cis_drv_config: unexpected value for option slow_skip\n"); + return SANE_STATUS_INVAL; + } + DBG (3, "cis_drv_config: disabling fast skipping\n"); + cisdev->fast_skip = SANE_FALSE; + } + else if (!strcmp(optname, "bw")) + { + if (!optval) + { + DBG (1, "cis_drv_config: missing value for option bw\n"); + return SANE_STATUS_INVAL; + } + value = atoi(optval); + if (value < 0 || value > 255) + { + DBG (1, "cis_drv_config: value for option bw out of range: " + "%d < 0 or %d > 255\n", value, value); + return SANE_STATUS_INVAL; + } + cisdev->bw_limit = value; + } + else if (!strcmp(optname, "calibration_mode")) + { + if (optval) + { + DBG (1, "cis_drv_config: unexpected value for option calibration_mode\n"); + return SANE_STATUS_INVAL; + } + DBG (3, "cis_drv_config: using calibration mode\n"); + cisdev->calib_mode = SANE_TRUE; + } + else if (!strcmp(optname, "engine_delay")) + { + if (!optval) + { + DBG (1, "cis_drv_config: missing value for option engine_delay\n"); + return SANE_STATUS_INVAL; + } + value = atoi(optval); + if (value < 0 || value > 100) /* 100 ms is already pretty slow */ + { + DBG (1, "cis_drv_config: value for option engine_delay out of range: " + "%d < 0 or %d > 100\n", value, value); + return SANE_STATUS_INVAL; + } + cisdev->engine_delay = value; + } + else + { + DBG (1, "cis_drv_config: unknown options %s\n", optname); + return SANE_STATUS_INVAL; + } + return SANE_STATUS_GOOD; +} + +/****************************************************************************** +* Close * +******************************************************************************/ +void cis_drv_close (SANE_Handle hndl) +{ + Mustek_pp_Handle *dev = hndl; + Mustek_PP_CIS_dev *cisdev = dev->priv; + DBG (3, "cis_close: resetting device.\n"); + sanei_pa4s2_enable (dev->fd, SANE_TRUE); + cis_reset_device (cisdev); + DBG (3, "cis_close: returning home.\n"); + cis_return_home (cisdev, SANE_TRUE); /* Don't wait */ + DBG (3, "cis_close: disabling fd.\n"); + sanei_pa4s2_enable (dev->fd, SANE_FALSE); + DBG (3, "cis_close: closing fd.\n"); + sanei_pa4s2_close (dev->fd); + DBG (3, "cis_close: done.\n"); + DBG (6, "cis_close: lamp_on: %d\n", (int)dev->lamp_on); + M1015_STOP_LL; + M1015_STOP_HL; +} + +/****************************************************************************** +* Start * +******************************************************************************/ +SANE_Status cis_drv_start (SANE_Handle hndl) +{ + Mustek_pp_Handle *dev = hndl; + Mustek_PP_CIS_dev *cisdev = dev->priv; + SANE_Int pixels = dev->params.pixels_per_line; + + if (!cisdev) + { + DBG (2, "cis_drv_start: not enough memory for device\n"); + return SANE_STATUS_NO_MEM; + } + + cisdev->CIS.exposeTime = 0xAA; + cisdev->CIS.setParameters = SANE_FALSE; + cisdev->CIS.use8KBank = SANE_TRUE; + cisdev->CIS.imagebytes = dev->bottomX - dev->topX; + cisdev->CIS.skipimagebytes = dev->topX; + + cisdev->CIS.res = dev->res; + + DBG (3, "cis_drv_start: %d dpi\n", dev->res); + + if (dev->res <= 50 && cisdev->model != MUSTEK_PP_CIS1200PLUS) + { + cisdev->CIS.hw_hres = 50; + } + else if (dev->res <= 75 && cisdev->model == MUSTEK_PP_CIS1200PLUS) + { + cisdev->CIS.hw_hres = 75; + } + else if (dev->res <= 100) + { + cisdev->CIS.hw_hres = 100; + } + else if (dev->res <= 200) + { + cisdev->CIS.hw_hres = 200; + } + else if (dev->res <= 300) + { + cisdev->CIS.hw_hres = 300; + } + else + { + if (cisdev->model == MUSTEK_PP_CIS600) + { + cisdev->CIS.hw_hres = 300; /* Limit for 600 CP */ + } + else if (dev->res <= 400) + { + cisdev->CIS.hw_hres = 400; + } + else + { + cisdev->CIS.hw_hres = 600; /* Limit for 1200 CP/CP+ */ + } + } + + if (cisdev->model == MUSTEK_PP_CIS600) + { + if (dev->res <= 150) + { + cisdev->CIS.hw_vres = 150; + } + else if (dev->res <= 300) + { + cisdev->CIS.hw_vres = 300; + } + else + { + cisdev->CIS.hw_vres = 600; + } + } + else + { + if (dev->res <= 300) + { + cisdev->CIS.hw_vres = 300; + } + else if (dev->res <= 600) + { + cisdev->CIS.hw_vres = 600; + } + else + { + cisdev->CIS.hw_vres = 1200; + } + } + + if (cisdev->model == MUSTEK_PP_CIS600 || + (cisdev->model == MUSTEK_PP_CIS1200 && dev->res <= 300)) + cisdev->CIS.cisRes = 300; + else + cisdev->CIS.cisRes = 600; + + /* Calibration only makes sense for hardware pixels, not for interpolated + pixels, so we limit the number of calibration pixels to the maximum + number of hardware pixels corresponding to the selected area */ + if (dev->res > cisdev->CIS.hw_hres) + cisdev->calib_pixels = (pixels * cisdev->CIS.hw_hres) / dev->res; + else + cisdev->calib_pixels = pixels; + + DBG (3, "cis_drv_start: hres: %d vres: %d cisres: %d\n", + cisdev->CIS.hw_hres, cisdev->CIS.hw_vres, cisdev->CIS.cisRes); + + sanei_pa4s2_enable (dev->fd, SANE_TRUE); + + cis_reset_device (cisdev); + cis_return_home (cisdev, SANE_TRUE); /* Don't wait here */ + +#ifdef M1015_TRACE_REGS + { + int i, j; + + /* + * Set all registers to -1 (uninitialized) + */ + for (i=0; i<4; ++i) + { + cisdev->CIS.regs.in_regs[i] = -1; + for (j=0; j<4; ++j) + { + cisdev->CIS.regs.out_regs[i][j] = -1; + } + } + + cisdev->CIS.regs.channel = -1; + /* These values have been read earlier. */ + cisdev->CIS.regs.in_regs[0] = 0xA5; + } +#endif + + cis_reset_device (cisdev); + cis_return_home (cisdev, SANE_TRUE); /* no wait */ + + /* Allocate memory for temporary color buffer */ + cisdev->tmpbuf = malloc (pixels); + if (cisdev->tmpbuf == NULL) + { + sanei_pa4s2_enable (dev->fd, SANE_FALSE); + DBG (2, "cis_drv_start: not enough memory for temporary buffer\n"); + free(cisdev); + dev->priv = NULL; + return SANE_STATUS_NO_MEM; + } + + /* Allocate memory for calibration; calibrating interpolated pixels + makes no sense */ + if (pixels > (dev->dev->maxhsize >> 1)) + pixels = (dev->dev->maxhsize >> 1); + + cisdev->calib_low[1] = malloc (pixels); + cisdev->calib_hi[1] = malloc (pixels); + + if (cisdev->calib_low[1] == NULL || cisdev->calib_hi[1] == NULL) + { + free (cisdev->calib_low[1]); cisdev->calib_low[1] = NULL; + free (cisdev->calib_hi[1]); cisdev->calib_hi[1] = NULL; + sanei_pa4s2_enable (dev->fd, SANE_FALSE); + DBG (2, "cis_drv_start: not enough memory for calibration buffer\n"); + free(cisdev->tmpbuf); cisdev->tmpbuf = NULL; + free(cisdev); dev->priv = NULL; + return SANE_STATUS_NO_MEM; + } + + cisdev->calib_low[0] = NULL; + cisdev->calib_low[2] = NULL; + cisdev->calib_hi[0] = NULL; + cisdev->calib_hi[2] = NULL; + if (dev->mode == MODE_COLOR) + { + cisdev->calib_low[0] = malloc (pixels); + cisdev->calib_low[2] = malloc (pixels); + cisdev->calib_hi[0] = malloc (pixels); + cisdev->calib_hi[2] = malloc (pixels); + + if ((cisdev->calib_low[0] == NULL) || (cisdev->calib_low[2] == NULL) || + (cisdev->calib_hi[0] == NULL) || (cisdev->calib_hi[2] == NULL)) + { + free (cisdev->calib_low[0]); cisdev->calib_low[0] = NULL; + free (cisdev->calib_low[1]); cisdev->calib_low[1] = NULL; + free (cisdev->calib_low[2]); cisdev->calib_low[2] = NULL; + free (cisdev->calib_hi[0]); cisdev->calib_hi[0] = NULL; + free (cisdev->calib_hi[1]); cisdev->calib_hi[1] = NULL; + free (cisdev->calib_hi[2]); cisdev->calib_hi[2] = NULL; + free(cisdev->tmpbuf); cisdev->tmpbuf = NULL; + free(cisdev); dev->priv = NULL; + sanei_pa4s2_enable (dev->fd, SANE_FALSE); + DBG (2, "cis_drv_start: not enough memory for color calib buffer\n"); + return SANE_STATUS_NO_MEM; + } + } + + DBG (3, "cis_drv_start: executing calibration\n"); + + if (!cis_calibrate (cisdev)) + { + free (cisdev->calib_low[0]); cisdev->calib_low[0] = NULL; + free (cisdev->calib_low[1]); cisdev->calib_low[1] = NULL; + free (cisdev->calib_low[2]); cisdev->calib_low[2] = NULL; + free (cisdev->calib_hi[0]); cisdev->calib_hi[0] = NULL; + free (cisdev->calib_hi[1]); cisdev->calib_hi[1] = NULL; + free (cisdev->calib_hi[2]); cisdev->calib_hi[2] = NULL; + free(cisdev->tmpbuf); cisdev->tmpbuf = NULL; + free(cisdev); dev->priv = NULL; + return SANE_STATUS_CANCELLED; /* Most likely cause */ + } + +/* M1015_DISPLAY_REGS(dev, "after calibration"); */ + + cis_get_bank_count(cisdev); + + cis_move_motor (cisdev, dev->topY); /* Measured in max resolution */ + + /* It is vital to reinitialize the scanner right before we start the + real scanning. Otherwise the bank synchronization may have gotten lost + by the time we reach the top of the scan area */ + + cisdev->CIS.setParameters = SANE_TRUE; + cis_config_ccd(cisdev); + cis_wait_read_ready(cisdev); + + sanei_pa4s2_enable (dev->fd, SANE_FALSE); + + cisdev->CIS.line_step = + SANE_FIX ((float) cisdev->CIS.hw_vres / (float) cisdev->CIS.res); + + /* + * It is very important that line_diff is not initialized at zero ! + * If it is set to zero, the motor will keep on moving forever (or better, + * till the scanner breaks). + */ + cisdev->line_diff = cisdev->CIS.line_step; + cisdev->ccd_line = 0; + cisdev->line = 0; + cisdev->lines_left = dev->params.lines; + + dev->state = STATE_SCANNING; + + DBG (3, "cis_drv_start: device ready for scanning\n"); + + return SANE_STATUS_GOOD; +} + +/****************************************************************************** +* Read * +******************************************************************************/ +void cis_drv_read (SANE_Handle hndl, SANE_Byte *buffer) +{ + Mustek_pp_Handle *dev = hndl; + Mustek_PP_CIS_dev *cisdev = dev->priv; + DBG(6, "cis_drv_read: Reading line\n"); + sanei_pa4s2_enable (dev->fd, SANE_TRUE); + switch (dev->mode) + { + case MODE_BW: + cis_get_lineart_line(cisdev, buffer); + break; + + case MODE_GRAYSCALE: + cis_get_grayscale_line(cisdev, buffer); + break; + + case MODE_COLOR: + cis_get_color_line(cisdev, buffer); + break; + } + sanei_pa4s2_enable (dev->fd, SANE_FALSE); +} + +/****************************************************************************** +* Stop * +******************************************************************************/ +void cis_drv_stop (SANE_Handle hndl) +{ + Mustek_pp_Handle *dev = hndl; + Mustek_PP_CIS_dev *cisdev = dev->priv; + + /* device is scanning: return lamp and free buffers */ + DBG (3, "cis_drv_stop: stopping current scan\n"); + dev->state = STATE_CANCELLED; + + DBG (9, "cis_drv_stop: enabling fd\n"); + sanei_pa4s2_enable (dev->fd, SANE_TRUE); + Mustek_PP_1015_write_reg(cisdev, MA1015W_MOTOR_CONTROL, 0); /* stop */ + DBG (9, "cis_drv_stop: resetting device (1)\n"); + cis_reset_device (cisdev); + DBG (9, "cis_drv_stop: returning home\n"); + cis_return_home (cisdev, SANE_TRUE); /* don't wait */ + DBG (9, "cis_drv_stop: resetting device (2)\n"); + cis_reset_device (cisdev); + DBG (9, "cis_drv_stop: disabling fd\n"); + sanei_pa4s2_enable (dev->fd, SANE_FALSE); + DBG (9, "cis_drv_stop: freeing buffers\n"); + + /* This is no good: canceling while the device is scanning and + freeing the data buffers can result in illegal memory accesses if + the device is still scanning in another thread. */ + free (cisdev->calib_low[1]); cisdev->calib_low[1] = NULL; + free (cisdev->calib_hi[1]); cisdev->calib_hi[1] = NULL; + free (cisdev->tmpbuf); cisdev->tmpbuf = NULL; + DBG (3, "cis_drv_stop: freed green and temporary buffers\n"); + + if (cisdev->CIS.mode == MODE_COLOR) + { + free (cisdev->calib_low[0]); cisdev->calib_low[0] = NULL; + free (cisdev->calib_low[2]); cisdev->calib_low[2] = NULL; + free (cisdev->calib_hi[0]); cisdev->calib_hi[0] = NULL; + free (cisdev->calib_hi[2]); cisdev->calib_hi[2] = NULL; + } + DBG (3, "cis_drv_stop: freed buffers\n"); + DBG (6, "cis_drv_stop: lamp_on: %d\n", (int)dev->lamp_on); +} |