/* Attempt to bring up and say hi to the BCM2045 bluetooth chip on the IPAQ214
*/

//#define NOIO

#define MAX_PACKET_SIZE 1024

#define N_BT_GPIOS 6
int btio[] = 		{ 124, 114, 81, 84,  71, 53 };
int lastGPIOState[] = 	{ -2,   -2, -2, -2, -2, -2 };

//UART registers (for get/setUARTReg() of gpios.c)
#define		RBR	0	// receive
#define		THR	0	// transmit holding
#define 	IER	1	// int enable
#define		IIR	2	// int ident
#define		LCR	3	// line ctrl
#define		MCR	4	// modem ctrl
#define		LSR	5	// line state
#define		MSR	6	// modem state

#include <stdio.h>
#include "gpios.h"	

#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <stdarg.h>
#include <sys/times.h>
#include <time.h>

void sendReset(int portFD);
void bringUpChip();
void shutdownChip();
unsigned int getTimeFromStart();
void twiddleTest();

struct timeval tStart;

/** Return time since start of program (overflows after 1.19 hours)*/
unsigned int getTimeFromStart(){
	struct timeval tNow;
	unsigned int t;
	
	gettimeofday(&tNow,NULL);
	t = (tNow.tv_sec - tStart.tv_sec); //seconds difference
	t *= 1000000; //in us
	t += tNow.tv_usec;	//oh ffs
	t -= tStart.tv_usec;

	return t;
}

int output(int addTime,const char *fmt, ...){
	va_list args;
	int r;
	unsigned int t;

	t=getTimeFromStart();

	if(addTime)  //makes a mess on screen
		printf("%i: ",t);
	
	va_start(args, fmt);
	r = vprintf(fmt, args);
	va_end(args);

	fflush(stdout);
	return r;
}

	
#ifndef NOIO
void dumpBTgpios(){
	int i;

	output(1,"state 1: ");
	for(i=0;i<N_BT_GPIOS;i++)
		output(1,"%i=%i ",btio[i],gpioGet(btio[i]));
	output(1,"\n");	
}

void dumpUARTRegs(){
	int i;
	output(1,"UART: IER=%02x, IIR=%02x, LCR=%02x, MCR=%02x, LSR=%02x, MSR=%02x.\n",
		getUARTReg(IER),getUARTReg(IIR),getUARTReg(LCR),getUARTReg(MCR),getUARTReg(LSR),getUARTReg(MSR));
}

void dumpUARTDL(){
	unsigned char dlm,dll;
	//set devisor latch and read speed
	setUARTReg(LCR, getUARTReg(LCR) | 0x00000080); 	
	dll = getUARTReg(RBR);
	dlm = getUARTReg(IER);
	setUARTReg(LCR, getUARTReg(LCR) & ~0x00000080);
	output(1,"DLM=%02x, DLL=%02X\n",dlm,dll);
}

#else
void dumpBTgpios(){};
void dumpUARTRegs(){};
#endif

void portSettings(int portFD, speed_t speed){
	struct termios options;
	
	tcgetattr(portFD, &options); //get current optinosa

	cfmakeraw(&options);
	
	cfsetispeed(&options, speed);
	cfsetospeed(&options, speed);	

	options.c_cflag |= (CLOCAL | CREAD); //enabled and local - err, yea
	
	options.c_cflag &= ~PARENB;	//no parity
	options.c_cflag &= ~CSTOPB;	//a stop bit
	options.c_cflag &= ~CSIZE;	//0 the character size bits
	options.c_cflag |= CS8;		//and select 8 data bits
	
	options.c_cflag |= CRTSCTS; //we DO want hardware flow control

	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);	//no local rubbish

	tcsetattr(portFD, TCSANOW, &options); //apply

	tcgetattr(portFD, &options); //get options backa again

}

int openPort(){	
	int portFD;

	portFD = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);

	if(portFD == -1){
		output(1,"No port descriptor, giving up\n");
		return -1;
	}

	fcntl(portFD, F_SETFL, 0);

