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

//define NOIO	//define for test runs off system

#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 runBluez();
void doHCIComms();
void bringUpChip();
void shutdownChip();
void loadFirmware(const char *fileName);
unsigned int getTimeFromStart();

int portFD;
FILE *logFile; //yay for globals
struct timeval tStart;

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

	t=getTimeFromStart();

	//output to file and screen
	if(logFile){
		if(addTime)
			fprintf(logFile,"%i: ",t);
		va_start(args, fmt);
		r = vfprintf(logFile, fmt, args);
		va_end(args);
		fflush(logFile);
	}

	//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;
}

/** 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;
}

	
#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(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(){	
	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(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(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;	
	unsigned char data[1024];

	gettimeofday(&tStart,NULL);

	output(1,"BTHello started.\n");

	logFile=fopen("/var/log/bthello.log","wt");
	if(!logFile){ logFile=NULL; output(1,"FAILED TO OPEN LOG FILE\n"); }
	
#ifndef NOIO
	gpioInit(1);
#else
	output(1,"TESTING MODE - NOT USING IO\n");
#endif
	
//	dumpUARTRegs();
//	dumpBTgpios();	

	openPort();
	if(portFD == -1){ fclose(logFile); return 0; }

	output(1,"BTHello bringing up chip\n");
	bringUpChip(portFD);	

	output(1,"Starting HCI comms.\n");
	doHCIComms(portFD);

//	dumpUARTRegs();
	output(1,"closing port...");	fflush(stdout);
	close(portFD);
	output(1,"done\n");
	
	

#ifndef NOIO
	gpioCleanup();
#endif
	
	if(logFile){
		output(1,"closing log...");
		fclose(logFile);
		output(1,"done\n"); fflush(stdout);
	}

	return 0;
}

void bringUpChip(){
#ifndef NOIO
//	dumpUARTRegs();
	
	output(1,"Setting all low ... ");
	gpioSet(81,0);
	gpioSet(3,0);
	gpioSet(53,0);
	gpioSet(71,0);
	gpioSet(124,0);
	gpioSet(114,0);
	output(1,"ok, now 2s sleep.\n");

	tcflush(portFD, TCIOFLUSH); //flush buffers again

	sleep(2); //wait 1 sec
	
// 	dumpBTgpios();	
// 	dumpUARTRegs();

	output(1,"Bringing up 53,71 and 124\n");
	gpioSet(53,1);
	gpioSet(71,1);
	gpioSet(124,1);
// 	output(1," ok, now 6ms sleep.\n"); fflush(stdout);
	
	dumpBTgpios();
// 	dumpUARTRegs();
	usleep(5000); //at least 5ms sleep
	
	gpioSet(114,1);		//raise 114
// 	dumpBTgpios();		
	usleep(3000);		//wait 3ms	
	gpioSet(114,0);		//drop it again
// 	dumpBTgpios();
	usleep(3000);		//wait 3ms
	gpioSet(81,1);		//raise 81 and 114
	gpioSet(114,1);

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

void shutdownChip(){
	output(1,"Setting all low ... ");
	gpioSet(81,0);
	gpioSet(3,0);
	gpioSet(53,0);
	gpioSet(71,0);
	gpioSet(124,0);
	gpioSet(114,0);
}

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){
// 			output(0,"[%02x]",*c);
			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;
}


/* open text file, read pcakets, send them and wait for responses and write back to ouput file */
void doHCIComms(int portFD){
	FILE *in;
	unsigned char data[MAX_PACKET_SIZE];
	unsigned long tmp1,tmp2,tmp3;	
	int i,ret,n,line;

	in=fopen("/etc/packets.txt","rt");
	if(!in){ output(1,"Cannot open packets.txt\n"); return; }

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

	line = 1;
	while(!feof(in)){
		//read 2xOpcode and 1xLength
		ret = fscanf(in,"%x %x %x",&tmp1,&tmp2,&tmp3);
		if(ret != 3){ output(1,"Line %i: Need at least 3 bytes\n",line); break; }

		if(tmp1==0xAB && tmp2==0xAB && tmp3==0xAB){ //signature for reading the firmware file
			fscanf(in,"%s %x",data,&tmp1);  //read filename and terminator			
			if(tmp1 != 0xFF){ output(1,"Line %i: Firmware read signature found but incorrect terminator.\n",line); break; }
			loadFirmware(data);
			continue;
		}
		
		data[0] = 0x01; //hci command
		data[1]=tmp1;
		data[2]=tmp2;
		data[3]=tmp3;
		
		n = (int)tmp3;
		if((n+4) > MAX_PACKET_SIZE){ output(1,"Line %i: Packet too long\n",line); }
		
		for(i=0;i<n;i++){
			ret = fscanf(in,"%x",&tmp1);
			data[4+i] = tmp1;
			if(ret != 1){ output(1,"Line %i: Failed loading byte %i\n",line,i); break; }
		}
		ret = fscanf(in,"%x",&tmp1);
		if(ret != 1 || tmp1 != 0xFF){ output(1,"Line %i: Didn't or couldn't get 0xFF terminator\n",line); break; }
		
		
		output(1,"Sending: ");
		for(i=0;i<n+4;i++)
			output(1,"%02x ",data[i]);
		output(1,"...");
		
		ret = write(portFD, data, (n+4));
		output(1," sent %i of %i\n",ret,(n+4));

		//usleep(100000);
		
		//ret = read(portFD, data, MAX_PACKET_SIZE);

		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");
		line++;
		
	}

	fclose(in);
}

