/*
 * testeep.c -- EEPROM read/write test application for self-made USB device 
 *              prototype applying FTDI's FT245BM USB FIFO chip. 
 * Version: 0.2
 * 
 * Copyright (c) 06/2004 by Wolfgang Wieser
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation. (See COPYING.GPL for details.)
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * 
 * USAGE: (As root)
 *   testeep [dump|write] [-v=vendor] [-d=device] 
 * NOTE: 
 *   You should compose your own EEPROM contents below "case +1:" in this 
 *   file, compile the program and then use "./testeep write" to write the 
 *   contents to the FTDI chip. This is not really convenient and the 
 *   name "testeep" already suggests that this is a test-and-demo program. 
 *   You may also use the ftdi_eeprom utility which can be found on the 
 *   FTDI home page (as of this writing, this utility can only write the 
 *   EEPROM but not read it -- this is the reason why I wrote this program). 
 */

#include "ftdi.h"
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

// gcc -O testeep.c -o testeep .libs/libftdi.a -I. -I.. -I../../libusb-0.1.7 -W -Wall -Wformat ../../libusb-0.1.7/.libs/libusb.a
// libftdi: make INCLUDES=-I../../libusb-0.1.7/ LIBS=../../libusb-0.1.7/.libs/libusb.so 

// /bin/sh ../libtool --silent --mode=link gcc  -O2  -I../confuse-2.0/   -L../confuse-2.0/src/ -o ftdi_eeprom   main.o  -lconfuse  -lftdi -lusb -L../../libftdi-0.3/ftdi/ -L../../libusb-0.1.7/
//  make INCLUDES=-I../../confuse-2.0/src/\ -I../../libftdi-0.3/ftdi/\ -I../../libusb-0.1.7/


static void Error(const char *where,int rv,struct ftdi_context *ctx,int doclose)
{
	if(rv)
	{
		fprintf(stderr,"%s: rv=%d, error=%s, errno=%s (%d)\n",
			where,rv,ctx->error_str,strerror(errno),errno);
		if(doclose)
		{
			rv=ftdi_usb_close(ctx);
			fprintf(stderr,"close: rv=%d, error=%s, errno=%s (%d)\n",
				rv,ctx->error_str,strerror(errno),errno);
		}
		ftdi_deinit(ctx);
		exit(1);
	}
	else
		fprintf(stderr,"%s: OK\n",where);
}


static void DumpEEPROM(const struct ftdi_eeprom *e)
{
	printf("EEPROM Content:\n"
		"  vendor=0x%04x,  product=0x%04x\n"
		"  self_powered=%d,  remote_wakeup=%d,  BM_type_chip=%d\n"
		"  in_is_isochronous=%d,  out_is_isochronous=%d,  suspend_pull_downs=%d\n"
		"  use_serial=%d,  change_usb_version=%d,  usb_version=%d,  max_power=%d\n"
		"  manufacturer=\"%s\",  product=\"%s\",  serial=\"%s\"\n",
		e->vendor_id,e->product_id,
		e->self_powered,e->remote_wakeup,e->BM_type_chip,
		e->in_is_isochronous,e->out_is_isochronous,e->suspend_pull_downs,
		e->use_serial,e->change_usb_version,e->usb_version,e->max_power,
		e->manufacturer,e->product,e->serial);
}


