 /*
  * This program is derived from code bearing the following Copyright(s)
  */

 /*                                                        -*- linux-c -*-
  *   _  _ ____ __ _ ___ ____ ____ __ _ _ _ _ |
  * .  \/  |--| | \|  |  |--< [__] | \| | _X_ | s e c u r e  s y s t e m s
  *
  * .vt|ar5k - PCI/CardBus 802.11a WirelessLAN driver for Atheros AR5k chipsets
  *
  * Copyright (c) 2002, .vantronix | secure systems
  *                     and Reyk Floeter <reyk_(_at_)_va_(_dot_)__(_dot_)__(_dot_)_>
  *
  * This program is free software ; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation ; either version 2 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY ; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program ; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */

 /*
  * Modified by Jan Krupa for EEPROM read/write/repair
  * Version 1.0
  */

 #include <sys/mman.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <fcntl.h>
 
 #define AR5K_PCICFG 0x4010 
 #define AR5K_PCICFG_EEAE 0x00000001 
 #define AR5K_PCICFG_CLKRUNEN 0x00000004 
 #define AR5K_PCICFG_LED_PEND 0x00000020 
 #define AR5K_PCICFG_LED_ACT 0x00000040 
 #define AR5K_PCICFG_SL_INTEN 0x00000800 
 #define AR5K_PCICFG_BCTL		 0x00001000 
 #define AR5K_PCICFG_SPWR_DN 0x00010000 
 
 /* EEPROM Registers in the MAC */
 #define AR5211_EEPROM_ADDR 0x6000 
 #define AR5211_EEPROM_DATA 0x6004
 #define AR5211_EEPROM_COMD 0x6008
 #define AR5211_EEPROM_COMD_READ 0x0001
 #define AR5211_EEPROM_COMD_WRITE 0x0002
 #define AR5211_EEPROM_COMD_RESET 0x0003
 #define AR5211_EEPROM_STATUS 0x600C
 #define AR5211_EEPROM_STAT_RDERR 0x0001
 #define AR5211_EEPROM_STAT_RDDONE 0x0002
 #define AR5211_EEPROM_STAT_WRERR 0x0003
 #define AR5211_EEPROM_STAT_WRDONE 0x0004
 #define AR5211_EEPROM_CONF 0x6010
 
 #define VT_WLAN_IN32(a)  (*((volatile unsigned long int *)(mem + (a))))
 #define VT_WLAN_OUT32(v,a) (*((volatile unsigned long int *)(mem + (a))) = (v))

 #define ATHEROS_PCI_MEM_SIZE 0x10000

 #define ATHEROS_EEPROM_SIZE 0x800

 #define DOT_NUM 0x40
 
 int
 vt_ar5211_eeprom_read( unsigned char *mem,
 		       unsigned long int offset,
 		       unsigned short int *data )
 {
 	int timeout = 10000 ;
 	unsigned long int status ;
 
 	VT_WLAN_OUT32( 0, AR5211_EEPROM_CONF ),
 	usleep( 5 ) ;
 
 	/** enable eeprom read access */
 	VT_WLAN_OUT32( VT_WLAN_IN32(AR5211_EEPROM_COMD)
 		     | AR5211_EEPROM_COMD_RESET, AR5211_EEPROM_COMD) ;
 	usleep( 5 ) ;
 
 	/** set address */
 	VT_WLAN_OUT32( offset, AR5211_EEPROM_ADDR) ;
 	usleep( 5 ) ;
 
 	VT_WLAN_OUT32( VT_WLAN_IN32(AR5211_EEPROM_COMD)
 		     | AR5211_EEPROM_COMD_READ, AR5211_EEPROM_COMD) ;
 
 	while (timeout > 0) {
 		usleep(1) ;
 		status = VT_WLAN_IN32(AR5211_EEPROM_STATUS) ;
 		if (status & AR5211_EEPROM_STAT_RDDONE) {
 			if (status & AR5211_EEPROM_STAT_RDERR) {
 				(void) fputs( "eeprom read access failed!\n",
 					      stderr ) ;
 				return 1 ;
 			}
 			status = VT_WLAN_IN32(AR5211_EEPROM_DATA) ;
 			*data = status & 0x0000ffff ;
 			return 0 ;
 		}
 		timeout-- ;
 	}
 
 	(void) fputs( "eeprom read timeout!\n", stderr ) ;
 
 	return 1 ;
 }
 
 int
 vt_ar5211_eeprom_write( unsigned char *mem,
 		        unsigned int offset,
 		        unsigned short int new_data )
 {
 	int timeout = 10000 ;
 	unsigned long int status ;
 	unsigned long int pcicfg ;
 	int i ;
 	unsigned short int sdata ;
 
 	/** enable eeprom access */
 	pcicfg = VT_WLAN_IN32( AR5K_PCICFG ) ;
 VT_WLAN_OUT32( ( pcicfg & ~AR5K_PCICFG_SPWR_DN ), AR5K_PCICFG ) ;
 usleep( 500 ) ;
 	VT_WLAN_OUT32( pcicfg | AR5K_PCICFG_EEAE /* | 0x2 */, AR5K_PCICFG) ;
 	usleep( 50 ) ;
 
 	VT_WLAN_OUT32( 0, AR5211_EEPROM_STATUS );
 	usleep( 50 ) ;
 
 	/* VT_WLAN_OUT32( 0x1, AR5211_EEPROM_CONF ) ; */
 	VT_WLAN_OUT32( 0x0, AR5211_EEPROM_CONF ) ;
 	usleep( 50 ) ;
 
 	i = 100 ;
 retry:
 	/** enable eeprom write access */
 	VT_WLAN_OUT32( AR5211_EEPROM_COMD_RESET, AR5211_EEPROM_COMD);
 	usleep( 500 ) ;
 
 	/* Write data */
 	VT_WLAN_OUT32( new_data, AR5211_EEPROM_DATA );
 	usleep( 5 ) ;
 
 	/** set address */
 	VT_WLAN_OUT32( offset, AR5211_EEPROM_ADDR);
 	usleep( 5 ) ;
 
 	VT_WLAN_OUT32( AR5211_EEPROM_COMD_WRITE, AR5211_EEPROM_COMD);
 	usleep( 5 ) ;
 
 	for ( timeout = 10000 ; timeout > 0 ; --timeout ) {
 		status = VT_WLAN_IN32( AR5211_EEPROM_STATUS );
 		if ( status & 0xC ) {
 			if ( status & AR5211_EEPROM_STAT_WRERR ) {
 				fprintf( stderr,
 				 "eeprom write access failed!\n");
 				return 1 ;
 			}
 			VT_WLAN_OUT32( 0, AR5211_EEPROM_STATUS );
 			usleep( 10 ) ;
 			break ;
 		}
 		usleep( 10 ) ;
 		timeout--;
 	}
 	(void) vt_ar5211_eeprom_read( mem, offset, &sdata ) ;
 	if ( ( sdata != new_data ) && i ) {
 		--i ;
 		fprintf( stderr, "Retrying eeprom write!\n");
 		goto retry ;
 	}
 
 	return !i ;
 }
 
 static void
 Usage( char *progname )
 {
 	(void) fprintf( stderr,
 			"Usage: %s -r|-w physical_address_base data_file\n",
 			progname ) ;
 	return ;
 }
 
 int
 main( int argc, char **argv )
 {
 	unsigned long int base_addr ;
 	int fd ;
 	void *membase ;
 	unsigned short int sdata ;
	unsigned long int i;
	unsigned short int sdata_p1, sdata_p2;
	unsigned short int eeprom_data1[ATHEROS_EEPROM_SIZE];
	FILE * f1;
 
 	if ( argc < 3 ) {
 		Usage( argv[0] ) ;
 		return -1 ;
 	}
 
	base_addr = strtoul( argv[2], NULL, 0 ) ;
	
 	fd = open( "/dev/mem", O_RDWR ) ;
 	if ( fd < 0 ) {
 		fprintf( stderr, "Open of /dev/mem failed!\n" ) ;
 		return -2 ;
 	}
 	membase = mmap( 0, ATHEROS_PCI_MEM_SIZE, PROT_READ|PROT_WRITE,
 			MAP_SHARED|MAP_FILE, fd, base_addr ) ;
 	if ( membase == (void *) -1 ) {
 		fprintf( stderr,
 			 "Mmap of device at 0x%08X for 0x%X bytes failed!\n",
 			 base_addr, ATHEROS_PCI_MEM_SIZE ) ;
 		return -3 ;
 	}
 
 #if 0
 	(void) vt_ar5211_eeprom_write( (unsigned char *) membase,
 				       AR5K_EEPROM_PROTECT_OFFSET, 0 ) ;
 #endif /* #if 0 */

	// read eeprom
	if(strcmp(argv[1],"-r")==0) {
		// read eeprom from card		
		printf("Reading EEPROM..\n");
		for (i=0; i<ATHEROS_EEPROM_SIZE/2; i++) {
			if (!(i%DOT_NUM)) {
				if (i!=0) printf("]\n");
				printf("0x%04X [", i*2);
			}

			if ( vt_ar5211_eeprom_read( (unsigned char *) membase, i, &sdata ) )
				fprintf( stderr, "EEPROM read failed\n" ) ;

			                              
			sdata_p1=sdata/0x100;
			sdata_p2=sdata%0x100;

			/* printf( "Current value 0x%04X on position 0x%04X will change to 0x%04X - 0x%02X 0x%02X\n", sdata,
				i, 0, sdata_p1, sdata_p2) ; */

			eeprom_data1[i*2]=sdata_p2;
			eeprom_data1[i*2+1]=sdata_p1;

			printf(".");
			usleep(500);
		}
		printf("]\n\n");

		// save eeprom to file
		printf("Saving EEPROM to file.. ");
		f1 = fopen (argv[3], "w");
		if (f1!=NULL) {
			for (i=0; i<ATHEROS_EEPROM_SIZE; i++) {
				fputc (eeprom_data1[i], f1);
			}
			fclose(f1);
		} else {
			printf("Error opening file.\n");
			exit(1);
		}
		printf("done\n");
	}

	// write eeprom
	if(strcmp(argv[1],"-w")==0) {
		// read eeprom from file
		printf("Reading EEPROM from file.. ");
		f1 = fopen (argv[3], "r");
		if (f1!=NULL) {
			for (i=0; i<ATHEROS_EEPROM_SIZE; i++) {
				eeprom_data1[i]=fgetc(f1);
			}
			fclose(f1);
		} else {
			printf("Error opening file.\n");
			exit(1);
		}
		printf("done\n\n");

		// read eeprom from card and replace differences by data from file
		printf("Flashing EEPROM.. ('.' = no change, 'x' = diff+flash)\n");
		for (i=0; i<ATHEROS_EEPROM_SIZE/2; i++) {
			if (!(i%DOT_NUM)) {
				if (i!=0) printf("]\n");
				printf("0x%04X [", i*2);
			}
			
			if ( vt_ar5211_eeprom_read( (unsigned char *) membase, i, &sdata ) )
				fprintf( stderr, "EEPROM read failed\n" ) ;
			
			sdata_p1=eeprom_data1[i*2+1]*0x100+eeprom_data1[i*2];
			/* printf("sdata=0x%04X sdata_p1=0x%04X\n", sdata, sdata_p1); */

			if (sdata_p1!=sdata) {
				printf("x");

			 	if ( vt_ar5211_eeprom_write( (unsigned char *) membase, i, sdata_p1 ) )
 					fprintf( stderr, "EEPROM write failed\n" ) ;
 
	 			if ( vt_ar5211_eeprom_read( (unsigned char *) membase, i, &sdata ) )
 					fprintf( stderr, "EEPROM read failed\n" ) ;
 
			 	if ( sdata != sdata_p1 )
 					fprintf( stderr, "Write & read don't match 0x%04X != 0x%04X\n",
 						 sdata_p1, sdata ) ;

			} else {
				printf(".");
			}

			usleep(500);
		}
		printf("]\n");
	}
 
 	return 0 ;
 }