#ifndef NOIO
	output(1,"Setting port to 9600, LCR=");
	portSettings(portFD,B9600); //once in 9600
	tcflush(portFD, TCIOFLUSH);  //go for a flush
	output(1,"%02x\n",getUARTReg(LCR));
	
	output(1,"Setting port to 115200, LCR=");	
	portSettings(portFD,B115200); //and again in 115200
	tcflush(portFD, TCIOFLUSH); //why not another?
	output(1,"%02x\n",getUARTReg(LCR));
	
	//the pxa27x_uart driver doesn't seem to know about the auto-flow bits
	//so set them ourself
	setUARTReg(MCR, getUARTReg(MCR) | 0x00000022); //enable both CTS and RTS auto flow control
#endif
	return portFD;
}


int main(int argc, char *argv[]) {
	int i,j,k,portFD;
	unsigned char data[1024];

	gettimeofday(&tStart,NULL);

	output(1,"Bluetooth diag test thingy\n----------------------------------\n");

#ifndef NOIO
	gpioInit(1);
#else
	output(1,"TESTING MODE - NOT USING IO\n");
#endif

	//if(argc > 1 && strcmp(argv[1],"on")==0){
		portFD=openPort();
		if(portFD == -1){ return 0; }

		output(1,"Shutting down chip first...\n");
#ifndef NOIO
		shutdownChip();
		tcflush(portFD, TCIOFLUSH); //flush buffers again
#endif
		output(1,"Waiting 2 secs.\n");
		sleep(2);

		output(1,"Bringing up chip...\n");
		twiddleTest();

		output(1,"Resetting chip...\n");
#ifndef NOIO
		sendReset(portFD);	
#endif

		output(1,"Turning LED on.\n");
#ifndef NOIO
		gpioSet(3,1);
#endif
	
		output(1,"closing port...");	fflush(stdout);
		close(portFD);
		output(1,"done\n");

	/*}else if(argc > 1 && strcmp(argv[1],"off")==0){
		output(1,"Shutting down chip...\n");
#ifndef NOIO
		shutdownChip();
		gpioSet(3,0);	//turn LED off
#endif
	
	}else
		printf("usage %s on/off\n",argv[0]);
	*/
	
#ifndef NOIO
	gpioCleanup();
#endif
	
	return 0;
}

void bringUpChip(){
#ifndef NOIO
		
	output(1,"Bringing up GPIOs 53,71 and 124\n");
	gpioSet(53,1);
	gpioSet(71,1);
	gpioSet(124,1);
	
	usleep(5000); //at least 5ms sleep
	
	output(1,"Twiddeling GPIO 114\n");
	gpioSet(114,1);		//raise 114
	usleep(3000);		//wait 3ms	
	gpioSet(114,0);		//drop it again
	
	usleep(3000);		//wait 3ms

	output(1,"Bringing up GPIOs 81 and 114\n");	
	gpioSet(81,1);		//raise 81 and 114
	gpioSet(114,1);

	usleep(500000);		//wait 500ms
#endif
}


void dumpAll(){
	int i;
	int gpio[] = { 53, 71, 79, 81, 84, 114, 124 };	
	output(1,"GPIOS: ");
	for(i=0;i<7;i++){		
		output(0,"%i=%i ",gpio[i],gpioGet(gpio[i]));
	}
	output(0,"\n");
}

