/* misc.c - Various functions that do not have a proper file (yet)
 * 
 * Author: Anderson Lizardo <lizardo@users.sourceforge.net>
 * 
 * Copyright (C) 2004, 2005 Anderson Lizardo
 *
 * 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
 */

#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

#include "geniusvp2-adc.h"
#include "geniusvp2-asic.h"
#include "geniusvp2-image.h"
#include "geniusvp2-misc.h"

/* Default values from e1.ini */
const int max_exposure_time = 13800;
const int min_exposure_time = 2200;
const int motor_change = 3000;
const float left_leading = 0.22;
const int back_step = 20;

const unsigned char ccd_line_offset = 4;

static void
init_timer (struct timeval *timer, unsigned long sec)
{
    gettimeofday (timer, NULL);
    timer->tv_sec += sec;
}

static int
check_timer (struct timeval *timer)
{
    struct timeval current_time;

    gettimeofday (&current_time, NULL);

    if (timercmp (&current_time, timer, >))
        return TIMEOUT;
    else
        return NO_TIMEOUT;
}

/*
 * Status (reg. 12) polling routine.
 * NOTE: Always start with waiting!
 */
void
sane_geniusvp2_poll_12 (unsigned char val, unsigned char mask)
{
    do
    {
        usleep (5000);
        sane_geniusvp2_reg_read (12, &reg12.w);
    }
    while ((reg12.w & mask) != val);
}

ssize_t
sane_geniusvp2_wait_fifo (size_t count)
{
    size_t fifo_count = 0;
    struct timeval timer;

    /* set timeout to 10 sec. */
    init_timer (&timer, 10);
    do
    {
        usleep (5000);
        sane_geniusvp2_reg_read (18, &reg18.w);
        sane_geniusvp2_reg_read (19, &reg19.w);
        sane_geniusvp2_reg_read (12, &reg12.w);
        /*fifo_count = (reg18.w << 8) | reg19.w; */
        fifo_count = (reg18.w * 2048 + reg19.w * 8 + 8) * 3;
        DBG (1, "sane_geniusvp2_wait_fifo: FIFO = %lu, FinishFlag = %d\n",
             (unsigned long) fifo_count, reg12.r.FinishFlag);
        if (check_timer (&timer))
        {
            DBG (1, "sane_geniusvp2_wait_fifo: TIMEOUT\n");
            return -1;
        }
    }
    while (!((fifo_count > 2 * count) || reg12.r.FinishFlag));

    if (reg12.r.FinishFlag)
        DBG (1, "sane_geniusvp2_wait_fifo: FinishFlag is set\n");

    return count;
}

#if 0
void
get_fifo_count (size_t * count)
{
    do
    {
        sane_geniusvp2_reg_read (18, &reg18.w);
        sane_geniusvp2_reg_read (19, &reg19.w);
        sane_geniusvp2_reg_read (12, &reg12.w);
        *count = (reg18.w << 8) | reg19.w;
    }
    while (!(*count /* || reg12.r.FinishFlag */ ));

    DBG (1, "get_fifo_count: FIFO has %lu bytes\n", (unsigned long) *count);

    if (reg12.r.FinishFlag)
    {
        DBG (1, "get_fifo_count: FinishFlag is set\n");
        *count = 0;
    }
}
#endif

void
sane_geniusvp2_set_operation_mode (operation_mode mode)
{
    sane_geniusvp2_reg_read (6, &reg6.w);
    if (mode == mScanMotorOff)
        reg6.r.Operation = mScan;
    else
        reg6.r.Operation = mode;
    if (mode == mStop || mode == mScanMotorOff)
        reg6.r.MotorPower = 0;
    else
        reg6.r.MotorPower = 1;
    sane_geniusvp2_reg_write (6, reg6.w);
}

void
sane_geniusvp2_reset_fifo (void)
{
    sane_geniusvp2_set_operation_mode (mStop);
    sane_geniusvp2_reg_read (3, &reg3.w);
    reg3.r.FiFoReset = 0;
    sane_geniusvp2_reg_write (3, reg3.w);
    reg3.r.FiFoReset = 1;
    sane_geniusvp2_reg_write (3, reg3.w);

    sane_geniusvp2_set_operation_mode (mStop);
    sane_geniusvp2_reg_read (3, &reg3.w);
    reg3.r.FiFoReset = 0;
    sane_geniusvp2_reg_write (3, reg3.w);
    reg3.r.FiFoReset = 1;
    sane_geniusvp2_reg_write (3, reg3.w);
}

void
sane_geniusvp2_set_memory_addr (int addr)
{
    reg14.w = (addr & 0x1f0000) >> 16;
    reg15.w = (addr & 0x00ff00) >> 8;
    reg16.w = (addr & 0x0000ff);

    sane_geniusvp2_reg_write (14, reg14.w);
    sane_geniusvp2_reg_write (15, reg15.w);
    sane_geniusvp2_reg_write (16, reg16.w);
}

