/* Copyright 2008-2011, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* To compile tscalib, connect to the board and run:
 *   gcc -mcpu=arm9 `pkg-config directfb --libs --cflags` tscalib.c -o tscalib
 *
 *  You may also need to install these prerequisites:
 *   apt-get install pkg-config libdirectfb-dev build-essential fbset
 *
 *  DirectFB requires the video mode you are using to be first in /etc/fb.modes.  
 *  You can get this information from fbset:
 *   cp /etc/fb.modes /etc/fb.modes.orig; fbset > /etc/fb.modes
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <string.h>
#include <directfb.h>
 
static IDirectFB *dfb = NULL;
static IDirectFBSurface *primary = NULL;

// The mpeek/mpoke functions are specific to the TS-47XX
long tsmem = 0x80004000;
volatile uint16_t *syscon = 0;
volatile uint16_t *fb;
int mem = 0;
int swapxy = 0, negx = 0, negy = 0; 

#define PENDOWN 0x1
#define DFBCHECK(x...)                                         \
  {                                                            \
    DFBResult err = x;                                         \
    if (err != DFB_OK)                                         \
      {                                                        \
        fprintf( stderr, "%s <%d>:\n\t", __FILE__, __LINE__ ); \
        DirectFBErrorFatal( #x, err );                         \
      }                                                        \
  }

uint16_t tpeek16(uint16_t addr) 
{
	uint16_t value;
	static uint16_t off = 0;
	if (mem == 0) {
		mem = open("/dev/mem", O_RDWR|O_SYNC);
		  syscon = mmap(0,
		  0x1000 * 6,
		  PROT_READ|PROT_WRITE,
		  MAP_SHARED,
		  mem,
		  tsmem);

		// Touch controller on 8900 is offboard
		if(syscon[0x4000/2] == 0x8900 || syscon[0x4000/2] == 0x8920) {
			off = 0x4080;
		}
	}

	return syscon[(addr+off)/2];
}

uint16_t readx() { return tpeek16(0x0); }
uint16_t ready() { return tpeek16(0x2); }

int get_ts_raw(int *x,int *y) {
	*x = swapxy ? ready() :readx();
	*y = swapxy ? readx() : ready();
	if ((*x & 1) && (*y & 1)) {
		*x = ((~*x) >> 4) & 0xfff;
		if (negx) *x = 4095-*x;
		*y = ((~*y) >> 4) & 0xfff;
		if (negy) *y = 4095-*y;
		return 1;
	}
	return 0;
}

void get_best_event_raw(int *px, int *py) {
	int i,x,y,tx,ty;

	for (i=0;i<8;i++) {
		get_ts_raw(&x,&y);
	}
	tx=0;
	ty=0;
	i = 0;
	while (i < 8) {
		if (get_ts_raw(&x,&y)) {
			tx += x;
			ty += y;
			i++;
		}
	}
	tx /= 8;
	ty /= 8;
	*px = tx;
	*py = ty;
}

int get_ts(int *xpt, int *ypt, int xres, int yres, int calib_dx, int calib_x0, 
	int calib_dy, int calib_y0) {
	get_best_event_raw(xpt, ypt);

	// calibration
	*xpt = ((*xpt * 4096) / calib_dx) - calib_x0;
	*ypt = ((*ypt * 4096) / calib_dy) - calib_y0;
	// resolution
	*xpt = *xpt * xres / 4096;
	*ypt = *ypt * yres / 4096;
}

int do_calibrate(int *calib_x0, int *calib_dx, int *calib_y0, int *calib_dy, 
	int xres, int yres)
{
	int x0,y0,x1,y1,x2,y2,x3,y3;
	int L,R,T,B;
	/* The method to get the calibration is to:
	 * 1 Clear the screen
	 * 2 Read any pending input values and toss them
	 * 3 Draw crosshair with a known intersection
	 * 4 Read touch even and average over several samples
	 * 5 Repeat this for all corners  */

	// Draw crosshair in upper left corner
	DFBCHECK (primary->SetColor (primary, 0, 0, 0, 0xff)); // Black
	DFBCHECK (primary->FillRectangle (primary, 0, 0, xres, yres));
	DFBCHECK (primary->SetColor (primary, 0xff, 0xff, 0xff, 0xff)); // white
	DFBCHECK (primary->DrawLine (primary, 45, 50, 55, 50));
	DFBCHECK (primary->DrawLine (primary, 50, 45, 50, 55));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
  	get_best_event_raw(&x0, &y0);
  	//printf("DEBUG upper left: %d, %d\n",x0, y0);

	// Draw crosshair in upper right corner
	DFBCHECK (primary->SetColor (primary, 0, 0, 0, 0xff)); // Black
	DFBCHECK (primary->FillRectangle (primary, 0, 0, xres, yres));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	usleep(500000); // Give user time to move finger
	DFBCHECK (primary->SetColor (primary, 0xff, 0xff, 0xff, 0xff)); // white
	DFBCHECK (primary->DrawLine (primary, xres - 55, 50, xres - 45, 50));
	DFBCHECK (primary->DrawLine (primary, xres - 50, 45, xres - 50, 55));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	get_best_event_raw(&x1,&y1);
	//printf("DEBUG upper right: %d, %d\n",x1,y1);

	// Draw a crosshair in the bottom left corner
	DFBCHECK (primary->SetColor (primary, 0, 0, 0, 0xff)); // Black
	DFBCHECK (primary->FillRectangle (primary, 0, 0, xres, yres));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	usleep(500000); // Give user time to move finger
	DFBCHECK (primary->SetColor (primary, 0xff, 0xff, 0xff, 0xff)); // white
	DFBCHECK (primary->DrawLine (primary, 45, yres - 50, 55, yres - 50));
	DFBCHECK (primary->DrawLine (primary, 50, yres - 55, 50, yres - 45));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	get_best_event_raw(&x2,&y2);
	//printf("DEBUG bottom left: %d, %d\n",x2,y2);

	// Draw a crosshair in the bottom right corner
	DFBCHECK (primary->SetColor (primary, 0, 0, 0, 0xff)); // Black
	DFBCHECK (primary->FillRectangle (primary, 0, 0, xres, yres));
	usleep(500000); // Give user time to move finger
	DFBCHECK (primary->SetColor (primary, 0xff, 0xff, 0xff, 0xff)); // white
	DFBCHECK (primary->DrawLine (primary, xres - 55, yres - 50, xres - 45, yres - 50));
	DFBCHECK (primary->DrawLine (primary, xres - 50, yres - 45, xres - 50, yres - 55));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	get_best_event_raw(&x3,&y3);
	//printf("DEBUG bottom right: %d, %d\n",x3,y3);

	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));

	L = (x0 + x2) / 2; // average offset reported to create X=50
	R = (x1 + x3) / 2; // average offset reported to create X=WIDE-50
	T = (y0 + y1) / 2; // average offset reported to create Y=50
	B = (y2 + y3) / 2; // average offset reported to create Y=430

	*calib_x0 = x0 - (R-L) * 50 / (xres - 100); // number of pels for 50 pixels wide
	*calib_y0 = y0 - (B-T) * 50 / (yres - 100); // number of pels for 50 pixels high
	  
	*calib_dx = (R-L) * xres / (xres - 100); // number of pels for xres pixels wide
	*calib_dy = (B-T) * yres / (yres - 100); // number of pels for yres pixels high
}