void twiddleTest(){

	output(1, "MFPS: 53=%08x 79=%08x 84=%08x\n",mfpGet(0x474),mfpGet(0x4dc),mfpGet(0x4f0));
	
	dumpAll();

	mfpSet(0x27c, 0x0844);	// 3
 	mfpSet(0x2e0, 0x8C40);	// 3_2
 	mfpSet(0x474, 0x8840);	// 53
 	mfpSet(0x4bc, 0x8840);	// 71
 	mfpSet(0x4dc, 0x0840);	// 79 CTS
 	mfpSet(0x4e4, 0x0840);	// 81
 	mfpSet(0x4f0, 0x0840);	// 84 RTS
 	mfpSet(0x63c, 0x8040);	// 114
 	mfpSet(0x664, 0x8840);	// 124
	usleep(1000);	

	gpioSet(3, 1);
	output(1, "MFPS: 53=%08x 79=%08x 84=%08x\n",mfpGet(0x474),mfpGet(0x4dc),mfpGet(0x4f0));

	dumpAll();

	gpioSetDir(53, 1);
	gpioSetDir(71, 1);
	gpioSetDir(79, 0);	// CTS
	gpioSetDir(81, 1);
	gpioSetDir(84, 1);	// RTS
	gpioSetDir(114, 1);
	gpioSetDir(124, 1);
	usleep(1000);

	dumpAll();

	//everything off
	gpioSet( 53, 0);
	gpioSet( 71, 0);	
	gpioSet( 81, 0);
	gpioSet( 84, 0);	// RTS
	gpioSet( 114, 0);
	gpioSet( 124, 0);
	usleep(100000);			//wait for it to settle

	dumpAll();
	
	gpioSet(53, 1);
	gpioSet(71, 1);
	gpioSet(124, 1);
	usleep(1000);

	dumpAll();
		
	gpioSet(81, 0);
	gpioSet(84, 1);		// RTS			
	gpioSet(114, 1);
	usleep(2000);	// delay(2)

	dumpAll();

	gpioSet(114, 0);
	usleep(2000);	// delay(2)

	dumpAll();

	gpioSet(114, 1);
	gpioSet(81, 1);
	usleep(500000);	// delay(500)

	dumpAll();

	gpioSet(84, 0);	// 84(RTS)	--> off
	usleep(1000);

	//int cts = getGPIO(vGPIOs, 79);
	dumpAll();

	//everything off again
/*	gpioSet(53, 0);
	gpioSet(71, 0);	
	gpioSet(81, 0);
	gpioSet(84, 0);	// RTS
	gpioSet(114, 0);
	gpioSet(124, 0);
	usleep(1000);
	dumpAll();
*/

	gpioSet(3, 0);

	mfpSet(0x4dc, 0x0841);	// 79 CTS
 	mfpSet(0x4f0, 0x0841);	// 84 RTS
 	
	output(1, "MFPS: 53=%08x 79=%08x 84=%08x\n",mfpGet(0x474),mfpGet(0x4dc),mfpGet(0x4f0));
	dumpAll();

}

void shutdownChip(){
	output(1,"Shutting down chip.\n");
	dumpAll();
	gpioSet(81,0);
	gpioSet(3,0);
	gpioSet(53,0);
	gpioSet(71,0);
	gpioSet(124,0);
	gpioSet(114,0);
	dumpAll();
}

int waitForByte(int portFD, unsigned char *c){
	int i,ret;

	unsigned int t;
	unsigned int t0;
	
	t0 = getTimeFromStart();

#ifdef NOIO
	return 0; //auto timeout when not on the iPAQ
#endif

	//wait for a byte with a 2second timeout
	do{
		ret = read(portFD, c, 1);
		
		if(ret == 1){
			return 1;
		}

	}while((getTimeFromStart()-t0) < 2000000);

	output(1,"Timed out (2sec) waiting for some data.\n");
	return 0; //timed out
	
}

int readHCIResponse(int portFD, unsigned char packet[], int maxBytes){
	unsigned char c;
	int i,ret,n;
	do{
		if(!waitForByte(portFD,&packet[0]))return 0;
			
		if(packet[0] != 0x04)
			output(1,"OOP[%02x] ",c); //report out of place character
	}while(packet[0] != 0x04);
	
	//read two opcode byte
	if(!waitForByte(portFD,&packet[1]))return 0;

	//read length byte
	if(!waitForByte(portFD,&packet[2]))return 0;
	
	//read packet data
	for(i=0;i<packet[2];i++){
		if((3+i) >= maxBytes)return maxBytes; //don't overrun the buffer
		if(!waitForByte(portFD,&packet[3+i]))return 0;
	}

	return packet[2] + 3;
}

void sendReset(int portFD){
	unsigned char data[MAX_PACKET_SIZE];
	int i,ret;

	fcntl(portFD, F_SETFL, FNDELAY);	//make sure the port is in non-blocked reading mode

	//reset the chip
	data[0] = 0x01;
	data[1] = 0x03;
	data[2] = 0x0c;
	data[3] = 0x00;
	ret = write(portFD, data, 4);

	ret = readHCIResponse(portFD,data,MAX_PACKET_SIZE);
	output(1,"Received %i bytes: ",ret);
	if(ret > 0)
		for(i=0;i<ret;i++)
			output(1,"%02x ",data[i]);	
	output(1,"\n");
}
