diff options
Diffstat (limited to 'backend/hp4200.c')
-rw-r--r-- | backend/hp4200.c | 2979 |
1 files changed, 2979 insertions, 0 deletions
diff --git a/backend/hp4200.c b/backend/hp4200.c new file mode 100644 index 0000000..a069be6 --- /dev/null +++ b/backend/hp4200.c @@ -0,0 +1,2979 @@ +/* + Copyright (C) 2000 by Adrian Perez Jorge + + 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. + */ + +/* Developers: + + Adrian Perez Jorge (APJ) - + Creator of the original HP4200C backend code. + adrianpj@easynews.com + + Andrew John Lewis (AJL) - + lewi0235@tc.umn.edu + + Arnar Mar Hrafnkelsson (AMH) - + addi@umich.edu + + Frank Zago + some cleanups and integration into SANE + + Henning Meier-Geinitz <henning@meier-geinitz.de> + more cleanups, bug fixes + +TODO: + + - support more scanning resolutions. + - support different color depths. + - support gray and lineart. + - improve scanning speed. Compute scanning parameters based on the + image size and the scanner-to-host bandwidth. + - improve image quality. + - fix problem concerning mangled images + +*/ + +#define BUILD 2 +#define BACKEND_NAME hp4200 + +#include "../include/sane/config.h" + +#include <sys/ioctl.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <math.h> +#include <sys/time.h> +#include <stdlib.h> +#include <getopt.h> +#include <ctype.h> +#include <assert.h> + +#include "../include/sane/sane.h" +#include "../include/sane/sanei.h" +#include "../include/sane/sanei_debug.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/sanei_usb.h" +#include "../include/sane/sanei_pv8630.h" +#include "../include/sane/saneopts.h" +#include "../include/sane/sanei_backend.h" +#include "hp4200.h" + +#include "hp4200_lm9830.c" + +#define HP4200_CONFIG_FILE "hp4200.conf" + +/*--------------------------------------------------------------------------*/ + +#if 0 +/* Some of these resolution need work in color shifting. */ +static const SANE_Int dpi_list[] = + { 8, 50, 75, 100, 150, 200, 300, 400, 600 }; +#else +static const SANE_Int dpi_list[] = { 4, 75, 150, 300, 600 }; +#endif +static SANE_Range x_range = { SANE_FIX (0), SANE_FIX (8.5 * MM_PER_INCH), 0 }; +static SANE_Range y_range = + { SANE_FIX (0), SANE_FIX (11.75 * MM_PER_INCH), 0 }; +static const SANE_Range u8_range = { 0, 255, 0 }; + +struct coarse_t +{ + int min_red; + int min_green; + int min_blue; + int max_red; + int max_green; + int max_blue; + int red_gain; + int red_offset; + int green_gain; + int green_offset; + int blue_gain; + int blue_offset; +}; + +static const double hdpi_mapping[8] = { 1, 1.5, 2, 3, 4, 6, 8, 12 }; + +static HP4200_Device *first_device = NULL; /* device list head */ +static int n_devices = 0; /* the device count */ +static const SANE_Device **devlist = NULL; + +static unsigned char +getreg (HP4200_Scanner * s, unsigned char reg) +{ + unsigned char reg_value; + + if ((reg > 0x08) && (reg < 0x5b)) + return (unsigned char) LOBYTE (s->regs[reg]); + else + { + lm9830_read_register (s->fd, reg, ®_value); + return reg_value; + } +} + +static void +setreg (HP4200_Scanner * s, unsigned char reg, unsigned char reg_value) +{ + s->regs[reg] = reg_value; /* dirty bit should be clear with this */ + if ((reg < 0x08) || (reg > 0x5b)) + { + lm9830_write_register (s->fd, reg, reg_value); + } +} + +static void +setbits (HP4200_Scanner * s, unsigned char reg, unsigned char bitmap) +{ + s->regs[reg] = (s->regs[reg] & 0xff) | bitmap; + if ((reg < 0x08) || (reg > 0x5b)) + { + lm9830_write_register (s->fd, reg, LOBYTE (s->regs[reg])); + } +} + +static void +clearbits (HP4200_Scanner * s, unsigned char reg, unsigned char mask) +{ + s->regs[reg] = (s->regs[reg] & ~mask) & 0xff; + if ((reg < 0x08) || (reg > 0x5b)) + { + lm9830_write_register (s->fd, reg, LOBYTE (s->regs[reg])); + } +} + +static int +cache_write (HP4200_Scanner * s) +{ + int i; +#ifdef DEBUG_REG_CACHE + int counter = 0; +#endif + + DBG (DBG_proc, "Writing registers\n"); + + for (i = 0; i < 0x80; i++) + if (!(s->regs[i] & 0x100)) + { /* modified register */ +#ifdef DEBUG_REG_CACHE + fprintf (stderr, "%.2x", i); + if (counter == 8) + fprintf (stderr, "\n"); + else + fprintf (stderr, ", "); + counter = (counter + 1) % 9; +#endif + lm9830_write_register (s->fd, i, s->regs[i]); + s->regs[i] |= 0x100; /* register is updated */ + } + return 0; +} + +/* + * HP4200-dependent register initialization. + */ + +static int +hp4200_init_registers (HP4200_Scanner * s) +{ + /* set up hardware parameters */ + + s->hw_parms.crystal_frequency = 48000000; + s->hw_parms.SRAM_size = 128; /* Kb */ + s->hw_parms.scan_area_width = 5100; /* pixels */ + s->hw_parms.scan_area_length = 11; /* inches */ + s->hw_parms.min_pixel_data_buffer_limit = 1024; /* bytes */ + s->hw_parms.sensor_line_separation = 4; /* lines */ + s->hw_parms.sensor_max_integration_time = 12; /* milliseconds */ + s->hw_parms.home_sensor = 2; + s->hw_parms.sensor_resolution = 1; /* 600 dpi */ + s->hw_parms.motor_full_steps_per_inch = 300; + s->hw_parms.motor_max_speed = 1.4; /* inches/second */ + s->hw_parms.num_tr_pulses = 1; + s->hw_parms.guard_band_duration = 1; + s->hw_parms.pulse_duration = 3; + s->hw_parms.fsteps_25_speed = 3; + s->hw_parms.fsteps_50_speed = 3; + s->hw_parms.target_value.red = 1000; + s->hw_parms.target_value.green = 1000; + s->hw_parms.target_value.blue = 1000; + + { + int i; + + /* + * we are using a cache-like data structure so registers whose + * values were written to the lm9830 and aren't volatile, have + * bit 0x100 activated. This bit must be cleared if you want the + * value to be written to the chip once cache_write() is called. + */ + + /* clears the registers cache */ + memset (s->regs, 0, sizeof (s->regs)); + + /* + * registers 0x00 - 0x07 are non-cacheable/volatile, so don't + * read the values using the cache. Instead use direct functions + * to read/write registers. + */ + + for (i = 0; i < 0x08; i++) + s->regs[i] = 0x100; + } + + setreg (s, 0x70, 0x70); /* noise filter */ + + setreg (s, 0x0b, + INPUT_SIGNAL_POLARITY_NEGATIVE | + CDS_ON | + SENSOR_STANDARD | + SENSOR_RESOLUTION_600 | LINE_SKIPPING_COLOR_PHASE_DELAY (0)); + + setreg (s, 0x0c, + PHI1_POLARITY_POSITIVE | + PHI2_POLARITY_POSITIVE | + RS_POLARITY_POSITIVE | + CP1_POLARITY_POSITIVE | + CP2_POLARITY_POSITIVE | + TR1_POLARITY_NEGATIVE | TR2_POLARITY_NEGATIVE); + + setreg (s, 0x0d, + PHI1_ACTIVE | + PHI2_ACTIVE | + RS_ACTIVE | + CP1_ACTIVE | + CP2_OFF | + TR1_ACTIVE | + TR2_OFF | NUMBER_OF_TR_PULSES (s->hw_parms.num_tr_pulses)); + + setreg (s, 0x0e, + TR_PULSE_DURATION (s->hw_parms.pulse_duration) | + TR_PHI1_GUARDBAND_DURATION (s->hw_parms.guard_band_duration)); + + /* for pixel rate timing */ + setreg (s, 0x0f, 6); + setreg (s, 0x10, 23); + setreg (s, 0x11, 1); + setreg (s, 0x12, 3); + setreg (s, 0x13, 3); /* 0 */ + setreg (s, 0x14, 5); /* 0 */ + setreg (s, 0x15, 0); + setreg (s, 0x16, 0); + setreg (s, 0x17, 11); + setreg (s, 0x18, 2); /* 1 */ + + setreg (s, 0x19, CIS_TR1_TIMING_OFF | FAKE_OPTICAL_BLACK_PIXELS_OFF); + + setreg (s, 0x1a, 0); + setreg (s, 0x1b, 0); + + setreg (s, 0x1c, 0x0d); + setreg (s, 0x1d, 0x21); + + setreg (s, 0x27, TR_RED_DROP (0) | TR_GREEN_DROP (0) | TR_BLUE_DROP (0)); + + setreg (s, 0x28, 0x00); + + setreg (s, 0x29, ILLUMINATION_MODE (1)); + setreg (s, 0x2a, HIBYTE (0)); /* 0 */ + setreg (s, 0x2b, LOBYTE (0)); /* 0 */ + + setreg (s, 0x2c, HIBYTE (16383)); + setreg (s, 0x2d, LOBYTE (16383)); + + setreg (s, 0x2e, HIBYTE (2)); /* 2 */ + setreg (s, 0x2f, LOBYTE (2)); /* 1 */ + + setreg (s, 0x30, HIBYTE (0)); + setreg (s, 0x31, LOBYTE (0)); + + setreg (s, 0x32, HIBYTE (0)); + setreg (s, 0x33, LOBYTE (0)); + + setreg (s, 0x34, HIBYTE (32)); + setreg (s, 0x35, LOBYTE (32)); + + setreg (s, 0x36, HIBYTE (48)); + setreg (s, 0x37, LOBYTE (48)); + + setreg (s, 0x42, EPP_MODE | PPORT_DRIVE_CURRENT (3)); + + setreg (s, 0x43, + RAM_SIZE_128 | + SRAM_DRIVER_CURRENT (3) | SRAM_BANDWIDTH_8 | SCANNING_FULL_DUPLEX); + + setreg (s, 0x45, + MICRO_STEPPING | + CURRENT_SENSING_PHASES (2) | + PHASE_A_POLARITY_POSITIVE | + PHASE_B_POLARITY_POSITIVE | STEPPER_MOTOR_OUTPUT); + + setreg (s, 0x4a, HIBYTE (100)); + setreg (s, 0x4b, LOBYTE (100)); + + setreg (s, 0x4c, HIBYTE (0)); + setreg (s, 0x4d, LOBYTE (0)); + + /* resume scan threshold */ + setreg (s, 0x4f, 64); + /* steps to reverse */ + setreg (s, 0x50, 40); + setreg (s, 0x51, + ACCELERATION_PROFILE_STOPPED (3) | + ACCELERATION_PROFILE_25P (s->hw_parms.fsteps_25_speed) | + ACCELERATION_PROFILE_50P (s->hw_parms.fsteps_50_speed)); + setreg (s, 0x54, NON_REVERSING_EXTRA_LINES (0) | FIRST_LINE_TO_PROCESS (0)); + setreg (s, 0x55, KICKSTART_STEPS (0) | HOLD_CURRENT_TIMEOUT (2)); + + /* stepper PWM frequency */ + setreg (s, 0x56, 8); + /* stepper pwm duty cycle */ + setreg (s, 0x57, 23); + + setreg (s, 0x58, + PAPER_SENSOR_1_POLARITY_HIGH | + PAPER_SENSOR_1_TRIGGER_EDGE | + PAPER_SENSOR_1_NO_STOP_SCAN | + PAPER_SENSOR_2_POLARITY_HIGH | + PAPER_SENSOR_2_TRIGGER_EDGE | PAPER_SENSOR_2_STOP_SCAN); + setreg (s, 0x59, + MISCIO_1_TYPE_OUTPUT | + MISCIO_1_POLARITY_HIGH | + MISCIO_1_TRIGGER_EDGE | + MISCIO_1_OUTPUT_STATE_HIGH | + MISCIO_2_TYPE_OUTPUT | + MISCIO_2_POLARITY_HIGH | + MISCIO_2_TRIGGER_EDGE | MISCIO_2_OUTPUT_STATE_HIGH); + + return 0; +} + +#ifdef DEBUG +static int +dump_register_cache (HP4200_Scanner * s) +{ + int i; + + for (i = 0; i < 0x80; i++) + { + fprintf (stderr, "%.2x:0x%.2x", i, s->regs[i]); + if ((i + 1) % 8) + fprintf (stderr, ", "); + else + fprintf (stderr, "\n"); + } + fputs ("", stderr); + return 0; +} +#endif + +/* + * returns the scanner head to home position + */ + +static int +hp4200_goto_home (HP4200_Scanner * s) +{ + unsigned char cmd_reg; + unsigned char status_reg; + unsigned char old_paper_sensor_reg; + + cmd_reg = getreg (s, 0x07); + if (cmd_reg != 2) + { + unsigned char paper_sensor_reg; + unsigned char sensor_bit[2] = { 0x02, 0x10 }; + /* sensor head is not returning */ + + /* let's see if it's already at home */ + /* first put paper (head) sensor level sensitive */ + paper_sensor_reg = getreg (s, 0x58); + old_paper_sensor_reg = paper_sensor_reg; + paper_sensor_reg &= ~sensor_bit[s->hw_parms.home_sensor - 1]; + setreg (s, 0x58, paper_sensor_reg); + cache_write (s); + + /* if the scan head is not at home then move motor backwards */ + status_reg = getreg (s, 0x02); + setreg (s, 0x58, old_paper_sensor_reg); + cache_write (s); + if (!(status_reg & s->hw_parms.home_sensor)) + { + setreg (s, 0x07, 0x08); + usleep (10 * 1000); + setreg (s, 0x07, 0x00); + usleep (10 * 1000); + setreg (s, 0x07, 0x02); + } + } + return 0; +} + +#define HP4200_CHECK_INTERVAL 1000 /* usecs between status checks */ +static int +hp4200_wait_homed (HP4200_Scanner * s) +{ + unsigned char cmd_reg; + + cmd_reg = getreg (s, 0x07); + while (cmd_reg != 0) + { + usleep (HP4200_CHECK_INTERVAL); + cmd_reg = getreg (s, 0x07); + } + return 0; +} + +static int +compute_fastfeed_step_size (unsigned long crystal_freq, int mclk, + float max_speed, int steps_per_inch, + int color_mode) +{ + int aux; + int r; + + if (color_mode == 0) + r = 24; + else + r = 8; + + aux = floor (crystal_freq / ((double) mclk * max_speed * 4.0 * + steps_per_inch * r)); + + + if (aux < 2) + aux = 2; + return aux; +} + +static SANE_Status +read_available_data (HP4200_Scanner * s, SANE_Byte * buffer, + size_t * byte_count) +{ + SANE_Status status; + unsigned char scankb1; + unsigned char scankb2; + size_t to_read; + size_t really_read; + size_t chunk; + + assert (buffer != NULL); + + *byte_count = 0; + do + { + scankb1 = getreg (s, 0x01); + scankb2 = getreg (s, 0x01); + if (s->aborted_by_user) + return SANE_STATUS_CANCELLED; + } + while ((scankb1 != scankb2) || (scankb1 < 12)); + + to_read = scankb1 * 1024; + + while (to_read) + { + if (s->aborted_by_user) + return SANE_STATUS_CANCELLED; + chunk = (to_read > 0xffff) ? 0xffff : to_read; + + sanei_pv8630_write_byte (s->fd, PV8630_REPPADDRESS, 0x00); + sanei_pv8630_prep_bulkread (s->fd, chunk); + really_read = chunk; + if ((status = sanei_usb_read_bulk (s->fd, buffer, &really_read)) != + SANE_STATUS_GOOD) + { + DBG (DBG_error, "sanei_usb_read_bulk failed (%s)\n", + sane_strstatus (status)); + return status; + } + if (really_read > to_read) + { + DBG (DBG_error, "USB stack read more bytes than requested!\n"); + return SANE_STATUS_IO_ERROR; + } + *byte_count += really_read; + buffer += really_read; + to_read -= really_read; +#ifdef DEBUG + fprintf (stderr, "read %d bytes\n", really_read); +#endif + } + return SANE_STATUS_GOOD; +} + +#ifdef unused +static int +compute_datalink_bandwidth (HP4200_Scanner * s) +{ + int line_size; + int pause_limit; + unsigned int color_mode; + + /* + * Line size for 8 bpp, the entire scan area width (plus the + * status byte) at optical resolution. + */ + + if (s->user_parms.color) + { + line_size = 3 * s->hw_parms.scan_area_width + 1; + color_mode = 0; + setreg (s, 0x26, color_mode); /* 3 channel pixel rate color */ + } + else + { + line_size = s->hw_parms.scan_area_width + 1; + color_mode = 4; + setreg (s, 0x26, 0x08 | color_mode); /* 1 channel mode A (green) */ + } + setreg (s, 0x09, (3 << 3)); /* h-divider = 1, 8 bpp */ + + { + int first_white_pixel; + unsigned int line_end; + + first_white_pixel = s->hw_parms.sensor_pixel_end - 10; + line_end = first_white_pixel + s->hw_parms.scan_area_width; + if (line_end > (s->hw_parms.sensor_num_pixels - 20)) + line_end = s->hw_parms.sensor_num_pixels - 20; + + setreg (s, 0x1c, HIBYTE (s->hw_parms.sensor_pixel_start)); + setreg (s, 0x1d, LOBYTE (s->hw_parms.sensor_pixel_end)); + setreg (s, 0x1e, HIBYTE (first_white_pixel)); + setreg (s, 0x1f, LOBYTE (first_white_pixel)); + setreg (s, 0x20, HIBYTE (s->hw_parms.sensor_num_pixels)); + setreg (s, 0x21, LOBYTE (s->hw_parms.sensor_num_pixels)); + setreg (s, 0x22, getreg (s, 0x1e)); + setreg (s, 0x23, getreg (s, 0x1f)); + setreg (s, 0x24, HIBYTE (line_end)); + setreg (s, 0x25, LOBYTE (line_end)); + } + + /* + * During transfer rate calculation don't forward scanner sensor. + * Stay in the calibration region. + */ + + setreg (s, 0x4f, 0); + clearbits (s, 0x45, 0x10); + + /* + * Pause the scan when memory is full. + */ + + pause_limit = s->hw_parms.SRAM_size - (line_size / 1024) - 1; + setreg (s, 0x4e, pause_limit & 0xff); + + s->mclk = compute_min_mclk (s->hw_parms.SRAM_bandwidth, + s->hw_parms.crystal_frequency); + + + /* + * Set step size to fast speed. + */ + + { + int step_size; + + step_size = + compute_fastfeed_step_size (s->hw_parms.crystal_frequency, + s->mclk, + s->hw_parms.scan_bar_max_speed, + s->hw_parms.motor_full_steps_per_inch, + color_mode); + + setreg (s, 0x46, HIBYTE (step_size)); + setreg (s, 0x47, LOBYTE (step_size)); + setreg (s, 0x48, HIBYTE (step_size)); + setreg (s, 0x49, LOBYTE (step_size)); + } + + cache_write (s); + + /* dump_register_cache (s); */ + + /* + * scan during 1 sec. aprox. + */ + + setreg (s, 0x07, 0x08); + setreg (s, 0x07, 0x03); + + { + struct timeval tv_before; + struct timeval tv_after; + int elapsed_time_ms = 0; + long bytes_read_total; + SANE_Byte *buffer; + + buffer = malloc (2 * 98304); /* check this */ + if (!buffer) + { + DBG (DBG_error, "compute_datalink_bandwidth: malloc failed\n"); + return 0; + } + bytes_read_total = 0; + gettimeofday (&tv_before, NULL); + do + { + size_t bytes_read; + SANE_Status status; + + status = read_available_data (s, buffer, &bytes_read); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "read_available_data failed (%s)\n", + sane_strstatus (status)); + return 0; + } + bytes_read_total += bytes_read; + gettimeofday (&tv_after, NULL); + elapsed_time_ms = (tv_after.tv_sec - tv_before.tv_sec) * 1000; + elapsed_time_ms += (tv_after.tv_usec - tv_before.tv_usec) / 1000; + } + while (elapsed_time_ms < 1000); + + setreg (s, 0x07, 0x00); + free (buffer); + + s->msrd_parms.datalink_bandwidth = bytes_read_total / + (elapsed_time_ms / 1000); + +#ifdef DEBUG + fprintf (stderr, "PC Transfer rate = %d bytes/sec. (%ld/%d)\n", + s->msrd_parms.datalink_bandwidth, bytes_read_total, + elapsed_time_ms); +#endif + } + return 0; +} +#endif + +static void +compute_first_gain_offset (int target, int max, int min, int *gain, + int *offset, int *max_gain, int *min_offset) +{ + *gain = (int) 15.0 *(target / (max - min) - 0.933); + *offset = (int) (-1.0 * min / (512.0 * 0.0195)); + if (*gain >= 32) + { + *gain = (int) 15.0 *(target / 3.0 / (max - min) - 0.933); + *offset = (int) -3.0 * min / (512.0 * 0.0195); + } + if (*gain < 0) + *gain = 0; + else if (*gain > 63) + *gain = 63; + + if (*offset < -31) + *offset = -31; + else if (*offset > 31) + *offset = 31; + + *max_gain = 63; + *min_offset = -31; +} + +#define DATA_PORT_READ (1 << 5) +#define DATA_PORT_WRITE 0 + +static int +write_gamma (HP4200_Scanner * s) +{ + SANE_Status status; + int color; + int i; + unsigned char gamma[1024]; + unsigned char read_gamma[1024]; + int retval; + size_t to_read; + size_t to_write; + + for (color = 0; color < 3; color++) + { + for (i = 0; i < 1024; i++) + gamma[i] = s->user_parms.gamma[color][i]; + + setreg (s, 0x03, color << 1); + setreg (s, 0x04, DATA_PORT_WRITE); + setreg (s, 0x05, 0x00); + sanei_pv8630_write_byte (s->fd, PV8630_REPPADDRESS, 0x06); + sanei_pv8630_prep_bulkwrite (s->fd, sizeof (gamma)); + to_write = sizeof (gamma); + sanei_usb_write_bulk (s->fd, gamma, &to_write); + + /* check if gamma vector was correctly written */ + + setreg (s, 0x03, color << 1); + setreg (s, 0x04, DATA_PORT_READ); + setreg (s, 0x05, 0x00); + sanei_pv8630_write_byte (s->fd, PV8630_REPPADDRESS, 0x06); + sanei_pv8630_prep_bulkread (s->fd, sizeof (read_gamma)); + to_read = sizeof (read_gamma); + status = sanei_usb_read_bulk (s->fd, read_gamma, &to_read); + retval = memcmp (read_gamma, gamma, sizeof (read_gamma)); + if (retval != 0) + { + DBG (DBG_error, "error: color %d has bad gamma table\n", color); + } +#ifdef DEBUG + else + fprintf (stderr, "color %d gamma table is good\n", color); +#endif + } + + return 0; +} + +static int +write_default_offset_gain (HP4200_Scanner * s, SANE_Byte * gain_offset, + int size, int color) +{ + SANE_Byte *check_data; + int retval; + size_t to_read; + size_t to_write; + + setreg (s, 0x03, (color << 1) | 1); + setreg (s, 0x04, DATA_PORT_WRITE); + setreg (s, 0x05, 0x00); + sanei_pv8630_write_byte (s->fd, PV8630_REPPADDRESS, 0x06); + sanei_pv8630_prep_bulkwrite (s->fd, size); + to_write = size; + sanei_usb_write_bulk (s->fd, gain_offset, &to_write); + + check_data = malloc (size); + setreg (s, 0x03, (color << 1) | 1); + setreg (s, 0x04, DATA_PORT_READ); + setreg (s, 0x05, 0x00); + sanei_pv8630_write_byte (s->fd, PV8630_REPPADDRESS, 0x06); + sanei_pv8630_prep_bulkread (s->fd, size); + to_read = size; + sanei_usb_read_bulk (s->fd, check_data, &to_read); + retval = memcmp (gain_offset, check_data, size); + free (check_data); + if (retval != 0) + { + DBG (DBG_error, "error: color %d has bad gain/offset table\n", color); + } +#ifdef DEBUG + else + fprintf (stderr, "color %d gain/offset table is good\n", color); +#endif + + return 0; +} + +static int +compute_gain_offset (int target, int max, int min, int *gain, + int *offset, int *max_gain, int *min_offset) +{ + int gain_stable; + int is_unstable; + + gain_stable = 1; /* unless the opposite is said */ + is_unstable = 0; + + if (max > target) + { + if (*gain > 0) + { + (*gain)--; + *max_gain = *gain; + gain_stable = 0; + is_unstable |= 1; + } + else + { + DBG (DBG_error, "error: integration time too long.\n"); + return -1; + } + } + else + { + if (*gain < *max_gain) + { + (*gain)++; + gain_stable = 0; + is_unstable |= 1; + } + } + + if (min == 0) + { + if (*offset < 31) + { + (*offset)++; + if (gain_stable) + *min_offset = *offset; + is_unstable |= 1; + } + else + { + DBG (DBG_error, "error: max static has pixel value == 0\n"); + return -1; + } + } + else + { + if (*offset > *min_offset) + { + (*offset)--; + is_unstable |= 1; + } + } + return is_unstable; +} + +static int +compute_bytes_per_line (int width_in_pixels, unsigned char hdpi_code, + unsigned char pixel_packing, + unsigned char data_mode, + unsigned char AFE_operation, int m) +{ + const int dpi_qot_mul[] = { 1, 2, 1, 1, 1, 1, 1, 1 }; + const int dpi_qot_div[] = { 1, 3, 2, 3, 4, 6, 8, 12 }; + int pixels_per_line; + int bytes_per_line; + int pixels_per_byte; + int status_bytes; + const int pixels_per_byte_mapping[] = { 8, 4, 2, 1 }; + + assert (hdpi_code <= 7); + pixels_per_line = (width_in_pixels * dpi_qot_mul[hdpi_code]) / + dpi_qot_div[hdpi_code]; + if ((width_in_pixels * dpi_qot_mul[hdpi_code]) % dpi_qot_div[hdpi_code]) + pixels_per_line++; + + + status_bytes = (m == 0) ? 1 : m; + + if (data_mode == 1) + pixels_per_byte = 1; /* should be 0.5 but later + bytes_per_line will be multiplied + by 2, and also the number of status + bytes, that in this case should be + 2. + umm.. maybe this should be done in + the cleaner way. + */ + else + { + assert (pixel_packing <= 3); + pixels_per_byte = pixels_per_byte_mapping[pixel_packing]; + } + + switch (AFE_operation) + { + case PIXEL_RATE_3_CHANNELS: + bytes_per_line = ((pixels_per_line * 3) / pixels_per_byte) + + status_bytes; + break; + case MODEA_1_CHANNEL: + bytes_per_line = (pixels_per_line / pixels_per_byte) + status_bytes; + break; + default: + /* Not implemented! (yet?) and not used. + * This case should not happen. */ + assert (0); + } + + if (data_mode == 1) /* see big note above */ + bytes_per_line *= 2; + + return bytes_per_line; +} + +static int +compute_pause_limit (hardware_parameters_t * hw_parms, int bytes_per_line) +{ + int coef_size; + const int coef_mapping[] = { 16, 32 }; + int pause_limit; + + coef_size = coef_mapping[hw_parms->sensor_resolution & 0x01]; + pause_limit = hw_parms->SRAM_size - coef_size - (bytes_per_line / 1024) - 1; + + if (pause_limit > 2) + pause_limit -= 2; + + return pause_limit; +} + +static int +compute_dpd (HP4200_Scanner * s, int step_size, int line_end) +{ + int tr, dpd; + + tr = 1 /* color mode */ * + (line_end + ((s->hw_parms.num_tr_pulses + 1) * + (2 * s->hw_parms.guard_band_duration + + s->hw_parms.pulse_duration + 1) + + 3 - s->hw_parms.num_tr_pulses)); + + if (tr == 0) + return 0; + + dpd = (((s->hw_parms.fsteps_25_speed * 4) + + (s->hw_parms.fsteps_50_speed * 2) + + s->hw_parms.steps_to_reverse) * 4 * step_size) % tr; + dpd = tr - dpd; + + return dpd; +} + +static SANE_Status +read_required_bytes (HP4200_Scanner * s, int required, SANE_Byte * buffer) +{ + int read_count = 0; + unsigned char scankb1; + unsigned char scankb2; + size_t to_read; + size_t really_read; + size_t chunk; + SANE_Status status; + + assert (buffer != NULL); + + while (required) + { + do + { + scankb1 = getreg (s, 0x01); + scankb2 = getreg (s, 0x01); + if (s->aborted_by_user) + return SANE_STATUS_CANCELLED; + } + while ((scankb1 != scankb2) || (scankb1 < 12)); + + to_read = min (required, (scankb1 * 1024)); + while (to_read) + { + if (s->aborted_by_user) + return SANE_STATUS_CANCELLED; + chunk = (to_read > 0xffff) ? 0xffff : to_read; + + sanei_pv8630_write_byte (s->fd, PV8630_REPPADDRESS, 0x00); + sanei_pv8630_prep_bulkread (s->fd, chunk); + really_read = chunk; + if ((status = sanei_usb_read_bulk (s->fd, buffer, &really_read)) != + SANE_STATUS_GOOD) + { + DBG (DBG_error, "sanei_usb_read_bulk failed (%s)\n", + sane_strstatus (status)); + return status; + } + if (really_read > chunk) + { + DBG (DBG_error, "USB stack read more bytes than requested!\n"); + return SANE_STATUS_IO_ERROR; + } + buffer += really_read; + required -= really_read; + to_read -= really_read; + read_count += really_read; + } + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +scanner_buffer_init (scanner_buffer_t * sb, int size_in_kb) +{ + + sb->size = size_in_kb * 1024 + 3; + sb->buffer = malloc (sb->size); + if (!sb->buffer) + return SANE_STATUS_NO_MEM; + sb->num_bytes = 0; + sb->data_ptr = sb->buffer; + + return SANE_STATUS_GOOD; +} + +static SANE_Status +scanner_buffer_read (HP4200_Scanner * s) +{ + SANE_Status status; + size_t num_bytes_read_now; + + assert (s->scanner_buffer.num_bytes <= 3); + + memcpy (s->scanner_buffer.buffer, s->scanner_buffer.data_ptr, 3); + + status = read_available_data (s, s->scanner_buffer.buffer + + s->scanner_buffer.num_bytes, + &num_bytes_read_now); + s->scanner_buffer.data_ptr = s->scanner_buffer.buffer; + s->scanner_buffer.num_bytes += num_bytes_read_now; + return status; +} + +#define OFFSET_CODE_SIGN(off) (((off) < 0) ? (-(off) & 0x1f) | 0x20 : (off)) +#define OFFSET_DECODE_SIGN(off) (((off) & 0x20) ? -(off & 0x1f) : (off)) + +static SANE_Status +do_coarse_calibration (HP4200_Scanner * s, struct coarse_t *coarse) +{ + SANE_Status status; + unsigned char *cal_line = NULL; + unsigned char *cal_line_ptr; + int cal_line_size; + /* local scanning params */ + int active_pixels_start; + int line_end; + int data_pixels_start; + int data_pixels_end; + int dpd; + int step_size; + int ff_step_size; + char steps_to_reverse; + char hdpi_div; + char line_rate_color; + int vdpi; /* vertical dots per inch */ + int hdpi_code; + int calibrated; + int first_time; + + int red_offset = 0; + int green_offset = 0; + int blue_offset = 0; + + int red_gain = 1; + int green_gain = 1; + int blue_gain = 1; + + int min_red_offset = -31; + int min_green_offset = -31; + int min_blue_offset = -31; + + int max_red_gain = 63; + int max_green_gain = 63; + int max_blue_gain = 63; + + int max_red; + int min_red; + int max_green; + int min_green; + int max_blue; + int min_blue; + static char me[] = "do_coarse_calibration"; + + DBG (DBG_proc, "%s\n", me); + + setreg (s, 0x07, 0x00); + usleep (10 * 1000); + + vdpi = 150; + hdpi_code = 0; + hdpi_div = hdpi_mapping[hdpi_code]; + active_pixels_start = 0x40; + line_end = 0x2ee0; + s->mclk_div = 2; + data_pixels_start = 0x40; + data_pixels_end = (int) (data_pixels_start + s->hw_parms.scan_area_width); + data_pixels_end = min (data_pixels_end, line_end - 20); + + cal_line_size = s->hw_parms.scan_area_width * 3 * 2 + 2; + + setreg (s, 0x1e, HIBYTE (active_pixels_start)); + setreg (s, 0x1f, LOBYTE (active_pixels_start)); + setreg (s, 0x20, HIBYTE (line_end)); + setreg (s, 0x21, LOBYTE (line_end)); + setreg (s, 0x22, HIBYTE (data_pixels_start)); + setreg (s, 0x23, LOBYTE (data_pixels_start)); + setreg (s, 0x24, HIBYTE (data_pixels_end)); + setreg (s, 0x25, LOBYTE (data_pixels_end)); + + setreg (s, 0x26, + PIXEL_RATE_3_CHANNELS | + GRAY_CHANNEL_RED | TR_RED (0) | TR_GREEN (0) | TR_BLUE (0)); + + + setreg (s, 0x08, (s->mclk_div - 1) * 2); + setreg (s, 0x09, hdpi_code | PIXEL_PACKING (3) | DATAMODE (1)); + setreg (s, 0x0a, 0); /* reserved and strange register */ + + setreg (s, 0x38, red_offset); + setreg (s, 0x39, green_offset); + setreg (s, 0x3a, blue_offset); + setreg (s, 0x3b, red_gain); + setreg (s, 0x3c, green_gain); + setreg (s, 0x3d, blue_gain); + + setreg (s, 0x5e, 0x80); + + setreg (s, 0x3e, 0x00); /* 1.5:1, 6/10 bits, 2*fixed */ + setreg (s, 0x3f, 0x00); + setreg (s, 0x40, 0x00); + setreg (s, 0x41, 0x00); + + setreg (s, 0x4e, 0x5b - 0x3c); /* max Kb to pause */ + setreg (s, 0x4f, 0x02); /* min Kb to resume */ + + line_rate_color = 1; + step_size = (vdpi * line_end * line_rate_color) / + (4 * s->hw_parms.motor_full_steps_per_inch); + + dpd = compute_dpd (s, step_size, line_end); /* 0x0ada; */ +#ifdef DEBUG + fprintf (stderr, "dpd = %d\n", dpd); +#endif + setreg (s, 0x52, HIBYTE (dpd)); + setreg (s, 0x53, LOBYTE (dpd)); + + setreg (s, 0x46, HIBYTE (step_size)); + setreg (s, 0x47, LOBYTE (step_size)); + + ff_step_size = compute_fastfeed_step_size (s->hw_parms.crystal_frequency, s->mclk_div, s->hw_parms.motor_max_speed, s->hw_parms.motor_full_steps_per_inch, 0); /* 0x0190; */ + setreg (s, 0x48, HIBYTE (ff_step_size)); + setreg (s, 0x49, LOBYTE (ff_step_size)); + setreg (s, 0x4b, 0x15); + steps_to_reverse = 0x3f; + setreg (s, 0x50, steps_to_reverse); + setreg (s, 0x51, 0x15); /* accel profile */ + + /* this is to stay the motor stopped */ + clearbits (s, 0x45, (1 << 4)); + + cache_write (s); + + calibrated = 0; + first_time = 1; + cal_line = malloc (cal_line_size + 1024); + + do + { + unsigned char cmd_reg; + + /* resets the lm9830 before start scanning */ + setreg (s, 0x07, 0x08); + do + { + setreg (s, 0x07, 0x03); + cmd_reg = getreg (s, 0x07); + } + while (cmd_reg != 0x03); + + cal_line_ptr = cal_line; + status = read_required_bytes (s, cal_line_size, cal_line_ptr); + if (status != SANE_STATUS_GOOD) + goto done; + + setreg (s, 0x07, 0x00); + { + unsigned int i; + min_red = max_red = (cal_line[0] * 256 + cal_line[1]) >> 2; + min_green = max_green = (cal_line[2] * 256 + cal_line[3]) >> 2; + min_blue = max_blue = (cal_line[4] * 256 + cal_line[5]) >> 2; + for (i = 6; i < (s->hw_parms.scan_area_width * 3 * 2); i += 6) + { + int value; + + value = cal_line[i] * 256 + cal_line[i + 1]; + value >>= 2; + if (value > max_red) + max_red = value; + value = cal_line[i + 2] * 256 + cal_line[i + 3]; + value >>= 2; + if (value > max_green) + max_green = value; + value = cal_line[i + 4] * 256 + cal_line[i + 5]; + value >>= 2; + if (value > max_blue) + max_blue = value; + value = cal_line[i] * 256 + cal_line[i + 1]; + value >>= 2; + if (value < min_red) + min_red = value; + value = cal_line[i + 2] * 256 + cal_line[i + 3]; + value >>= 2; + if (value < min_green) + min_green = value; + value = cal_line[i + 4] * 256 + cal_line[i + 5]; + value >>= 2; + if (value < min_blue) + min_blue = value; + } +#ifdef DEBUG + fprintf (stderr, "max_red:%d max_green:%d max_blue:%d\n", + max_red, max_green, max_blue); + fprintf (stderr, "min_red:%d min_green:%d min_blue:%d\n", + min_red, min_green, min_blue); +#endif + + if (first_time) + { + first_time = 0; + compute_first_gain_offset (s->hw_parms.target_value.red, + max_red, min_red, + &red_gain, &red_offset, + &max_red_gain, &min_red_offset); + compute_first_gain_offset (s->hw_parms.target_value.green, + max_green, min_green, + &green_gain, &green_offset, + &max_green_gain, &min_green_offset); + compute_first_gain_offset (s->hw_parms.target_value.blue, + max_blue, min_blue, &blue_gain, + &blue_offset, &max_blue_gain, + &min_blue_offset); + } + else + { + int retval; + + /* this code should check return value -1 for error */ + + retval = compute_gain_offset (s->hw_parms.target_value.red, + max_red, min_red, + &red_gain, &red_offset, + &max_red_gain, &min_red_offset); + if (retval < 0) + break; + retval |= compute_gain_offset (s->hw_parms.target_value.green, + max_green, min_green, + &green_gain, &green_offset, + &max_green_gain, + &min_green_offset); + if (retval < 0) + break; + retval |= compute_gain_offset (s->hw_parms.target_value.blue, + max_blue, min_blue, + &blue_gain, &blue_offset, + &max_blue_gain, &min_blue_offset); + if (retval < 0) + break; + calibrated = !retval; + } + + setreg (s, 0x3b, red_gain); + setreg (s, 0x3c, green_gain); + setreg (s, 0x3d, blue_gain); + + setreg (s, 0x38, OFFSET_CODE_SIGN (red_offset)); + setreg (s, 0x39, OFFSET_CODE_SIGN (green_offset)); + setreg (s, 0x3a, OFFSET_CODE_SIGN (blue_offset)); + +#ifdef DEBUG + fprintf (stderr, "%d, %d, %d %d, %d, %d\n", red_gain, + green_gain, blue_gain, red_offset, green_offset, + blue_offset); +#endif + cache_write (s); + } + } + while (!calibrated); + coarse->min_red = min_red; + coarse->min_green = min_green; + coarse->min_blue = min_blue; + coarse->max_red = max_red; + coarse->max_green = max_green; + coarse->max_blue = max_blue; + coarse->red_gain = red_gain; + coarse->green_gain = green_gain; + coarse->blue_gain = blue_gain; + coarse->red_offset = red_offset; + coarse->green_offset = green_offset; + coarse->blue_offset = blue_offset; + + status = SANE_STATUS_GOOD; + +done: + if (cal_line) + free (cal_line); + + return status; +} + +static int +compute_corr_code (int average, int min_color, int range, int target) +{ + int value; + int corr_code; + + value = average - min_color; + if (value > 0) + corr_code = + (int) (range * ((double) target / (double) value - 1.0) + 0.5); + else + corr_code = 0; + if (corr_code < 0) + corr_code = 0; + else if (corr_code > 2048) + corr_code = 0; + else if (corr_code > 1023) + corr_code = 1023; + return corr_code; +} + +static int +compute_hdpi_code (int hres) +{ + int hdpi_code; + + /* Calculate the horizontal DPI code based on the requested + horizontal resolution. Defaults to 150dpi. */ + switch (hres) + { + case 600: + hdpi_code = 0; + break; + case 400: + hdpi_code = 1; + break; + case 300: + hdpi_code = 2; + break; + case 200: + hdpi_code = 3; + break; + case 150: + hdpi_code = 4; + break; + case 100: + hdpi_code = 5; + break; + case 75: + hdpi_code = 6; + break; + case 50: + hdpi_code = 7; + break; + default: + hdpi_code = 4; + } + return hdpi_code; +} + + +static SANE_Status +do_fine_calibration (HP4200_Scanner * s, struct coarse_t *coarse) +{ + SANE_Status status; + unsigned char *cal_line; + unsigned char *cal_line_ptr; + int *average; + SANE_Byte red_gain_offset[5460 * 2]; + SANE_Byte green_gain_offset[5460 * 2]; + SANE_Byte blue_gain_offset[5460 * 2]; + int *corr_red = NULL; + int *corr_green = NULL; + int *corr_blue = NULL; + int registro[30][5460 * 3]; + int cal_line_size; + /* local scanning params */ + int active_pixels_start; + int line_end; + int line_length; + int data_pixels_start; + int data_pixels_end; + int dpd; + int step_size; + int ff_step_size; + char steps_to_reverse; + char hdpi_div; + char line_rate_color; + int vdpi; /* vertical dots per inch */ + int hdpi_code; + int calibrated; + int first_time; + int lines_to_process; + + static char me[] = "do_fine_calibration"; + + DBG (DBG_proc, "%s\n", me); + + setreg (s, 0x07, 0x00); + usleep (10 * 1000); + + vdpi = 150; + hdpi_code = compute_hdpi_code (s->user_parms.horizontal_resolution); + + /* figure out which horizontal divider to use based on the + calculated horizontal dpi code */ + hdpi_div = hdpi_mapping[hdpi_code]; + active_pixels_start = 0x40; + line_end = 0x2ee0; + line_length = s->user_parms.image_width * hdpi_div; + s->mclk_div = 2; + data_pixels_start = 0x72 + s->runtime_parms.first_pixel * hdpi_div; + data_pixels_end = + (int) (data_pixels_start + s->user_parms.image_width * hdpi_div); + data_pixels_end = min (data_pixels_end, line_end - 20); + + cal_line_size = line_length * 3 * 2 + 2; + + setreg (s, 0x1e, HIBYTE (active_pixels_start)); + setreg (s, 0x1f, LOBYTE (active_pixels_start)); + setreg (s, 0x20, HIBYTE (line_end)); + setreg (s, 0x21, LOBYTE (line_end)); + setreg (s, 0x22, HIBYTE (data_pixels_start)); + setreg (s, 0x23, LOBYTE (data_pixels_start)); + setreg (s, 0x24, HIBYTE (data_pixels_end)); + setreg (s, 0x25, LOBYTE (data_pixels_end)); + + setreg (s, 0x26, + PIXEL_RATE_3_CHANNELS | + GRAY_CHANNEL_RED | TR_RED (0) | TR_GREEN (0) | TR_BLUE (0)); + + + setreg (s, 0x08, (s->mclk_div - 1) * 2); + setreg (s, 0x09, 0 | PIXEL_PACKING (3) | DATAMODE (1)); + setreg (s, 0x0a, 0); /* reserved and strange register */ + + setreg (s, 0x38, 1); + setreg (s, 0x39, 1); + setreg (s, 0x3a, 1); + setreg (s, 0x3b, coarse->red_gain); + setreg (s, 0x3c, coarse->green_gain); + setreg (s, 0x3d, coarse->blue_gain); + + setreg (s, 0x5e, 0x80); + + setreg (s, 0x3e, 0x00); /* 1.5:1, 6/10 bits, 2*fixed */ + setreg (s, 0x3f, 0x00); + setreg (s, 0x40, 0x00); + setreg (s, 0x41, 0x00); + + setreg (s, 0x4e, 0x5b - 0x3c); /* max Kb to pause */ + setreg (s, 0x4f, 0x02); /* min Kb to resume */ + + line_rate_color = 1; + step_size = (vdpi * line_end * line_rate_color) / + (4 * s->hw_parms.motor_full_steps_per_inch); + + dpd = compute_dpd (s, step_size, line_end); /* 0x0ada; */ +#ifdef DEBUG + fprintf (stderr, "dpd = %d\n", dpd); +#endif + setreg (s, 0x52, HIBYTE (dpd)); + setreg (s, 0x53, LOBYTE (dpd)); + + setreg (s, 0x46, HIBYTE (step_size)); + setreg (s, 0x47, LOBYTE (step_size)); + + ff_step_size = compute_fastfeed_step_size (s->hw_parms.crystal_frequency, s->mclk_div, s->hw_parms.motor_max_speed, s->hw_parms.motor_full_steps_per_inch, 0); /* 0x0190; */ + setreg (s, 0x48, HIBYTE (ff_step_size)); + setreg (s, 0x49, LOBYTE (ff_step_size)); + setreg (s, 0x4b, 0x15); + steps_to_reverse = 0x3f; + setreg (s, 0x50, steps_to_reverse); + setreg (s, 0x51, 0x15); /* accel profile */ + + /* this is to activate the motor */ + setbits (s, 0x45, (1 << 4)); + + lines_to_process = 8 * step_size * 4 / line_end; + if (lines_to_process < 1) + lines_to_process = 1; + +#ifdef DEBUG + fprintf (stderr, "lines to process = %d\n", lines_to_process); +#endif + + setreg (s, 0x58, 0); + + cache_write (s); + + calibrated = 0; + first_time = 1; + cal_line = malloc (cal_line_size + 1024); + average = malloc (sizeof (int) * line_length * 3); + memset (average, 0, sizeof (int) * line_length * 3); + { + int i; + for (i = 0; i < 12; i++) + { + memset (registro[i], 0, 5460 * 3); + } + } + + /* resets the lm9830 before start scanning */ + setreg (s, 0x07, 0x08); + setreg (s, 0x07, 0x03); + + usleep (100); + + do + { + + cal_line_ptr = cal_line; + + status = read_required_bytes (s, cal_line_size, cal_line_ptr); + if (status != SANE_STATUS_GOOD) + goto done; + { + int i, j; + + if (calibrated == 0) + for (j = 0, i = 0; i < (line_length * 3); i++, j += 2) + { + average[i] = (cal_line[j] * 256 + cal_line[j + 1]) >> 2; + registro[calibrated][i] = average[i]; + } + else + for (j = 0, i = 0; i < (line_length * 3); i++, j += 2) + { + int value; + value = (cal_line[j] * 256 + cal_line[j + 1]) >> 2; + average[i] += value; + average[i] /= 2; + registro[calibrated][i] = value; + } + } + calibrated++; + } + while (calibrated < lines_to_process); + lm9830_write_register (s->fd, 0x07, 0x00); + usleep (10 * 1000); + +#if 0 + { + int i; + int j = 0; + do + { + for (i = 3; (i + 6) < (line_length * 3); i += 3) + { + average[i] = + (2 * average[i - 3] + average[i] + 2 * average[i + 3]) / 5; + average[i + 1] = + (2 * average[i - 2] + average[i + 1] + 2 * average[i + 4]) / 5; + average[i + 2] = + (2 * average[i - 1] + average[i + 2] + 2 * average[i + 5]) / 5; + } + j++; + } + while (j < 3); + } +#endif + { + int i; + int max_red; + int min_red; + int max_green; + int min_green; + int max_blue; + int min_blue; + min_red = max_red = average[0]; + min_green = max_green = average[1]; + min_blue = max_blue = average[2]; + for (i = 3; i < (line_length * 3); i += 3) + { + int value; + + value = average[i]; + if (value > max_red) + max_red = value; + value = average[i + 1]; + if (value > max_green) + max_green = value; + value = average[i + 2]; + if (value > max_blue) + max_blue = value; + value = average[i]; + if (value < min_red) + min_red = value; + value = average[i + 1]; + if (value < min_green) + min_green = value; + value = average[i + 2]; + if (value < min_blue) + min_blue = value; + } +#ifdef DEBUG + fprintf (stderr, "max_red:%d max_green:%d max_blue:%d\n", + max_red, max_green, max_blue); + fprintf (stderr, "min_red:%d min_green:%d min_blue:%d\n", + min_red, min_green, min_blue); +#endif + + /* do fine calibration */ + { + int min_white_red; + int min_white_green; + int min_white_blue; + double ratio; + int range; + double aux; + int min_white_err; + int j; + + min_white_red = min_white_green = min_white_blue = 0x3ff; + for (i = 0; i < (line_length * 3); i += 3) + { + int value; + + value = average[i] - coarse->min_red; + if ((value > 0) && (value < min_white_red)) + min_white_red = value; + value = average[i + 1] - coarse->min_green; + if ((value > 0) && (value < min_white_green)) + min_white_green = value; + value = average[i + 2] - coarse->min_blue; + if ((value > 0) && (value < min_white_blue)) + min_white_blue = value; + } + + ratio = 0; + min_white_err = 0x3ff; + + aux = (double) s->hw_parms.target_value.red / min_white_red; + if (aux > ratio) + ratio = aux; + if (min_white_err > min_white_red) + min_white_err = min_white_red; + aux = (double) s->hw_parms.target_value.green / min_white_green; + if (aux > ratio) + ratio = aux; + if (min_white_err > min_white_green) + min_white_err = min_white_green; + aux = (double) s->hw_parms.target_value.blue / min_white_blue; + if (aux > ratio) + ratio = aux; + if (min_white_err > min_white_blue) + min_white_err = min_white_blue; + +#ifdef DEBUG + fprintf (stderr, "min_white_err = %d, ratio = %f\n", + min_white_err, ratio); +#endif + if (ratio <= 1.5) + range = 2048; + else if (ratio <= 2.0) + range = 1024; + else + range = 512; + + corr_red = malloc (sizeof (int) * line_length); + corr_green = malloc (sizeof (int) * line_length); + corr_blue = malloc (sizeof (int) * line_length); + + for (i = 0, j = 0; i < (line_length * 3); i += 3, j++) + { + corr_red[j] = compute_corr_code (average[i], + coarse->min_red, + range, + s->hw_parms.target_value.red); + corr_green[j] = + compute_corr_code (average[i + 1], coarse->min_green, + range, s->hw_parms.target_value.green); + corr_blue[j] = + compute_corr_code (average[i + 2], coarse->min_blue, + range, s->hw_parms.target_value.blue); + } +#ifdef DEBUG + { + FILE *kaka; + int i; + kaka = fopen ("corr.raw", "w"); + for (i = 0; i < line_length; i++) + { + fprintf (kaka, "%d %d %d %d %d %d ", + corr_red[i], corr_green[i], corr_blue[i], + average[3 * i], average[3 * i + 1], average[3 * i + 2]); + fprintf (kaka, "%d %d %d %d %d %d %d %d %d ", + registro[0][3 * i], registro[0][3 * i + 1], + registro[0][3 * i + 2], registro[1][3 * i], + registro[1][3 * i + 1], registro[1][3 * i + 2], + registro[2][3 * i], registro[2][3 * i + 1], + registro[2][3 * i + 2]); + fprintf (kaka, "%d %d %d %d %d %d %d %d %d\n", + registro[3][3 * i], registro[3][3 * i + 1], + registro[3][3 * i + 2], registro[4][3 * i], + registro[4][3 * i + 1], registro[4][3 * i + 2], + registro[5][3 * i], registro[5][3 * i + 1], + registro[5][3 * i + 2]); + } + fclose (kaka); + } +#endif + { + int max_black; + int use_six_eight_bits; + + max_black = max (coarse->min_red, coarse->min_green); + max_black = max (max_black, coarse->min_blue); + use_six_eight_bits = (max_black < 64); + + if (use_six_eight_bits) + { + setreg (s, 0x3e, (1 << 4) | (1 << 3) | (1024 / range)); + } + else + { + setreg (s, 0x3e, (1 << 4) | (1 << 3) | (1 << 2) | (1024 / range)); + } + memset (red_gain_offset, 0, sizeof (red_gain_offset)); + memset (green_gain_offset, 0, sizeof (green_gain_offset)); + memset (blue_gain_offset, 0, sizeof (blue_gain_offset)); + for (i = 0, j = (data_pixels_start - active_pixels_start) * 2; + i < line_length; i++, j += 2) + { + if (use_six_eight_bits) + { + red_gain_offset[j] = (coarse->min_red << 2) | + ((corr_red[i] >> 8) & 0x03); + red_gain_offset[j + 1] = corr_red[i] & 0xff; + green_gain_offset[j] = (coarse->min_green << 2) | + ((corr_green[i] >> 8) & 0x03); + green_gain_offset[j + 1] = corr_green[i] & 0xff; + blue_gain_offset[j] = (coarse->min_blue << 2) | + ((corr_blue[i] >> 8) & 0x03); + blue_gain_offset[j + 1] = corr_blue[i] & 0xff; + } + else + { + red_gain_offset[j] = coarse->min_red; + red_gain_offset[j + 1] = corr_red[j] >> 2; + green_gain_offset[j] = coarse->min_green; + green_gain_offset[j + 1] = corr_green[j] >> 2; + blue_gain_offset[j] = coarse->min_blue; + blue_gain_offset[j + 1] = corr_blue[j] >> 2; + } + } + write_default_offset_gain (s, red_gain_offset, 5460 * 2, 0); + write_default_offset_gain (s, green_gain_offset, 5460 * 2, 1); + write_default_offset_gain (s, blue_gain_offset, 5460 * 2, 2); + } + } + } + + status = SANE_STATUS_GOOD; + +done: + if (corr_red) + free (corr_red); + if (corr_green) + free (corr_green); + if (corr_blue) + free (corr_blue); + if (cal_line) + free (cal_line); + if (average) + free (average); + + return status; +} + +static void +ciclic_buffer_init_offset_correction (ciclic_buffer_t * cb, int vres) +{ + cb->blue_idx = 0; + switch (vres) + { + case 600: + cb->green_idx = 4; + cb->red_idx = 8; + cb->first_good_line = 8; + break; + case 400: + cb->green_idx = 3; + cb->red_idx = 6; + cb->first_good_line = 6; + break; + case 300: + cb->green_idx = 2; + cb->red_idx = 4; + cb->first_good_line = 4; + break; + case 200: + cb->blue_idx = 0; + cb->green_idx = 1; + cb->red_idx = 2; + cb->first_good_line = 4; + break; + case 150: + cb->green_idx = 1; + cb->red_idx = 2; + cb->first_good_line = 2; + break; + case 75: + cb->green_idx = 1; + cb->red_idx = 2; + cb->first_good_line = 2; + break; + default: + cb->green_idx = 0; + cb->red_idx = 0; + cb->first_good_line = 0; + break; + } + + cb->buffer_position = cb->buffer_ptrs[cb->first_good_line]; +} + +static SANE_Status +ciclic_buffer_init (ciclic_buffer_t * cb, SANE_Int bytes_per_line, + int vres, int status_bytes) +{ + cb->good_bytes = 0; + cb->num_lines = 12; + cb->size = bytes_per_line * cb->num_lines; + cb->can_consume = cb->size + cb->num_lines * status_bytes; + + cb->buffer = malloc (cb->size); + if (!cb->buffer) + return SANE_STATUS_NO_MEM; + + { + int i; + unsigned char *buffer; + unsigned char **ptrs; + + ptrs = cb->buffer_ptrs = (unsigned char **) + malloc (sizeof (unsigned char *) * cb->num_lines); + if (!cb->buffer_ptrs) + return SANE_STATUS_NO_MEM; + + buffer = cb->buffer; + for (i = 0; i < cb->num_lines; i++) + { + ptrs[i] = buffer; + buffer += bytes_per_line; + } + } + + cb->current_line = 0; + cb->pixel_position = 0; + ciclic_buffer_init_offset_correction (cb, vres); + + return SANE_STATUS_GOOD; +} + +static int +prepare_for_a_scan (HP4200_Scanner * s) +{ + /* local scanning params */ + int active_pixels_start; + int line_end; + int data_pixels_start; + int data_pixels_end; + int ff_step_size; + int dpd; + int step_size; + char steps_to_reverse; + char hdpi_div; + char line_rate_color; + int hdpi_code; + unsigned char pixel_packing; + unsigned char data_mode; + unsigned char AFE_operation; + int pause_limit; + int n = 0, m = 0; + + setreg (s, 0x07, 0x00); + usleep (10 * 1000); + + hdpi_code = compute_hdpi_code (s->user_parms.horizontal_resolution); + /* figure out which horizontal divider to use based on the + calculated horizontal dpi code */ + hdpi_div = hdpi_mapping[hdpi_code]; + + /* image_width is set to the correct number of pixels by calling + fxn. This might be the reason we can't do high res full width + scans though...not sure. */ + /*s->user_parms.image_width /= 4; */ + active_pixels_start = 0x40; + line_end = 0x2ee0; /* 2ee0 */ + s->mclk_div = 2; + data_pixels_start = 0x72 + s->runtime_parms.first_pixel * hdpi_div; + data_pixels_end = + (int) (data_pixels_start + s->user_parms.image_width * hdpi_div); + data_pixels_end = min (data_pixels_end, line_end - 20); + setreg (s, 0x1e, HIBYTE (active_pixels_start)); + setreg (s, 0x1f, LOBYTE (active_pixels_start)); + setreg (s, 0x20, HIBYTE (line_end)); + setreg (s, 0x21, LOBYTE (line_end)); + setreg (s, 0x22, HIBYTE (data_pixels_start)); + setreg (s, 0x23, LOBYTE (data_pixels_start)); + setreg (s, 0x24, HIBYTE (data_pixels_end)); + setreg (s, 0x25, LOBYTE (data_pixels_end)); + + AFE_operation = PIXEL_RATE_3_CHANNELS; + setreg (s, 0x26, + AFE_operation | + GRAY_CHANNEL_RED | TR_RED (0) | TR_GREEN (0) | TR_BLUE (0)); + + setreg (s, 0x08, (s->mclk_div - 1) * 2); + pixel_packing = 3; + data_mode = 0; + setreg (s, 0x09, hdpi_code | PIXEL_PACKING (pixel_packing) | + DATAMODE (data_mode)); + setreg (s, 0x0a, 0); /* reserved and strange register */ + + setreg (s, 0x5c, 0x00); + setreg (s, 0x5d, 0x00); + setreg (s, 0x5e, 0x00); + + if (s->user_parms.vertical_resolution == 1200) + { + /* 1 out of 2 */ + n = 1; + m = 2; + } + setreg (s, 0x44, (256 - n) & 0xff); + setreg (s, 0x5a, m); + s->runtime_parms.status_bytes = (m == 0) ? 1 : m; + if (data_mode == 1) + s->runtime_parms.status_bytes *= 2; + + s->runtime_parms.scanner_line_size = + compute_bytes_per_line (data_pixels_end - data_pixels_start, + hdpi_code, pixel_packing, data_mode, + AFE_operation, m); + pause_limit = compute_pause_limit (&(s->hw_parms), + s->runtime_parms.scanner_line_size); + +#ifdef DEBUG + fprintf (stderr, "scanner_line_size = %d\npause_limit = %d\n", + s->runtime_parms.scanner_line_size, pause_limit); +#endif + + setreg (s, 0x4e, pause_limit); /* max Kb to pause */ + setreg (s, 0x4f, 0x02); /* min Kb to resume */ + + line_rate_color = 1; + step_size = + (s->user_parms.vertical_resolution * line_end * line_rate_color) / + (4 * s->hw_parms.motor_full_steps_per_inch); + + if (s->val[OPT_BACKTRACK].b) + { + steps_to_reverse = 0x3f; + setreg (s, 0x50, steps_to_reverse); + setreg (s, 0x51, 0x15); /* accel profile */ + } + else + { + s->hw_parms.steps_to_reverse = 0; + setreg (s, 0x50, s->hw_parms.steps_to_reverse); + setreg (s, 0x51, 0); /* accel profile */ + s->hw_parms.fsteps_25_speed = 0; + s->hw_parms.fsteps_50_speed = 0; + } + + dpd = compute_dpd (s, step_size, line_end); /* 0x0ada; */ +#ifdef DEBUG + fprintf (stderr, "dpd = %d\n", dpd); +#endif + setreg (s, 0x52, HIBYTE (dpd)); + setreg (s, 0x53, LOBYTE (dpd)); + + setreg (s, 0x46, HIBYTE (step_size)); + setreg (s, 0x47, LOBYTE (step_size)); + + ff_step_size = compute_fastfeed_step_size (s->hw_parms.crystal_frequency, + s->mclk_div, + s->hw_parms.motor_max_speed, + s->hw_parms. + motor_full_steps_per_inch, 0); + setreg (s, 0x48, HIBYTE (ff_step_size)); + setreg (s, 0x49, LOBYTE (ff_step_size)); + setreg (s, 0x4b, 0x15); + /* this is to stay the motor running */ + setbits (s, 0x45, (1 << 4)); + + setreg (s, 0x4a, HIBYTE (47 + s->runtime_parms.steps_to_skip)); + setreg (s, 0x4b, LOBYTE (47 + s->runtime_parms.steps_to_skip)); + + setreg (s, 0x58, 0); + + ciclic_buffer_init (&(s->ciclic_buffer), + s->runtime_parms.image_line_size, + s->user_parms.vertical_resolution, + s->runtime_parms.status_bytes); + + s->runtime_parms.num_bytes_left_to_scan = + s->user_parms.lines_to_scan * s->runtime_parms.image_line_size; + +#ifdef DEBUG + fprintf (stderr, "bytes to scan = %ld\n", + s->runtime_parms.num_bytes_left_to_scan); +#endif + + cache_write (s); + +#ifdef DEBUG + lm9830_dump_registers (s->fd); +#endif + + lm9830_reset (s->fd); + + setreg (s, 0x07, 0x03); + usleep (100); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +end_scan (HP4200_Scanner * s) +{ + s->scanning = SANE_FALSE; + setreg (s, 0x07, 0x00); + lm9830_reset (s->fd); + setbits (s, 0x58, PAPER_SENSOR_2_STOP_SCAN); + cache_write (s); + setreg (s, 0x07, 0x02); + + /* Free some buffers */ + if (s->ciclic_buffer.buffer) + { + free (s->ciclic_buffer.buffer); + s->ciclic_buffer.buffer = NULL; + } + if (s->ciclic_buffer.buffer_ptrs) + { + free (s->ciclic_buffer.buffer_ptrs); + s->ciclic_buffer.buffer_ptrs = NULL; + } + if (s->scanner_buffer.buffer) + { + free (s->scanner_buffer.buffer); + s->scanner_buffer.buffer = NULL; + } + + return SANE_STATUS_GOOD; +} + +static int +hp4200_init_scanner (HP4200_Scanner * s) +{ + int ff_step_size; + int mclk_div; + + lm9830_ini_scanner (s->fd, NULL); + hp4200_init_registers (s); + scanner_buffer_init (&(s->scanner_buffer), s->hw_parms.SRAM_size); + setreg (s, 0x07, 0x08); + usleep (10 * 1000); + setreg (s, 0x07, 0x00); + usleep (10 * 1000); + mclk_div = 2; + + setreg (s, 0x08, (mclk_div - 1) * 2); + ff_step_size = + compute_fastfeed_step_size (s->hw_parms.crystal_frequency, + mclk_div, + s->hw_parms.motor_max_speed, + s->hw_parms.motor_full_steps_per_inch, 0); + setreg (s, 0x48, HIBYTE (ff_step_size)); + setreg (s, 0x49, LOBYTE (ff_step_size)); + setbits (s, 0x45, (1 << 4)); + cache_write (s); + return 0; +} + +static void +ciclic_buffer_copy (ciclic_buffer_t * cb, SANE_Byte * buf, + SANE_Int num_bytes, int image_line_size, int status_bytes) +{ + int biggest_upper_block_size; + int upper_block_size; + int lower_block_size; + int bytes_to_be_a_entire_line; + + /* copy the upper block */ + biggest_upper_block_size = cb->size - (cb->buffer_position - cb->buffer); + upper_block_size = min (biggest_upper_block_size, num_bytes); + memcpy (buf, cb->buffer_position, upper_block_size); + cb->good_bytes -= upper_block_size; + + bytes_to_be_a_entire_line = (cb->buffer_position - cb->buffer) % + image_line_size; + cb->can_consume += upper_block_size + + status_bytes * (((bytes_to_be_a_entire_line + upper_block_size) / + image_line_size) - 1); + + if (num_bytes < biggest_upper_block_size) + { + cb->buffer_position += num_bytes; + return; + } + + /* copy the lower block */ + lower_block_size = num_bytes - biggest_upper_block_size; + if (lower_block_size > 0) + { + memcpy (buf + biggest_upper_block_size, cb->buffer, lower_block_size); + cb->good_bytes -= lower_block_size; + cb->can_consume += lower_block_size + status_bytes * + (lower_block_size / image_line_size); + cb->buffer_position = cb->buffer + lower_block_size; + } + else + { + cb->buffer_position = cb->buffer; + } + assert (cb->good_bytes >= 0); + assert (lower_block_size >= 0); +} + +static void +ciclic_buffer_consume (ciclic_buffer_t * cb, + scanner_buffer_t * scanner_buffer, + int image_width, int status_bytes) +{ + int to_consume; + int to_consume_now; + int i; + int processed; + + to_consume = min (cb->can_consume, scanner_buffer->num_bytes); + + while (to_consume) + { + + if (cb->pixel_position == image_width) + { + if (scanner_buffer->num_bytes >= status_bytes) + { + /* forget status bytes */ + scanner_buffer->data_ptr += status_bytes; + scanner_buffer->num_bytes -= status_bytes; + cb->can_consume -= status_bytes; + to_consume -= status_bytes; + + cb->pixel_position = 0; /* back to the start pixel */ + + cb->red_idx = (cb->red_idx + 1) % cb->num_lines; + cb->green_idx = (cb->green_idx + 1) % cb->num_lines; + cb->blue_idx = (cb->blue_idx + 1) % cb->num_lines; + cb->current_line++; + } + else + break; + } + + to_consume_now = min ((image_width - cb->pixel_position) * 3, + to_consume); + + if (to_consume_now < 3) + break; + + for (i = cb->pixel_position * 3; to_consume_now >= 3; + i += 3, to_consume_now -= 3) + { + cb->buffer_ptrs[cb->red_idx][i] = scanner_buffer->data_ptr[0]; + cb->buffer_ptrs[cb->green_idx][i + 1] = scanner_buffer->data_ptr[1]; + cb->buffer_ptrs[cb->blue_idx][i + 2] = scanner_buffer->data_ptr[2]; + scanner_buffer->data_ptr += 3; + } + processed = i - (cb->pixel_position * 3); + cb->pixel_position = i / 3; + to_consume -= processed; + cb->can_consume -= processed; + scanner_buffer->num_bytes -= processed; + if (cb->current_line > cb->first_good_line) + cb->good_bytes += processed; + } +} + +SANE_Status +sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len) +{ + SANE_Status status; + int to_copy_now; + int bytes_to_copy_to_frontend; + HP4200_Scanner *s = h; + + static char me[] = "sane_read"; + DBG (DBG_proc, "%s\n", me); + + if (!(s->scanning)) + { + /* OOPS, not scanning */ + return SANE_STATUS_CANCELLED; + } + + if (!buf || !len) + return SANE_STATUS_INVAL; + + *len = 0; + + if (s->runtime_parms.num_bytes_left_to_scan == 0) + { + end_scan (s); + return SANE_STATUS_EOF; + } + + bytes_to_copy_to_frontend = min (s->runtime_parms.num_bytes_left_to_scan, + maxlen); + + /* first copy available data from the ciclic buffer */ + to_copy_now = min (s->ciclic_buffer.good_bytes, bytes_to_copy_to_frontend); + + if (to_copy_now > 0) + { + ciclic_buffer_copy (&(s->ciclic_buffer), buf, to_copy_now, + s->runtime_parms.image_line_size, + s->runtime_parms.status_bytes); + buf += to_copy_now; + bytes_to_copy_to_frontend -= to_copy_now; + *len += to_copy_now; + } + + /* if not enough bytes, get data from the scanner */ + while (bytes_to_copy_to_frontend) + { + if (s->scanner_buffer.num_bytes < 3) + { /* cicl buf consumes modulo 3 + bytes at least now for rgb + color 8 bpp fixme: but this + is ugly and not generic + */ + status = scanner_buffer_read (s); + + if (status == SANE_STATUS_CANCELLED) + { + end_scan (s); + s->aborted_by_user = SANE_FALSE; + return status; + } + if (status != SANE_STATUS_GOOD) + return status; + } + + while ((s->scanner_buffer.num_bytes > 3) && bytes_to_copy_to_frontend) + { + ciclic_buffer_consume (&(s->ciclic_buffer), &(s->scanner_buffer), + s->user_parms.image_width, + s->runtime_parms.status_bytes); + to_copy_now = min (s->ciclic_buffer.good_bytes, + bytes_to_copy_to_frontend); + + if (to_copy_now > 0) + { + ciclic_buffer_copy (&(s->ciclic_buffer), buf, to_copy_now, + s->runtime_parms.image_line_size, + s->runtime_parms.status_bytes); + buf += to_copy_now; + bytes_to_copy_to_frontend -= to_copy_now; + *len += to_copy_now; + } + } + } + + s->runtime_parms.num_bytes_left_to_scan -= *len; + + if (s->runtime_parms.num_bytes_left_to_scan < 0) + *len += s->runtime_parms.num_bytes_left_to_scan; + + return SANE_STATUS_GOOD; +} + +static HP4200_Device * +find_device (SANE_String_Const name) +{ + static char me[] = "find_device"; + HP4200_Device *dev; + + DBG (DBG_proc, "%s\n", me); + + for (dev = first_device; dev; dev = dev->next) + { + if (strcmp (dev->dev.name, name) == 0) + { + return dev; + } + } + return NULL; +} + +static SANE_Status +add_device (SANE_String_Const name, HP4200_Device ** argpd) +{ + int fd; + HP4200_Device *pd; + static const char me[] = "add_device"; + SANE_Status status; + + DBG (DBG_proc, "%s(%s)\n", me, name); + + /* Avoid adding the same device more than once */ + if ((pd = find_device (name))) + { + if (argpd) + *argpd = pd; + return SANE_STATUS_GOOD; + } + + /* open the device file, but read only or read/write to perform + ioctl's ? */ + if ((status = sanei_usb_open (name, &fd)) != SANE_STATUS_GOOD) + { + DBG (DBG_error, "%s: open(%s) failed: %s\n", me, name, + sane_strstatus (status)); + return SANE_STATUS_INVAL; + } + + /* put here some code to probe that the device attached to the + device file is a supported scanner. Maybe some ioctl */ + sanei_usb_close (fd); + + pd = (HP4200_Device *) calloc (1, sizeof (HP4200_Device)); + if (!pd) + { + DBG (DBG_error, "%s: out of memory allocating device.\n", me); + return SANE_STATUS_NO_MEM; + } + + pd->dev.name = strdup (name); + pd->dev.vendor = "Hewlett-Packard"; + pd->dev.model = "HP-4200"; + pd->dev.type = "flatbed scanner"; + + if (!pd->dev.name || !pd->dev.vendor || !pd->dev.model || !pd->dev.type) + { + DBG (DBG_error, + "%s: out of memory allocating device descriptor strings.\n", me); + free (pd); + return SANE_STATUS_NO_MEM; + } + + pd->handle = NULL; + pd->next = first_device; + first_device = pd; + n_devices++; + if (argpd) + *argpd = pd; + + return SANE_STATUS_GOOD; +} + +static SANE_Status +attach (SANE_String_Const name) +{ + static char me[] = "attach"; + DBG (DBG_proc, "%s\n", me); + return add_device (name, NULL); +} + +SANE_Status +sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) +{ + static const char me[] = "sane_hp4200_init"; + char dev_name[PATH_MAX]; + FILE *fp; + + authorize = authorize; /* keep gcc quiet */ + + DBG_INIT (); + + DBG (DBG_proc, "%s\n", me); + DBG (DBG_error, "SANE hp4200 backend version %d.%d build %d from %s\n", + SANE_CURRENT_MAJOR, V_MINOR, BUILD, PACKAGE_STRING); + /* put some version_code checks here */ + + if (NULL != version_code) + { + *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, 0); + } + + sanei_usb_init (); + sanei_pv8630_init (); + + fp = sanei_config_open (HP4200_CONFIG_FILE); + if (!fp) + { + DBG (DBG_error, "%s: configuration file not found!\n", me); + + return SANE_STATUS_INVAL; + } + else + { + while (sanei_config_read (dev_name, sizeof (dev_name), fp)) + { + if (dev_name[0] == '#') /* ignore line comments */ + continue; + + if (strlen (dev_name) == 0) + continue; /* ignore empty lines */ + + DBG (DBG_info, "%s: looking for devices matching %s\n", + me, dev_name); + + sanei_usb_attach_matching_devices (dev_name, attach); + } + + fclose (fp); + } + + return SANE_STATUS_GOOD; +} + +void +sane_exit (void) +{ + HP4200_Device *device, *next; + + DBG (DBG_proc, "sane_hp4200_exit\n"); + + for (device = first_device; device; device = next) + { + next = device->next; + if (device->handle) + { + sane_close (device->handle); + } + if (device->dev.name) + { + free (device->dev.name); + } + free (device); + } + first_device = NULL; + + if (devlist) + { + free (devlist); + devlist = NULL; + } + + n_devices = 0; + + DBG (DBG_proc, "sane_exit: exit\n"); +} + +SANE_Status +sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) +{ + int i; + HP4200_Device *pdev; + + DBG (DBG_proc, "sane_get_devices (%p, %d)\n", (void *) device_list, + local_only); + + /* Waste the last list returned from this function */ + if (devlist) + free (devlist); + + devlist = (const SANE_Device **) + malloc ((n_devices + 1) * sizeof (SANE_Device *)); + + if (!devlist) + { + DBG (DBG_error, "sane_get_devices: out of memory\n"); + return SANE_STATUS_NO_MEM; + } + + for (i = 0, pdev = first_device; pdev; i++, pdev = pdev->next) + { + devlist[i] = &(pdev->dev); + } + devlist[i] = NULL; + + *device_list = devlist; + + DBG (DBG_proc, "sane_get_devices: exit\n"); + + return SANE_STATUS_GOOD; +} + +static void +init_options (HP4200_Scanner * s) +{ + s->opt[OPT_NUM_OPTS].name = ""; + s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; + s->opt[OPT_NUM_OPTS].unit = SANE_UNIT_NONE; + s->opt[OPT_NUM_OPTS].size = sizeof (SANE_Word); + s->opt[OPT_NUM_OPTS].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_NUM_OPTS].w = NUM_OPTIONS; + + s->opt[OPT_RES].name = SANE_NAME_SCAN_RESOLUTION; + s->opt[OPT_RES].title = SANE_TITLE_SCAN_RESOLUTION; + s->opt[OPT_RES].desc = SANE_DESC_SCAN_RESOLUTION; + s->opt[OPT_RES].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_RES].type = SANE_TYPE_INT; + s->opt[OPT_RES].size = sizeof (SANE_Word); + s->opt[OPT_RES].unit = SANE_UNIT_DPI; + s->opt[OPT_RES].constraint_type = SANE_CONSTRAINT_WORD_LIST; + s->opt[OPT_RES].constraint.word_list = dpi_list; + s->val[OPT_RES].w = 150; + + s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; + s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; + s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; + s->opt[OPT_TL_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_TL_X].type = SANE_TYPE_FIXED; + s->opt[OPT_TL_X].size = sizeof (SANE_Fixed); + s->opt[OPT_TL_X].unit = SANE_UNIT_MM; + s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_TL_X].constraint.range = &x_range; + s->val[OPT_TL_X].w = x_range.min; + + s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; + s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; + s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; + s->opt[OPT_TL_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED; + s->opt[OPT_TL_Y].size = sizeof (SANE_Fixed); + s->opt[OPT_TL_Y].unit = SANE_UNIT_MM; + s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_TL_Y].constraint.range = &y_range; + s->val[OPT_TL_Y].w = y_range.min; + + s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X; + s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; + s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; + s->opt[OPT_BR_X].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_BR_X].type = SANE_TYPE_FIXED; + s->opt[OPT_BR_X].size = sizeof (SANE_Fixed); + s->opt[OPT_BR_X].unit = SANE_UNIT_MM; + s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BR_X].constraint.range = &x_range; + s->val[OPT_BR_X].w = x_range.max; + + s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; + s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; + s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; + s->opt[OPT_BR_Y].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED; + s->opt[OPT_BR_Y].size = sizeof (SANE_Fixed); + s->opt[OPT_BR_Y].unit = SANE_UNIT_MM; + s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BR_Y].constraint.range = &y_range; + s->val[OPT_BR_Y].w = y_range.max; + + s->opt[OPT_BACKTRACK].name = SANE_NAME_BACKTRACK; + s->opt[OPT_BACKTRACK].title = SANE_TITLE_BACKTRACK; + s->opt[OPT_BACKTRACK].desc = SANE_DESC_BACKTRACK; + s->opt[OPT_BACKTRACK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_BACKTRACK].type = SANE_TYPE_BOOL; + s->opt[OPT_BACKTRACK].size = sizeof (SANE_Bool); + s->opt[OPT_BACKTRACK].unit = SANE_UNIT_NONE; + s->opt[OPT_BACKTRACK].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_BACKTRACK].b = SANE_TRUE; + + s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].cap = + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_R].size = 1024 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_R].wa = s->user_parms.gamma[0]; + + s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].cap = + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_G].size = 1024 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_G].wa = s->user_parms.gamma[1]; + + s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].cap = + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_B].size = 1024 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_B].wa = s->user_parms.gamma[2]; + + { + int i; + double gamma = 2.0; + for (i = 0; i < 1024; i++) + { + s->user_parms.gamma[0][i] = + 255 * pow (((double) i + 1) / 1024, 1.0 / gamma); + s->user_parms.gamma[1][i] = s->user_parms.gamma[0][i]; + s->user_parms.gamma[2][i] = s->user_parms.gamma[0][i]; +#ifdef DEBUG + printf ("%d %d\n", i, s->user_parms.gamma[0][i]); +#endif + } + } + + /* preview */ + s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; + s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; + s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; + s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; + s->opt[OPT_PREVIEW].size = sizeof (SANE_Word); + s->opt[OPT_PREVIEW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT; + s->val[OPT_PREVIEW].w = SANE_FALSE; +} + +SANE_Status +sane_open (SANE_String_Const name, SANE_Handle * h) +{ + static const char me[] = "sane_hp4200_open"; + SANE_Status status; + HP4200_Device *dev; + HP4200_Scanner *s; + + DBG (DBG_proc, "%s (%s, %p)\n", me, name, (void *) h); + + if (name && name[0]) + { + dev = find_device (name); + if (!dev) + { + status = add_device (name, &dev); + if (status != SANE_STATUS_GOOD) + return status; + } + } + else + { + dev = first_device; + } + if (!dev) + return SANE_STATUS_INVAL; + + if (!h) + return SANE_STATUS_INVAL; + + s = *h = (HP4200_Scanner *) calloc (1, sizeof (HP4200_Scanner)); + if (!s) + { + DBG (DBG_error, "%s: out of memory creating scanner structure.\n", me); + return SANE_STATUS_NO_MEM; + } + + dev->handle = s; + s->aborted_by_user = SANE_FALSE; + s->ciclic_buffer.buffer = NULL; + s->scanner_buffer.buffer = NULL; + s->dev = dev; + s->user_parms.image_width = 0; + s->user_parms.lines_to_scan = 0; + s->user_parms.vertical_resolution = 0; + s->scanning = SANE_FALSE; + s->fd = -1; + + init_options (s); + + if ((sanei_usb_open (dev->dev.name, &s->fd) != SANE_STATUS_GOOD)) + { + DBG (DBG_error, "%s: Can't open %s.\n", me, dev->dev.name); + return SANE_STATUS_IO_ERROR; /* fixme: return busy when file is + being accessed already */ + } + + return SANE_STATUS_GOOD; +} + +void +sane_close (SANE_Handle h) +{ + HP4200_Scanner *s = (HP4200_Scanner *) h; + DBG (DBG_proc, "sane_hp4200_close (%p)\n", (void *) h); + + if (s) + { + s->dev->handle = NULL; + if (s->fd != -1) + { + sanei_usb_close (s->fd); + } + free (s); + } +} + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle h, SANE_Int n) +{ + static char me[] = "sane_get_option_descriptor"; + HP4200_Scanner *s = (HP4200_Scanner *) h; + + DBG (DBG_proc, "%s\n", me); + + if ((n < 0) || (n >= NUM_OPTIONS)) + return NULL; + + return s->opt + n; +} + +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int option, + SANE_Action action, void *val, SANE_Int * info) +{ + HP4200_Scanner *s = (HP4200_Scanner *) handle; + SANE_Status status; + SANE_Int myinfo = 0; + SANE_Word cap; + + DBG (DBG_proc, "sane_control_option\n"); + + if (info) + *info = 0; + + if (s->scanning) + { + return SANE_STATUS_DEVICE_BUSY; + } + + if (option < 0 || option >= NUM_OPTIONS) + { + return SANE_STATUS_INVAL; + } + + cap = s->opt[option].cap; + if (!SANE_OPTION_IS_ACTIVE (cap)) + { + return SANE_STATUS_INVAL; + } + + if (action == SANE_ACTION_GET_VALUE) + { + + switch (option) + { + case OPT_NUM_OPTS: + case OPT_RES: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + case OPT_PREVIEW: + *(SANE_Word *) val = s->val[option].w; + break; + + case OPT_BACKTRACK: + *(SANE_Bool *) val = s->val[option].b; + break; + + case OPT_GAMMA_VECTOR_R: + case OPT_GAMMA_VECTOR_G: + case OPT_GAMMA_VECTOR_B: + memcpy (val, s->val[option].wa, s->opt[option].size); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + else if (action == SANE_ACTION_SET_VALUE) + { + + if (!SANE_OPTION_IS_SETTABLE (cap)) + { + DBG (DBG_error, "could not set option, not settable\n"); + return SANE_STATUS_INVAL; + } + + status = sanei_constrain_value (s->opt + option, val, &myinfo); + if (status != SANE_STATUS_GOOD) + return status; + + switch (option) + { + + /* Numeric side-effect free options */ + case OPT_PREVIEW: + s->val[option].w = *(SANE_Word *) val; + return SANE_STATUS_GOOD; + + /* Numeric side-effect options */ + case OPT_RES: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + myinfo |= SANE_INFO_RELOAD_PARAMS; + s->val[option].w = *(SANE_Word *) val; + break; + + case OPT_BACKTRACK: + s->val[option].b = *(SANE_Bool *) val; + break; + + case OPT_GAMMA_VECTOR_R: + case OPT_GAMMA_VECTOR_G: + case OPT_GAMMA_VECTOR_B: + memcpy (s->val[option].wa, val, s->opt[option].size); + break; + default: + return SANE_STATUS_UNSUPPORTED; + } + } + else + { + return SANE_STATUS_UNSUPPORTED; + } + + if (info) + *info = myinfo; + + return SANE_STATUS_GOOD; +} + +static void +compute_parameters (HP4200_Scanner * s) +{ + int resolution; + int opt_tl_x; + int opt_br_x; + int opt_tl_y; + int opt_br_y; + + if (s->val[OPT_PREVIEW].w == SANE_TRUE) + { + resolution = 50; + opt_tl_x = SANE_UNFIX (x_range.min); + opt_tl_y = SANE_UNFIX (y_range.min); + opt_br_x = SANE_UNFIX (x_range.max); + opt_br_y = SANE_UNFIX (y_range.max); + } + else + { + resolution = s->val[OPT_RES].w; + opt_tl_x = SANE_UNFIX (s->val[OPT_TL_X].w); + opt_tl_y = SANE_UNFIX (s->val[OPT_TL_Y].w); + opt_br_x = SANE_UNFIX (s->val[OPT_BR_X].w); + opt_br_y = SANE_UNFIX (s->val[OPT_BR_Y].w); + } + + s->user_parms.horizontal_resolution = resolution; + s->user_parms.vertical_resolution = resolution; + + s->runtime_parms.steps_to_skip = floor (300.0 / MM_PER_INCH * opt_tl_y); + s->user_parms.lines_to_scan = + floor ((opt_br_y - opt_tl_y) / MM_PER_INCH * resolution); + s->user_parms.image_width = + floor ((opt_br_x - opt_tl_x) / MM_PER_INCH * resolution); + s->runtime_parms.first_pixel = floor (opt_tl_x / MM_PER_INCH * resolution); + + /* fixme: add support for more depth's and bpp's. */ + s->runtime_parms.image_line_size = s->user_parms.image_width * 3; +} + +SANE_Status +sane_get_parameters (SANE_Handle h, SANE_Parameters * p) +{ + static char me[] = "sane_get_parameters"; + HP4200_Scanner *s = (HP4200_Scanner *) h; + + DBG (DBG_proc, "%s\n", me); + if (!p) + return SANE_STATUS_INVAL; + + p->format = SANE_FRAME_RGB; + p->last_frame = SANE_TRUE; + p->depth = 8; + if (!s->scanning) + { + compute_parameters (s); + } + + p->lines = s->user_parms.lines_to_scan; + p->pixels_per_line = s->user_parms.image_width; + p->bytes_per_line = s->runtime_parms.image_line_size; + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_start (SANE_Handle h) +{ + HP4200_Scanner *s = (HP4200_Scanner *) h; + struct coarse_t coarse; + + static char me[] = "sane_start"; + DBG (DBG_proc, "%s\n", me); + + s->scanning = SANE_TRUE; + s->aborted_by_user = SANE_FALSE; + s->user_parms.color = SANE_TRUE; + + compute_parameters (s); + + hp4200_init_scanner (s); + hp4200_goto_home (s); + hp4200_wait_homed (s); + /* restore default register values here... */ + write_gamma (s); + hp4200_init_registers (s); + lm9830_ini_scanner (s->fd, NULL); + /* um... do not call cache_write() here, don't know why :( */ + do_coarse_calibration (s, &coarse); + do_fine_calibration (s, &coarse); + prepare_for_a_scan (s); + + return SANE_STATUS_GOOD; +} + +void +sane_cancel (SANE_Handle h) +{ + static char me[] = "sane_cancel"; + HP4200_Scanner *s = (HP4200_Scanner *) h; + DBG (DBG_proc, "%s\n", me); + + s->aborted_by_user = SANE_TRUE; + + end_scan (s); +} + +SANE_Status +sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) +{ + HP4200_Scanner *dev = handle; + SANE_Status status; + + non_blocking = non_blocking; /* silence gcc */ + + if (dev->scanning == SANE_FALSE) + { + return SANE_STATUS_INVAL; + } + + if (non_blocking == SANE_FALSE) + { + status = SANE_STATUS_GOOD; + } + else + { + status = SANE_STATUS_UNSUPPORTED; + } + + DBG (DBG_proc, "sane_set_io_mode: exit\n"); + + return status; +} + +SANE_Status +sane_get_select_fd (SANE_Handle h, SANE_Int * fd) +{ + static char me[] = "sane_get_select_fd"; + + h = h; /* keep gcc quiet */ + fd = fd; /* keep gcc quiet */ + + DBG (DBG_proc, "%s\n", me); + return SANE_STATUS_UNSUPPORTED; +} |