/* adc.c - WM8143-12 ADC I/O functions
 * 
 * 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 "geniusvp2-adc.h"
#include "geniusvp2-asic.h"
#include "geniusvp2-misc.h"

#define DARK_REF_LINES  5

/*
 * Write data to the specified ADC register
 */
static void
adc_write (unsigned char reg, unsigned char data)
{
    int bit;

    reg13.w = 0x80;
    sane_geniusvp2_reg_write (13, reg13.w);

    for (bit = 5; bit >= 0; bit--)
    {
        if (reg & (1 << bit))
            reg13.r.SDO = 1;
        else
            reg13.r.SDO = 0;
        sane_geniusvp2_reg_write (13, reg13.w);
        reg13.r.SCLK = 1;
        sane_geniusvp2_reg_write (13, reg13.w);
        reg13.r.SCLK = 0;
        sane_geniusvp2_reg_write (13, reg13.w);
    }
    for (bit = 7; bit >= 0; bit--)
    {
        if (data & (1 << bit))
            reg13.r.SDO = 1;
        else
            reg13.r.SDO = 0;
        sane_geniusvp2_reg_write (13, reg13.w);
        reg13.r.SCLK = 1;
        sane_geniusvp2_reg_write (13, reg13.w);
        reg13.r.SCLK = 0;
        sane_geniusvp2_reg_write (13, reg13.w);
    }
    reg13.r.SEN = 1;
    sane_geniusvp2_reg_write (13, reg13.w);
    reg13.r.SDO = 0;
    reg13.r.SEN = 0;
    sane_geniusvp2_reg_write (13, reg13.w);
}

/*
 * Set offset value
 * xoff = -255 .. +255
 */
static void
set_adc_offset (int roff, int goff, int boff)
{
    /* Set DAC Signs register */
    if (roff < 0)
        adc_write (0x24, 1);
    else
        adc_write (0x24, 0);
    if (goff < 0)
        adc_write (0x25, 1);
    else
        adc_write (0x25, 0);
    if (boff < 0)
        adc_write (0x26, 1);
    else
        adc_write (0x26, 0);

    if (roff < 0)
        roff = -roff;
    if (goff < 0)
        goff = -goff;
    if (boff < 0)
        boff = -boff;

    /* Set DAC Values register */
    adc_write (0x20, roff & 0xff);
    adc_write (0x21, goff & 0xff);
    adc_write (0x22, boff & 0xff);

    sane_geniusvp2_reg_write (13, 0x20);
}

/*
 * Set Programmable Gain Amplifier
 */
void
sane_geniusvp2_set_adc_gain (int rgain, int ggain, int bgain)
{
    adc_write (0x28, rgain & 0x1f);
    adc_write (0x29, ggain & 0x1f);
    adc_write (0x2a, bgain & 0x1f);
    sane_geniusvp2_reg_write (13, 0x20);
}

/*
 * Initialize ADC
 */
void
sane_geniusvp2_init_adc (void)
{
    sane_geniusvp2_reg_write (5, 0x90);
    sane_geniusvp2_reg_write (25, 0x0f);

    /* Clear panel button */
    sane_geniusvp2_reg_read (12, &reg12.w);
    reg12.r.KeyButton = 0;
    sane_geniusvp2_reg_write (12, reg12.w);

    adc_write (0x01, 0x03);
    adc_write (0x02, 0x04);
    adc_write (0x03, 0x22);
    adc_write (0x05, 0x10);
    set_adc_offset (190, 190, 190);
    sane_geniusvp2_set_adc_gain (2, 2, 2);
}

/*
 * Adjust offset to get a reading in the range 1 to 10
 */
void
sane_geniusvp2_adjust_offset (void)
{
    int offsetr = 0;
    int offsetg = 0;
    int offsetb = 0;
    int mask, rtmp, gtmp, btmp;

    set_adc_offset (offsetr, offsetg, offsetb);

    for (mask = 0x80; mask != 0x00; mask = mask >> 1)
    {
        sane_geniusvp2_get_avg_reading (&rtmp, &gtmp, &btmp);

        if (rtmp < 1)
            offsetr = offsetr - mask;
        else if (rtmp > 10)
            offsetr = offsetr + mask;

        if (gtmp < 1)
            offsetg = offsetg - mask;
        else if (gtmp > 10)
            offsetg = offsetg + mask;

        if (btmp < 1)
            offsetb = offsetb - mask;
        else if (btmp > 10)
            offsetb = offsetb + mask;

        set_adc_offset (offsetr, offsetg, offsetb);
    }
}

/*
 * Get an average color reading
 */
void
sane_geniusvp2_get_avg_reading (int *red, int *green, int *blue)
{
    int i, j;
    ScanArea area;

    area.top = 0;
    area.left = 6;
    area.height = DARK_REF_LINES;
    area.width = 7;

    /*sane_geniusvp2_lamp_off (); */

    sane_geniusvp2_set_scan_area (area);
    sane_geniusvp2_set_scan_params (600);
    sane_geniusvp2_reset_fifo ();
    sane_geniusvp2_set_operation_mode (mStop);
    sane_geniusvp2_set_operation_mode (mScanMotorOff);

    /* Read ADC to get average values */
    *red = *green = *blue = 0;
    for (i = 0; i < DARK_REF_LINES; i++)
    {
        sane_geniusvp2_wait_fifo (area.width);
        for (j = 0; j < area.width; j++)
        {
            sane_geniusvp2_reg_read (17, &reg17.w);
            *red += reg17.w;
            sane_geniusvp2_reg_read (17, &reg17.w);
            *green += reg17.w;
            sane_geniusvp2_reg_read (17, &reg17.w);
            *blue += reg17.w;
        }
    }
    *red /= (area.width * DARK_REF_LINES);
    *green /= (area.width * DARK_REF_LINES);
    *blue /= (area.width * DARK_REF_LINES);

    sane_geniusvp2_set_operation_mode (mStop);

    /*sane_geniusvp2_lamp_on (); */
}