static void
sane_geniusvp2_set_line_start (int start)
{
    reg20.w = (start & 0x3f00) >> 8;
    reg21.w = (start & 0x00ff);

    sane_geniusvp2_reg_write (20, reg20.w);
    sane_geniusvp2_reg_write (21, reg21.w);
}

static void
sane_geniusvp2_set_line_width (int width)
{
    reg22.w = (width & 0x3f00) >> 8;
    reg23.w = (width & 0x00ff);

    sane_geniusvp2_reg_write (22, reg22.w);
    sane_geniusvp2_reg_write (23, reg23.w);
}

/*
 * Set the distance of carriage's next movement.
 */
static void
sane_geniusvp2_set_distance (int distance)
{
    reg7.w = (distance & 0xff00) >> 8;
    reg8.w = (distance & 0x00ff);

    sane_geniusvp2_reg_write (7, reg7.w);
    sane_geniusvp2_reg_write (8, reg8.w);
}

/*
 * Set for how long the carriage stays on each step.
 */
static void
sane_geniusvp2_set_step_time (int time)
{
    reg10.w = (time & 0xff00) >> 8;
    reg11.w = (time & 0x00ff);

    sane_geniusvp2_reg_write (10, reg10.w);
    sane_geniusvp2_reg_write (11, reg11.w);
}

static void
sane_geniusvp2_set_scan_speed (int speed)
{
    speed &= 0x07;
    sane_geniusvp2_reg_read (3, &reg3.w);
    reg3.r.ScanSpeed = speed;
    sane_geniusvp2_reg_write (3, reg3.w);
}

/*
 * Move carriage forward.
 */
void
sane_geniusvp2_move_carriage_forward (int distance)
{
    int StepTime;
    unsigned char ScanSpeed;

    sane_geniusvp2_set_operation_mode (mStop);
    ScanSpeed = 7;
    sane_geniusvp2_set_scan_speed (ScanSpeed);

    StepTime = max_exposure_time / (ScanSpeed + 1);
    if (StepTime < min_exposure_time)
        StepTime = min_exposure_time;
    sane_geniusvp2_set_step_time (StepTime);

    sane_geniusvp2_set_distance (distance);

    sane_geniusvp2_reg_read (6, &reg6.w);
    reg6.r.MotorPower = 1;
    reg6.r.HalfFull = 1;
    sane_geniusvp2_reg_write (6, reg6.w);
    sane_geniusvp2_set_operation_mode (mForward);

    sane_geniusvp2_poll_12 (0x08, 0x08);
}

static void
sane_geniusvp2_set_line_offset (int offset)
{
    sane_geniusvp2_reg_read (6, &reg6.w);
    sane_geniusvp2_reg_read (4, &reg4.w);

    if (offset < 0)
        reg4.r.NoLineOffset = 0;
    else
        reg4.r.NoLineOffset = 1;
    reg6.r.LineOffset = offset & 0x07;

    sane_geniusvp2_reg_write (6, reg6.w);
    sane_geniusvp2_reg_write (4, reg4.w);
}

/*
 * Set scanning resolution.
 */
static void
sane_geniusvp2_set_resolution (unsigned int res)
{
    sane_geniusvp2_reg_read (3, &reg3.w);
    sane_geniusvp2_reg_read (27, &reg27.w);
    sane_geniusvp2_reg_read (28, &reg28.w);
    sane_geniusvp2_reg_read (29, &reg29.w);

    reg27.r.XRes = (res & 0x0700) >> 8;
    reg28.r.XRes = (res & 0x00ff);
    reg27.r.YRes = ((res * (reg3.r.ScanSpeed + 1)) & 0x0700) >> 8;
    reg29.r.YRes = ((res * (reg3.r.ScanSpeed + 1)) & 0x00ff);

    sane_geniusvp2_reg_write (27, reg27.w);
    sane_geniusvp2_reg_write (28, reg28.w);
    sane_geniusvp2_reg_write (29, reg29.w);
}

/*
 * Return carriage to its home position.
 */
void
sane_geniusvp2_go_home (void)
{
    int ScanSpeed, StepTime;

    sane_geniusvp2_reg_read (12, &reg12.w);
    if (reg12.r.HomeSensor)
        return;

    sane_geniusvp2_set_operation_mode (mStop);
    ScanSpeed = 7;
    sane_geniusvp2_set_scan_speed (ScanSpeed);

    StepTime = max_exposure_time / (ScanSpeed + 1);
    if (StepTime < min_exposure_time)
        StepTime = min_exposure_time;
    sane_geniusvp2_set_step_time (StepTime);

    sane_geniusvp2_reg_read (6, &reg6.w);
    reg6.r.MotorPower = 1;
    reg6.r.HalfFull = 1;
    sane_geniusvp2_reg_write (6, reg6.w);
    sane_geniusvp2_set_operation_mode (mAutoHome);
}

