/*
 *    QuickCam
 *    --------
 *
 *    QuickCam code by Thomas Davis
 *    Additional QuickCam code Scott Laird <scott@laird.com>
 *    Polished and cleaned up by Michael Rudolph 
 *
 *    History
 *    -------
 *    1.02 - autofind camera, detect bimode, image stability, load flashes etc.
 *    1.01 - fixed getstatus for unidirectional, 
 *           Russ Nelson's shared image patches
 *    1.00 - complete overhaul: total camera control, bidirectional support,
 *           multiple x depths, etc.
 *    0.2  - initial forms based interface to set brightness,contrast,
 *           whitebal and depth
 *    <.2  - xqcam: no gui, just qcam output in an xwindow
 *
 *    Adapted by Thomas Braunl, Uni Stuttgart, 1996
 *    Adapted by Thomas Braunl, UWA, 1998
 *
 */

#define _IMPLEMENT_QCAM

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <sys/stat.h>
#include <asm/io.h>
#include "qcam.h"


QC_CONFIG _QCConfig;

static int status;
static BYTE bpp4tobpp4[16] = { 0, 15, 14, 13, 12, 11, 10,
                               9, 8, 7, 6, 5, 4, 3, 2, 1 };


void QC_reset(int port)
{
    outb(0x20, port+2);
    outb(0x75, port);
    status = inb(port);
    outb(0x0b, port+2);
    usleep(250);
    outb(0x0e, port+2);
    usleep(250);
}

int QC_find(void)
{
    int port;
    int ports[]= { 0x278, 0x378, 0x3bc };
    int i, reg, lastreg, count, p;

    for(p = 0; p < 3; p++)
    {
        port = ports[p];
	if (ioperm(port, 3, 1))
	    return -1;

	count = 0;
        lastreg = reg = inb(port+1) & 0xf0;
	for(i = 0; i < 30; i++)
	{
	    reg = inb(port+1) & 0xf0;
	    if (reg != lastreg)
                count++;

	    lastreg = reg;
	    usleep(10000);
	}

        if (count > 3)
	    return port;
	else
	    ioperm(port,3,0);
    }

    return 0;
}


int QC_waitfor_bi(int port)
{
    unsigned char status1 = 0, status2 = 1;

    if (_QCConfig.debugFlag) 
        fprintf(stderr, "waiting for camera to say \"Go!\"\n");
    outb(0x26, port + 2);

    while ((status1 & 0x0f) != 0x08)
        status1 = inb(port+1);

    outb(0x2f, port + 2);

    while ((status2 & 0x0f) != 0)
        status2 = inb(port + 1);

    return ((status1 & 0xF0 ) | ( status2 >> 4 ));
}


int QC_getversion(int port, int *colCamera, int *camVersion, int *connectorVersion)
/* returns 1 for gray, 2 for color */
{
    /* Get camera version to check whether we have a B/W or Colour camera*/
    *colCamera = 0; *camVersion = 0; *connectorVersion = 0;

    QC_reset(port);
    QC_sendbyte(port, 0x17);
    *camVersion = QC_getstatus(port);

    if((*camVersion & 0xf0) == 0) {
	_QCConfig.colour = 0;
        *colCamera = 0;
	return 1;
    }
    _QCConfig.colour = 1;
    *colCamera = 1;
    *connectorVersion = QC_getstatus(port);
    return 2;
}      

int QC_getstatus(int port)
{
    unsigned char status1 , status2 ;
		
    outb(0x06, port + 2);
    do
        status1 = inb(port + 1);
    while ((status1 & 0x08) == 0);

    outb(0x0e, port + 2);
    do
        status2 = inb(port + 1);
    while ((status2 & 0x08) != 0);

    return ((status1 & 0xF0 ) | ( status2 >> 4 ));
}


int QC_readbyte(int port)
{
  return QC_getstatus(port) ^ 0x88;  /* don't ask ... */
}


int QC_sendbyte(int port, int value)
{
    outb(value, port);
/*    outb(value, port);
    outb(value, port);*/

    return (QC_getstatus(port));
}

