/* parspi
 * Reads an ADNS-2610 sensor attached to the parallel port. 
 * modified on 2006-04-25 to read the ADNS-2610
 * by Carsten Gross <carsten@siski.de>
 * 
 * Copyright (c) 2006 Carsten Gross
 * 
 * totally based on the original software:
 * Reads an ADNS-2050/ADNS-2051 sensor attached to the parallel port.
 * January 8, 2006
 * Joshua Wise <joshua@joshuawise.com>
 *
 * Copyright (c) 2006 Joshua Wise.
 *
 * You are free to copy this source verbatim or modified, as long as this
 * copyright statement is preserved. You are also free to copy any binaries
 * produced, so long as the source accompanies them.
 *
 */

#include <stdio.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include <linux/ppdev.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#define  XK_MISCELLANY
#include <X11/keysymdef.h>
#include <stdlib.h>

int pfd;
Display *mydisplay;
Window mywindow;
GC mygc;
XEvent myevent;
XSizeHints myhint;
XWMHints mywmhint;
int myscreen;
unsigned long colf,colb;

void openport()
{
  pfd = open("/dev/parport0", O_RDWR);
  if (pfd < 0)
  {
    perror("open(parport0)");
    exit(1);
  }
  if ((ioctl(pfd, PPEXCL) < 0) || (ioctl(pfd, PPCLAIM) < 0))
  {
    perror("lock parport0");
    close(pfd);
    exit(1);
  }
}

void relport()
{
  ioctl(pfd, PPRELEASE);
  close(pfd);
}

void vusleep(int i)
{
	struct timespec t;
	int j = i * 10;
	t.tv_sec = 0;
	t.tv_nsec = i * 1000;
	while (j > 0) {
		j--;
	}
	return;
}

int dclock(int i)
{
  /* SCK low, set output data */
  unsigned char data = (i)?8:0;
  if (ioctl(pfd, PPWDATA, &data))
  {
    perror("PPWDATA");
    relport();
    exit(1);
  }
  /* SCK high, hold output data */
  data |= 0x80;
  vusleep(10);
  if (ioctl(pfd, PPWDATA, &data))
  {
    perror("PPWDATA");
    relport();
    exit(1);
  }
  vusleep(01);
  /* read data from chip */
  if (ioctl(pfd, PPRSTATUS, &data) < 0)
  {
    perror("PPRSTATUS");
    relport();
    exit(1);
  }
  vusleep(10);
	if (data & 0x20) {
		return 1;
	} 
	return 0;
}

void send(unsigned char a,unsigned char d)
{
  int i;
  a |= 0x80;
  
  for (i=7; i>=0; i--)
    dclock((a>>i) & 0x1);
  for (i=7; i>=0; i--)
    dclock((d>>i) & 0x1);
}

/* read register a */

unsigned char recv(unsigned char a)
{
  int i;
  unsigned char d;
  a = a & 0x7F;
  for (i=7; i>=0; i--)
    dclock((a>>i) & 0x1);
  d=0;
  usleep(10);
  for (i=7; i>=0; i--)
    d |= dclock(1) << i;
 /* fprintf(stderr, "Status: %d\n", d); */
  return d;
}

unsigned char recvinv(unsigned char a)
{
  int i;
  unsigned char d;
  a = a & 0x7F;
  a=~a;
  for (i=7; i>=0; i--)
    dclock((a>>i) & 0x1);
  d=0;
  for (i=7; i>=0; i--)
    d |= dclock(1) << i;
  return d;
}

void initX();
void setstat(int,int,int);


int main()
{
  int i; unsigned char d;
  int a;
  int x = 0, y = 0;

  openport();
// reset:
  /* reset chip, set always awake */
  send(0x00 /* Configuration */, 0x81);
  send(0x00 /* Configuration */, 0x01);

  d = 0xFF;
  i=0;
  while (d != 0x1)
  {
	/* Read Status register, check wether chip is awake */
    d = recv(0x01);
    if (i > 128)
    {
      printf("Timeout while waiting for device to come out of reset (Status: %d)\n", d);
      relport();
      exit(1);
    }
    i++;
  }
  printf("Register dump:\n");
  for (a=0; a<0x80; a++)
  {
    printf("%02x ",recv(a));
    if ((a % 16) == 15)
      printf("\n");
  }
  initX();
	
  /* setup pixel read */
  send(0x08, 0);

  while (1) {
	  i = 0; x = 0; y = 0;
	  while (i < 324)
	  {
		int a;
		/* read byte */
		while ((a & 0x40) == 0) {
			a=recv(0x08);
			if (a & 0x80) {
				fprintf(stderr, "Start of frame\n");
			}
			vusleep(10);
		}
		a = a & 0x3f;
		setstat(x, y, a);
	//	fprintf(stderr, "DEBUG: y=%d, x=%d, a=%d\n", y, x, a);
		/* count the pixels */
		i = i + 1;
		y = y + 1;
		if (y > 17) {
			x = x + 1;
			y = 0;
		}
		XFlush(mydisplay);
	  }
	  fprintf(stderr, "Image is drawn.\n");
  }
  sleep(2);
  exit(0);
}

/* X stuff */
const char title[] = {"ADNS-2610 Dump"};

#define SCALE 16

void initX()
{
  mydisplay = XOpenDisplay("");
  if(mydisplay==NULL) {fprintf(stderr,"\033[2J\033[HCan't connect to %s\n",
                      XDisplayName("")); getchar(); return;}
  myscreen = DefaultScreen(mydisplay);
  colf = 0xFFFFFF;
  colb = BlackPixel(mydisplay,myscreen);
  myhint.x=200;myhint.y=0;
  myhint.width=16*SCALE+1;myhint.height=16*SCALE+1;
  myhint.flags=PPosition|PSize;
  mywmhint.flags=InputHint;
  mywmhint.input=True;

  mywindow=XCreateSimpleWindow(mydisplay,
      DefaultRootWindow(mydisplay),
      myhint.x,myhint.y,myhint.width,myhint.height,
      5,colf,colb);
  XSetStandardProperties(mydisplay,mywindow,title,title,
      None,NULL,0,&myhint);
  XSetWMHints(mydisplay,mywindow,&mywmhint);

  mygc=XCreateGC(mydisplay,mywindow,0,0);
  XSetBackground(mydisplay,mygc,colb);
  XSetForeground(mydisplay,mygc,colf);

  XSelectInput(mydisplay,mywindow,
      ButtonPressMask|KeyPressMask|KeyReleaseMask|ExposureMask);

  XMapRaised(mydisplay,mywindow);
  //cmap=DefaultColormap(mydisplay,myscreen);

  XClearWindow(mydisplay,mywindow);
  XFlush(mydisplay);
}

void setstat(int x, int y, int c)
{
  int grey = c<<2;
  int color = grey << 16 | grey << 8 | grey;
  XSetForeground(mydisplay, mygc, color);
  XFillRectangle(mydisplay, mywindow, mygc, x*SCALE, y*SCALE, SCALE, SCALE);
}