// Reverse of ftdi_eeprom_build() from libftdi: 
static void ftdi_eeprom_unbuild(unsigned char *buf,struct ftdi_eeprom *eeprom)
{
	// Addr 00: Stay 00 00
	// Addr 02: Vendor ID
	eeprom->vendor_id = (int)buf[0x02] | (int)buf[0x03]<<8;

	// Addr 04: Product ID
	eeprom->product_id = (int)buf[0x04] | (int)buf[0x05]<<8;

	// Addr 06: Device release number (0400h for BM features)
	switch((int)buf[0x06] | (int)buf[0x07]<<8)
	{
		case 0x0400:  eeprom->BM_type_chip=1;  break;
		case 0x0200:  eeprom->BM_type_chip=0;  break;
		default:  eeprom->BM_type_chip=-1;  break;
	}

	// Addr 08: Config descriptor
	// Bit 1: remote wakeup if 1
	// Bit 0: self powered if 1
	//
	eeprom->self_powered = (buf[0x08]&0x01) ? 1 : 0;
	eeprom->remote_wakeup = (buf[0x08]&0x02) ? 1 : 0;

	// Addr 09: Max power consumption: max power = value * 2 mA
	eeprom->max_power = buf[0x09];

	// Addr 0A: Chip configuration
	// Bit 7: 0 - reserved
	// Bit 6: 0 - reserved
	// Bit 5: 0 - reserved
	// Bit 4: 1 - Change USB version
	// Bit 3: 1 - Use the serial number string
	// Bit 2: 1 - Enable suspend pull downs for lower power
	// Bit 1: 1 - Out EndPoint is Isochronous
	// Bit 0: 1 - In EndPoint is Isochronous
	//
	eeprom->in_is_isochronous = (buf[0x0A]&0x01) ? 1 : 0;
	eeprom->out_is_isochronous = (buf[0x0A]&0x02) ? 1 : 0;
	eeprom->suspend_pull_downs = (buf[0x0A]&0x04) ? 1 : 0;
	eeprom->use_serial = (buf[0x0A]&0x08) ? 1 : 0;
	eeprom->change_usb_version = (buf[0x0A]&0x10) ? 1 : 0;

	// Addr 0B: reserved

	// Addr 0C: USB version low byte when 0x0A bit 4 is set
	// Addr 0D: USB version high byte when 0x0A bit 4 is set
	//if (eeprom->change_usb_version == 1)
		eeprom->usb_version = (int)buf[0x0C] | (int)buf[0x0D]<<8;

	eeprom->manufacturer=NULL;
	eeprom->product=NULL;
	eeprom->serial=NULL;

	do {
		// Addr 0E: Offset of the manufacturer string + 0x80
		int manufacturer_offset = (int)buf[0x0E]-0x80;

		// Addr 0F: Length of manufacturer string
		int manufacturer_size=(int)buf[0x0F]/2-1;

		// Addr 10: Offset of the product string + 0x80
		int product_offset = (int)buf[0x10]-0x80;

		// Addr 11: Length of product string
		int product_size=(int)buf[0x11]/2-1;

		// Addr 12: Offset of the serial string + 0x80
		int serial_offset = (int)buf[0x12]-0x80;

		// Addr 13: Length of serial string
		int serial_size = (int)buf[0x13]/2-1;
		
		int dummy,i,j;

		// Read manufacturer
		if(manufacturer_offset+2>=128)  break;
		dummy=(int)buf[manufacturer_offset]/2-1;
		if(dummy!=manufacturer_size)  break;
		if(buf[manufacturer_offset+1]!=0x03)  break;  // type: string

		i = manufacturer_offset+2;
		eeprom->manufacturer=(char*)malloc(manufacturer_size+2);
		if(i+manufacturer_size*2>=128)  break;
		for (j = 0; j < manufacturer_size; j++,i+=2)
			eeprom->manufacturer[j]=buf[i];
		eeprom->manufacturer[j]='\0';
		
		// Read product name
		if(product_offset+2>=128)  break;
		dummy=(int)buf[product_offset]/2-1;
		if(dummy!=product_size)  break;
		if(buf[product_offset+1]!=0x03)  break;  // type: string
		
		i = product_offset+2;
		eeprom->product=(char*)malloc(product_size+2);
		if(i+product_size*2>=128)  break;
		for (j = 0; j < product_size; j++,i+=2)
			eeprom->product[j]=buf[i];
		eeprom->product[j]='\0';
		
		// Read serial
		if(serial_offset+2>=128)  break;
		dummy=(int)buf[serial_offset]/2-1;
		if(dummy!=serial_size)  break;
		if(buf[serial_offset+1]!=0x03)  break;  // type: string
		
		i = serial_offset+2;
		eeprom->serial=(char*)malloc(serial_size+2);
		if(i+serial_size*2>=128)  break;
		for (j = 0; j < serial_size; j++,i+=2)
			eeprom->serial[j]=buf[i];
		eeprom->serial[j]='\0';
	} while(0);
}


static void PrintUsage()
{
	fprintf(stderr,
		"usage: testeep [dump|write] [-b=baudrate] "
		"[-v=vendor] [-d=device]\n");
	exit(1);
}


int main(int argc,char **arg)
{
	struct ftdi_context ctx;
	int rv,i;
	char *opmode=NULL;
	int iomode=-1;
	int device=0x6001,vendor=0x0403;
	
	for(i=1; i<argc; i++)
	{
		if(!strncmp(arg[i],"-v=",3))
			vendor=strtol(arg[i]+3,NULL,16);
		else if(!strncmp(arg[i],"-d=",3))
			device=strtol(arg[i]+3,NULL,16);
		else if(*arg[i]!='-' && !opmode)  opmode=arg[i];
		else PrintUsage();
	}
	
	if(opmode)
	{
		if(!strcmp(opmode,"dump"))  iomode=-1;
		else if(!strcmp(opmode,"write"))  iomode=+1;
		else PrintUsage();
	}
	
	rv=ftdi_init(&ctx);
	Error("init",rv,&ctx,0);
	
	rv=ftdi_usb_open(&ctx,vendor,device);
	Error("open",rv,&ctx,0);
	
	rv=ftdi_usb_reset(&ctx);
	Error("reset",rv,&ctx,1);
	
	switch(iomode)
	{
		case -1:
		{
			struct ftdi_eeprom eep;
			unsigned char buf[128];
			rv=ftdi_read_eeprom(&ctx,buf);
			Error("read eeprom",rv,&ctx,1);
			ftdi_eeprom_unbuild(buf,&eep);
			DumpEEPROM(&eep);
		}	break;
		case +1:
		{
			struct ftdi_eeprom eep,eep2;
			unsigned char buf[128];
			ftdi_eeprom_initdefaults(&eep);
			// Modify eep. 
			eep.vendor_id=0x0403;
			eep.product_id=0x6002;
			eep.manufacturer=strdup("Yoyodoyne");
			eep.product=strdup("YoyoNator");
			eep.serial=strdup("12865E-45OX-22-01");
			rv=ftdi_eeprom_build(&eep,buf);
			if(rv<0) Error("build eeprom",rv,&ctx,1);
			ftdi_eeprom_unbuild(buf,&eep2);
			DumpEEPROM(&eep2);
			rv=ftdi_erase_eeprom(&ctx);
			Error("erase eeprom",rv,&ctx,1);
			rv=ftdi_write_eeprom(&ctx,buf);
			Error("write eeprom",rv,&ctx,1);
		}	break;
	}
	
	rv=ftdi_usb_close(&ctx);
	Error("close",rv,&ctx,0);
	
	ftdi_deinit(&ctx);
	return(0);
}