SANE_Bool
sane_geniusvp2_is_lamp_on (void)
{
    sane_geniusvp2_reg_read (12, &reg12.w);
    return (reg12.r.Lamp ? SANE_TRUE : SANE_FALSE);
}

SANE_Status
sane_geniusvp2_lamp_on (void)
{
    if (sane_geniusvp2_reg_read (12, &reg12.w))
        return SANE_STATUS_IO_ERROR;
    reg12.r.Lamp = 1;
    if (sane_geniusvp2_reg_write (12, reg12.w))
        return SANE_STATUS_IO_ERROR;

    return SANE_STATUS_GOOD;
}

SANE_Status
sane_geniusvp2_lamp_off (void)
{
    if (sane_geniusvp2_reg_read (12, &reg12.w))
        return SANE_STATUS_IO_ERROR;
    reg12.r.Lamp = 0;
    if (sane_geniusvp2_reg_write (12, reg12.w))
        return SANE_STATUS_IO_ERROR;

    return SANE_STATUS_GOOD;
}

int
sane_geniusvp2_read_scan_data (SANE_Byte * data, size_t size)
{
    return sane_geniusvp2_reg_read_buf (17, (unsigned char *) data, size);
}

void
sane_geniusvp2_set_scan_area (ScanArea area)
{
    sane_geniusvp2_set_line_start (area.left);
    sane_geniusvp2_set_line_width (area.width);
    sane_geniusvp2_set_distance (area.height);
}

void
sane_geniusvp2_set_scan_params (int res)
{
    int ScanSpeed, LineOffset, StepTime;

    /* FIXME: This value is not fixed; should be calculated */
    sane_geniusvp2_reg_write (26, 0x09);

    ScanSpeed = (OPT_RESOLUTION / res) - 1;
    if (ScanSpeed > 7)
        ScanSpeed = 7;
    sane_geniusvp2_set_scan_speed (ScanSpeed);

    sane_geniusvp2_set_resolution (res);

    LineOffset = (ccd_line_offset / (ScanSpeed + 1)) - 1;
    sane_geniusvp2_set_line_offset (LineOffset);

    StepTime = max_exposure_time / (ScanSpeed + 1);
    if (StepTime < min_exposure_time)
        StepTime = min_exposure_time;
    sane_geniusvp2_set_step_time (StepTime * 2);

    sane_geniusvp2_reg_read (6, &reg6.w);
    reg6.r.HalfFull = 0;
    sane_geniusvp2_reg_write (6, reg6.w);

    /* Set motor back steps */
    sane_geniusvp2_reg_write (9, back_step);

    if (StepTime >= motor_change)
        sane_geniusvp2_reg_write (25, 0x00);
    else
        sane_geniusvp2_reg_write (25, 0x0f);
}

SANE_Status
sane_geniusvp2_prepare_scan (ScanArea area, int res)
{
    int i, j;
    unsigned char *memdata;

    sane_geniusvp2_go_home ();
    sane_geniusvp2_poll_12 (0x04, 0x04);

    sane_geniusvp2_reg_write (0x06, 0x80);
    sane_geniusvp2_init_adc ();
    sane_geniusvp2_reg_write (0x06, 0x80);

    memdata = calloc (20 * 1024, sizeof (unsigned char));
    if (!memdata)
        return SANE_STATUS_NO_MEM;

    /* Reset gain table */
    sane_geniusvp2_set_memory_addr (0x006000);
    sane_geniusvp2_reg_write_buf (17, memdata, 20 * 1024);

    /* Reset dark table */
    sane_geniusvp2_set_memory_addr (0x003000);
    sane_geniusvp2_reg_write_buf (17, memdata, 12 * 1024);

    /* Download linear LUT to scanner */
    sane_geniusvp2_set_memory_addr (0x000000);
    for (i = 0; i <= 255; i++)
    {
        for (j = 0; j < 16; j++)
        {
            memdata[0 * 4096 + i * 16 + j] = i;
            memdata[1 * 4096 + i * 16 + j] = i;
            memdata[2 * 4096 + i * 16 + j] = i;
        }
    }
    sane_geniusvp2_reg_write_buf (17, memdata, 12 * 1024);

    free (memdata);

    /* Disabled for now as it's broken */
    /*calibrate (image); */

    sane_geniusvp2_go_home ();
    sane_geniusvp2_poll_12 (0x04, 0x04);

    /* Move to start of scan area (with some offset to skip over calibration
     * lines) */
    sane_geniusvp2_move_carriage_forward (area.top);
    sane_geniusvp2_set_scan_area (area);
    sane_geniusvp2_set_scan_params (res);
    sane_geniusvp2_reset_fifo ();
    sane_geniusvp2_set_operation_mode (mStop);
    sane_geniusvp2_set_operation_mode (mScan);

    return SANE_STATUS_GOOD;
}