void loadFirmware(const char *fileName){
	FILE *in;	
	unsigned char packet[MAX_PACKET_SIZE]; //max block size is 256 anyway
	int i,j,n,ret;
	unsigned long l0,l1,l2,m0,m1,m2;
	
	output(1,"Loading firmware file '%s'\n",fileName);

	in=fopen(fileName,"rb");
	if(!in){ output(1,"Failed to open firmware file"); return; }

	while(!feof(in)){
		packet[0] = 0x01; //hci command

		//read op code and size into place
		ret = fread(&packet[1],1,3,in);
		if(ret != 3){ output(1,"Failed to read another 3 bytes, stopping\n"); break; }

		//read rest of packet
		ret = fread(&packet[4],1,packet[3],in);
		if(ret != packet[3]){ output(1,"Failed to read %i bytes of next packet.\n",packet[3]); break; }

		output(1,"Loaded packet of length %i, opcode %02x %02x\n",packet[3],packet[1],packet[2]);
// 		output(1,"Sending: ");
// 		for(i=0;i<(packet[3]+4);i++)
// 			output(1,"%02x ",packet[i]);
// 		output(1,"...");
		n = (packet[3]+4);
		i=0;

		do{
			if(n > 16){
				ret = write(portFD,packet+i,16);
				output(1,"(%02x,%i,%i) ",i,16,ret);
				i+=16;
				n-=16;
			}else{				
				ret = write(portFD,packet+i,n);
				output(1,"(%02x,%i,%i) ",i,n,ret);
				break;
			}
			usleep(10000);
		}while(n>0);
		//send this to the chip
		/* ret = write(portFD, packet, n);
		output(1," sent %i of %i\n",ret,n);  */

		//and wait for an ack
		ret = readHCIResponse(portFD,packet,MAX_PACKET_SIZE);
		output(1,"Received %i bytes: ",ret);
		if(ret > 0)
			for(i=0;i<ret;i++)
				output(1,"%02x ",packet[i]);	
		output(1,"\n");

	}

	fclose(in);

	//give the chip time to think about it
	usleep(500000);

	dumpUARTDL();

	output(1,"Resetting chip, setting baud rate to 921600 and waiting for chip to come back up...\n");
	//reset the chip
	packet[0] = 0x01;
	packet[1] = 0x03;
	packet[2] = 0x0c;
	packet[3] = 0x00;
	ret = write(portFD, packet, 4);
	
	//see if anything comes back in 115200
// 	ret = readHCIResponse(portFD,packet,MAX_PACKET_SIZE);
// 	output(1,"Received %i bytes: ",ret);
// 	if(ret > 0)
// 		for(i=0;i<ret;i++)
// 			output(1,"%02x ",packet[i]);	
// 	output(1,"\n");

	
//sit watching the CTS line	
/*	m0 = getUARTReg(MSR);
	l0 = getUARTReg(LSR);
	m1=m0;
	l1=l0;
	for(i=0;i<1000000;i++){
		m2 = getUARTReg(MSR);
		l2 = getUARTReg(LSR);
		if(m2 != m1){ output(1,"m2=%02x ",m2); m1=m2; }
		if(l2 != l1){ output(1,"l2=%02x ",l2); l1=l2; }		
		usleep(10);
	}	
	output(1,"m0=%02x l0=%02x\n",m0,l0); */

	
/*#ifndef NOIO
	//turn off flow control briefly
	setUARTReg(MCR, getUARTReg(MCR) &~ 0x00000022); //enable both CTS and RTS auto flow control
#endif*/
	

	//output(1,"sleep 200ms");	
	//usleep(200000); //wait a bit

dumpUARTRegs();
	//no sure where this ought to be got from
	#define   B921600 0010007 
dumpUARTRegs();
	portSettings(B921600); //and again in 921600
	//tcflush(portFD, TCIOFLUSH); //why not another?
	//dumpUARTDL();
dumpUARTRegs();	
#ifndef NOIO
	//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	
	output(1,"Set port to 921600,n,8,1 +flow control\n");
	dumpUARTRegs();
	dumpUARTDL();

	output(1,"Reset write wrote %i bytes.\n",ret);

	//just sit listening
	for(i=1;i<100;i++){
		if(!waitForByte(portFD,&packet[0]))return;
		output(1,"[%02x]\n",packet[0]); //we want times here
	}

	//and sit waiting for the ACK
// 	ret = readHCIResponse(portFD,packet,MAX_PACKET_SIZE);
// 	output(1,"Received %i bytes: ",ret);
// 	if(ret > 0)
// 		for(i=0;i<ret;i++)
// 			output(1,"%02x ",packet[i]);	
// 	output(1,"\n");
// 	

	
	
}