int QC_sendcmd(int cmd, int value)
{
    if (_QCConfig.debugFlag)
        fprintf(stderr, "cmd = 0x%x, value = 0x%x\n", cmd, value);

    if (QC_sendbyte(_QCConfig.quickcam_port, cmd) != cmd )
        return 1;

    if (QC_sendbyte(_QCConfig.quickcam_port, value) != value )
	return 1;

    return 0;
}


void QC_set_brightness()
{
    QC_sendcmd(QC_BRIGHTNESS, _QCConfig.brightness);
    QC_sendcmd(QC_BRIGHTNESS, 0x01);
    QC_sendcmd(QC_BRIGHTNESS, 0x01);
    QC_sendcmd(QC_BRIGHTNESS, _QCConfig.brightness);
    QC_sendcmd(QC_BRIGHTNESS, _QCConfig.brightness);
    QC_sendcmd(QC_BRIGHTNESS, _QCConfig.brightness);
}


void QC_set_size()
{

  if (_QCConfig.bpp6)
    QC_sendcmd(QC_XSIZE, _QCConfig.xsize / 4);
  else if (_QCConfig.colour) {
    switch (_QCConfig.zoom) { 
    case 0: /* 4:1 decimation */
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize*2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize*4);
      break;
    case 1: /* 2:1 decimation */
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize*2);
      break;
    case 2: /* No decimation */
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize/2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize);
      break;
    default:
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize/2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize);
      fprintf(stderr, "Unknown decimation\n");
      break;
    }
  } else {
    switch (_QCConfig.zoom) { 
    case 0: /* 4:1 decimation */
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize/2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize);
      break;
    case 1: /* 2:1 decimation */
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize/2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize);
      break;
    case 2: /* No decimation */
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize/2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize);
      break;
    default:
      QC_sendcmd(QC_XSIZE, _QCConfig.xsize/2);
      QC_sendcmd(QC_YSIZE, _QCConfig.ysize);
      fprintf(stderr, "Unknown decimation\n");
      break;
    }
  }    

      
  QC_sendcmd(QC_UNK1, _QCConfig.unk1); /* top */
  QC_sendcmd(QC_UNK2, _QCConfig.unk2); /* left */
}


void QC_set_contrast()
{
    if(_QCConfig.colour) {
	QC_sendcmd(0x25, _QCConfig.contrast);
    } else {
    	QC_sendcmd(QC_CONTRAST, _QCConfig.contrast);
    }
}

void QC_set_whitebalance()
{
    if(_QCConfig.colour) {
        QC_sendcmd(0x1f, _QCConfig.whitebalance);
    } else {
	QC_sendcmd(QC_WHITEBALANCE, _QCConfig.whitebalance);
    }
}

void QC_set_blackbalance()
{
    if(_QCConfig.colour) {
        QC_sendcmd(0x1d, _QCConfig.blackbalance);
    }
}

void QC_set_hue()
{
    if(_QCConfig.colour) {
        QC_sendcmd(0x21, _QCConfig.hue);
    }
}

void QC_set_saturation()
{
    if(_QCConfig.colour) {
        QC_sendcmd(0x23, _QCConfig.saturation);
    }
}

void QC_set_speed()
{
    if(_QCConfig.colour) {
        QC_sendcmd(0x2d, _QCConfig.speed);
    }
}

void QC_set_all()
{
    QC_set_brightness();
    QC_set_contrast();
    QC_set_whitebalance();
    /*    QC_set_saturation();*/
    /*    QC_set_hue();*/
    QC_set_blackbalance();
    QC_set_size();
}