void usage(char **argv) {
	fprintf(stderr,
	  "Usage: %s [OPTIONS] ...\n"
	  "Technologic Systems resistive touch calibration utility.\n"
	  "\n"
	  "  -c, --calibrate          Perform the calibration and print new values"
	  "                           to stdout"
	  "  -v, --vals <x0,dx,y0,dy> Specify comma seperated values for testing\n"
	  "  -t, --test               Test values after calibration\n"
	  "  -s, --swapxy             Swap the X and Y values\n"
	  "  -a, --negx               Flip the X value\n"
	  "  -b, --negy               Flip the y value\n"
	  "  -h, --help               This help\n"
	  "\n"
	  "  Options -s, -a, and -b only work with --calibrate\n"
	  "\n",
	  argv[0]
	);
}

int main(int argc, char **argv) {
	int opt_test = 0, opt_calibrate = 0;
	int calib_x0 = 0, calib_dx = 0, calib_y0 = 0, calib_dy = 0;
	int c;
	int offset;
	int xres;
	int yres;
	int i = 0, j = 0, sz;
	char *st, *en;

	DFBSurfaceDescription dsc;
	DFBCHECK (DirectFBInit (&argc, &argv));
	static struct option long_options[] = {
	  { "calibrate", 0, 0, 'c' },
	  { "vals", 1, 0, 'v' },
	  { "help", 0, 0, 'h' },
	  { "test", 0, 0, 't' },
	  { "swapxy", 0, 0, 's' },
	  { "negx", 0, 0, 'a' },
	  { "negy", 0, 0, 'b' },
	  { 0, 0, 0, 0 }
	};

	while((c = getopt_long(argc, argv, "hsabv:ct", long_options, NULL)) != -1) {
		switch(c) {
			case 'h':
				usage(argv);
				return 0;
			case 't':
				if(opt_calibrate != 1 && calib_dx == 0 && calib_x0 == 0){
					fprintf(stderr, "Either --calibrate or --vals must be specifed to test\n");
					return 1;
				}

				opt_test = 1;
				break;
			case 's':
				swapxy = 1;
				break;
			case 'a':
				negx = 1;
				break;
			case 'b':
				negy = 1;
				break;
			case 'c':
				opt_calibrate = 1;
				break;
			case 'v':
				// Parse vals like "87,3867,129,3848"
				sz = strlen(optarg);
				st = 0, en = 0;
				for(st = optarg; j < sz; st = en + 1) {
					int val = strtoul(st, &en, 0);
					j += (en - st) + 1;
					if(i == 0)
						calib_x0 = val;
					else if (i == 1)
						calib_dx = val;
					else if (i == 2)
						calib_y0 = val;
					else if (i == 3)
						calib_dy = val;
					i++;
				}
				break;
			default:
				usage(argv);
				fprintf(stderr, "Unknown option \"%c\"\n", c);
				return 1;
		}
	}

	DFBCHECK (DirectFBCreate (&dfb));
	DFBCHECK (dfb->SetCooperativeLevel (dfb, DFSCL_FULLSCREEN));
	dsc.flags = DSDESC_CAPS;
	dsc.caps  = DSCAPS_PRIMARY | DSCAPS_FLIPPING;
	DFBCHECK (dfb->CreateSurface( dfb, &dsc, &primary ));
	DFBCHECK (primary->GetSize (primary, &xres, &yres));


	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));
	DFBCHECK (primary->Flip (primary, NULL, 0));
	DFBCHECK (primary->Clear (primary, 0, 0, 0, 0));


	if(opt_calibrate){ 
		do_calibrate(&calib_x0, &calib_dx, &calib_y0, &calib_dy, xres, yres);
		printf("calib_x0=%d\n"
			   "calib_dx=%d\n"
			   "calib_y0=%d\n"
			   "calib_dy=%d\n",
			calib_x0, calib_dx, calib_y0, calib_dy);
	}

	if(!opt_test) return;

	while(1) {
		int xpt;
		int ypt;

		// Gets an x & y coordinate based on the resolution
		get_ts(&xpt, &ypt, xres, yres, calib_dx, calib_x0, calib_dx, calib_y0);

		// Draw square at cursor position, and then flip to draw it on both
		// surfaces
		DFBCHECK (primary->SetColor (primary, 0xff, 0xff, 0xff, 0xff)); // white
		DFBCHECK (primary->FillRectangle (primary, xpt, ypt, 10, 10));
		DFBCHECK (primary->Flip (primary, NULL, 0));
		DFBCHECK (primary->SetColor (primary, 0xff, 0xff, 0xff, 0xff)); // white
		DFBCHECK (primary->FillRectangle (primary, xpt, ypt, 10, 10));
	}
}