void QC_Get4bppImageUni(BYTE * buffer)
{   unsigned int b1,b2,i;

    QC_sendcmd(QC_XFERMODE, _QCConfig.xfermode);

    for(i = 0; i < (_QCConfig.xsize * _QCConfig.ysize)/2; i++)
    {
        outb(0x06, _QCConfig.quickcam_port + 2);
        do
	    b1 = inb(_QCConfig.quickcam_port + 1);
	while ((b1 & 0x08) == 0);

	outb(0x0e, _QCConfig.quickcam_port + 2);
	do
	    b2 = inb(_QCConfig.quickcam_port + 1);
	while ((b2 & 0x08) != 0);

#if 0
	*buffer++ = bpp4tobpp6[b1>>4] >> 2;
	*buffer++ = bpp4tobpp6[b2>>4] >> 2;
#else
	*buffer++ = bpp4tobpp4[b1 >> 4];
	*buffer++ = bpp4tobpp4[b2 >> 4];
#endif
    }

    outb(0x0e, _QCConfig.quickcam_port + 2);

    do
        b2 = inb(_QCConfig.quickcam_port + 1);
    while ((b2 & 0x08) != 0);

    outb(0x06, _QCConfig.quickcam_port + 2);

    do
        b1 = inb(_QCConfig.quickcam_port + 1);
    while ((b1 & 0x08) != 0);

    outb(0x0f, _QCConfig.quickcam_port + 2);
}


void QC_CGet4bppImageUni(BYTE * buffer)
{ /* Get colour image in "millions of colour" mode */

    int numIterations;
    unsigned int i, stat;

    /* QC_set_size(); */
    /* check whether cam ready */
    do {
      QC_sendbyte(_QCConfig.quickcam_port, QC_SENDSTATUS);
      stat = QC_getstatus(_QCConfig.quickcam_port) & 0xfd;
    } while (stat);  /* not ready */

    numIterations = _QCConfig.xsize * _QCConfig.ysize;

    QC_sendcmd(QC_XFERMODE, _QCConfig.xfermode + 1);
    for(i = 0; i < numIterations; i++)
    {   /* get pixel value: red - green - blue */
        *buffer++ = QC_readbyte(_QCConfig.quickcam_port); 
        *buffer++ = QC_readbyte(_QCConfig.quickcam_port); 
        *buffer++ = QC_readbyte(_QCConfig.quickcam_port); 
    }
    /* read EOF sequence */
    while ((stat=QC_readbyte(_QCConfig.quickcam_port)) != 0x0e) {
    }
    if ((stat=QC_readbyte(_QCConfig.quickcam_port)) != 0x00) {
	fprintf(stderr, "0 byte expected %x\n",stat);
	exit(1);
    }
    if ((stat=QC_readbyte(_QCConfig.quickcam_port)) != 0x0f) {
	fprintf(stderr, "f byte expected %x\n",stat);
	exit(1);
    }
    /* write 0 byte and check */
    QC_sendbyte(_QCConfig.quickcam_port, 0);
    if ((stat=QC_getstatus(_QCConfig.quickcam_port)) != 0) {
        fprintf(stderr, "error in write 0\n");
    }
}
 

void QC_exit()
{
    remove(FILE_QCAM);
}

int QC_init()
{
    FILE *fp;

    /*atexit(QC_exit);*/

    if(geteuid())
    {
        fprintf(stderr, "Must be installed SUID or run as root. Exiting.\n");
	return -2;
    }

    fp = fopen(FILE_QCAM, "r");
    if (fp != NULL)
    {
        fclose(fp);
        return -1;
    }

    fp = fopen(FILE_QCAM, "w");
    if (fp == NULL)
        return -1;
    else
        fclose(fp);

    chown(FILE_QCAM, getuid(), getgid());
    chmod(FILE_QCAM, 0666);

    _QCConfig.quickcam_port = 0x378;
    _QCConfig.brightness    = 140;
    _QCConfig.whitebalance  = 50;
    _QCConfig.blackbalance  = 128;
    _QCConfig.saturation    = 130;
    _QCConfig.hue           = 128;
    _QCConfig.contrast      = 100;
    _QCConfig.unk1          = 0x01;
    _QCConfig.unk2          = 0x07;
    _QCConfig.bpp6          = 0;
    _QCConfig.biMode        = 0;
    _QCConfig.canDoBi       = 0;
    _QCConfig.zoom          = 1;
    _QCConfig.xfermode      = 0;
    _QCConfig.speed         = 2;
    _QCConfig.debugFlag     = 0;
    _QCConfig.xsize         = 160;
    _QCConfig.ysize         = 120;

    return 0;
}


