/*****************************************************************************/

/*
 *	writeeeprom.c  --  "Burn" EEPROM on the modem.
 *
 *	Copyright (C) 1999-2001
 *          Thomas Sailer (t.sailer@alumni.ethz.ch)
 *
 *	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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  History:
 *   0.1  26.05.99  Created
 *
 */

/*****************************************************************************/

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <math.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "getopt.h"
#endif

#include "baycomusb.h"
#include "usbdrv.h"
#include "firmware/reset2firmware.h"

/* ---------------------------------------------------------------------- */

static int writecpucs(struct usbdevice *dev, unsigned char buf)
{
        int r;

        r = usb_control_msg(dev, 0x40, 0xa0, 0x7F92, 0, 1, &buf, 5000);
        if (r != 1) {
                fprintf(stderr, "ezusb: writecpucs(0x%02x) failed %d\n", buf, r);
                return -1;
        }
        return 0;
}

static int loadmem(struct usbdevice *dev, const unsigned char *fw, unsigned int len)
{
        unsigned addr, sz;
        int r;

        if (writecpucs(dev, 1))
                return -1;
        for (addr = 0; addr < len; addr += 64) {
                sz = len - addr;
                if (sz > 64)
                        sz = 64;
                r = usb_control_msg(dev, 0x40, 0xa0, addr, 0, sz, (void *)(fw+addr), 5000);
                if (r != sz) {
                        fprintf(stderr, "ezusb: code download(sz=%d,addr=0x%04x) returned %d\n", sz, addr, r);
                        return -1;
                }
        }
        return 0;
}

int adapter_reset(struct usbdevice *dev)
{
        /* first download the loader firmware to the AnchorChips core */
        if (loadmem(dev, reset2firmware, sizeof(reset2firmware)) ||
            /* start AnchorChips core */
            writecpucs(dev, 0))
                return -1;
        return 0;
}

/* --------------------------------------------------------------------- */

static int displaymem(struct usbdevice *dev)
{
        unsigned char buf[16];
        unsigned addr, u;
        int r;

        printf("EZUSB Memory contents:\n");
        for (addr = 0; addr < 0x2000; addr += 16) {
                r = usb_control_msg(dev, 0xc0, 0xa0, addr, 0, 16, buf, 5000);
                if (r != 16) {
                        fprintf(stderr, "ezusb: code upload(sz=%d,addr=0x%04x) returned %d\n", 16, addr, r);
                        return -1;
                }
                printf("%04x:", addr);
                for (u = 0; u < 16; u++)
                        printf(" %02x", buf[u]);
                printf("\n");
        }
        r = usb_control_msg(dev, 0xc0, 0xa0, 0x7F92, 0, 1, buf, 5000);
        if (r != 1) {
                fprintf(stderr, "ezusb: readcpucs returned %d\n", r);
                return -1;
        }
        printf("CPUCS: %02x\n", buf[0]);
        return 0;
}

static int displaycpucs(struct usbdevice *dev)
{
        unsigned char buf;
        int r;

        r = usb_control_msg(dev, 0xc0, 0xa0, 0x7F92, 0, 1, &buf, 5000);
        if (r != 1) {
                fprintf(stderr, "ezusb: readcpucs returned %d\n", r);
                return -1;
        }
        printf("CPUCS: %02x\n", buf);
        return 0;
}

/* --------------------------------------------------------------------- */

static int write_eeprom_byte(struct usbdevice *dev, unsigned char addr, unsigned char byte)
{
	unsigned char data[2];

	data[0] = addr;
	data[1] = byte;
	if (usb_control_msg(dev, 0x40, 0xb4, 0, 0xa0, 2, data, 500) != 2)
		return -1;
	/* I2C EEPROM takes 5ms to write byte */
	usleep(10000);
	return 0;
}

static int read_eeprom_byte(struct usbdevice *dev, unsigned char addr)
{
	unsigned char data[1];

	data[0] = addr;
	if (usb_control_msg(dev, 0x40, 0xb4, 0, 0xa0, 1, data, 500) != 1)
		return -1;
	if (usb_control_msg(dev, 0xc0, 0xb4, 0, 0xa0, 1, data, 500) != 1)
		return -1;
	return data[0] & 0xff;
}

/* --------------------------------------------------------------------- */

static int display_eeprom(struct usbdevice *dev, unsigned int serial)
{
	unsigned char eeprom[256];
	unsigned int i;
	int r;

	for (i = 0; i < 256; i++) {
		r = read_eeprom_byte(dev, i);
		if (r == -1) {
			fprintf(stderr, "Error reading EEPROM address 0x%02x\n", i);
			return -1;
		}
		eeprom[i] = r;
	}
	printf("EEPROM contents:");
	for (i = 0; i < 256; i++) {
		if (!(i & 15))
			printf("\n%04x:", i);
		printf(" %02x", eeprom[i]);
	}
	printf("\n\n"
	       "VID    %04x\n"
	       "PID    %04x\n"
	       "DID    %02x.%02x\n"
	       "Serial %c%c%c%c%c%c\n\n", 
	       eeprom[1] | (eeprom[2] << 8), 
	       eeprom[3] | (eeprom[4] << 8), 
	       eeprom[6], eeprom[5],
	       (eeprom[0x10] >= '0' && eeprom[0x10] <= '9') ? eeprom[0x10] : '.',
	       (eeprom[0x11] >= '0' && eeprom[0x11] <= '9') ? eeprom[0x11] : '.',
	       (eeprom[0x12] >= '0' && eeprom[0x12] <= '9') ? eeprom[0x12] : '.',
	       (eeprom[0x13] >= '0' && eeprom[0x13] <= '9') ? eeprom[0x13] : '.',
	       (eeprom[0x14] >= '0' && eeprom[0x14] <= '9') ? eeprom[0x14] : '.',
	       (eeprom[0x15] >= '0' && eeprom[0x15] <= '9') ? eeprom[0x15] : '.');
	return 0;
}

/* --------------------------------------------------------------------- */

static int program_eeprom(struct usbdevice *dev, unsigned int serial)
{
	unsigned char eeprom[256];
	unsigned int i;
	int r, ret = 0;

	memset(eeprom, 0xff, sizeof(eeprom));
	eeprom[0] = 0xb0;
	eeprom[1] = 0xc0;
	eeprom[2] = 0xba;
	eeprom[3] = 0x34;
	eeprom[4] = 0x61;
	eeprom[5] = 0x01;
	eeprom[6] = 0x01;

	eeprom[0x10] = '0' + ((serial / 100000) % 10);
	eeprom[0x11] = '0' + ((serial / 10000) % 10);
	eeprom[0x12] = '0' + ((serial / 1000) % 10);
	eeprom[0x13] = '0' + ((serial / 100) % 10);
	eeprom[0x14] = '0' + ((serial / 10) % 10);
	eeprom[0x15] = '0' + (serial % 10);

	for (i = 0; i < 7; i++) {
		r = write_eeprom_byte(dev, i, eeprom[i]);
		if (r == -1) {
			fprintf(stderr, "Error writing EEPROM address 0x%02x\n", i);
			return -1;
		}
	}
	for (i = 0x10; i < 0x16; i++) {
		r = write_eeprom_byte(dev, i, eeprom[i]);
		if (r == -1) {
			fprintf(stderr, "Error writing EEPROM address 0x%02x\n", i);
			return -1;
		}
	}
	for (i = 0; i < 7; i++) {
		r = read_eeprom_byte(dev, i);
		if (r == -1) {
			fprintf(stderr, "Error reading EEPROM address 0x%02x\n", i);
			return -1;
		}
		if (r != eeprom[i]) {
			fprintf(stderr, "Error comparing EEPROM address 0x%02x: 0x%02x != 0x%02x\n", i, r, eeprom[i]);
			ret = -1;
		}
	}
	for (i = 0x10; i < 0x16; i++) {
		r = read_eeprom_byte(dev, i);
		if (r == -1) {
			fprintf(stderr, "Error reading EEPROM address 0x%02x\n", i);
			return -1;
		}
		if (r != eeprom[i]) {
			fprintf(stderr, "Error comparing EEPROM address 0x%02x: 0x%02x != 0x%02x\n", i, r, eeprom[i]);
			ret = -1;
		}
	}
	if (!ret)
		printf("Successfully programmed serial %c%c%c%c%c%c\n", 
		       eeprom[0x10], eeprom[0x11], eeprom[0x12], eeprom[0x13], eeprom[0x14], eeprom[0x15]);
	return ret;
}

/* --------------------------------------------------------------------- */

int main(int argc, char *argv[])
{
        static const struct option long_options[] = {
                { "drv", 1, 0, 'D' },
                { "vendor", 1, 0, 'V' },
                { "product", 1, 0, 'P' },
		{ 0, 0, 0, 0 }
        };
	struct usbdevice *dev;
	struct usb_device_descriptor devdesc;
	int c, err = 0;
	unsigned int vid = 0x547, pid = 0x2131, serial;
	int (*func)(struct usbdevice *dev, unsigned int serial) = program_eeprom;
	struct timeval tv;

	printf("writeeeprom v" VERSION " (C) 1999-2001 by Thomas Sailer, HB9JNX/AE4WA\n");
	gettime(&tv);
	srand(tv.tv_sec ^ tv.tv_usec);
	serial = rand();
	while ((c = getopt_long(argc, argv, "D:V:P:dn:v:", long_options, NULL)) != EOF) {
		switch (c) {
		case 'D':
			usb_setmountpoint(optarg);
			break;

		case 'V':
			vid = strtoul(optarg, NULL, 0);
			break;

		case 'P':
			pid = strtoul(optarg, NULL, 0);
			break;

		case 'd':
			func = display_eeprom;
			break;

		case 'n':
			serial = strtoul(optarg, NULL, 0);
			break;

                case 'v':
                        verboselevel = strtoul(optarg, NULL, 0);
                        break;

		default:
			err++;
			break;
		}
	}
	if (err) {
		fprintf(stderr, "usage: %s [-v <vl>] [-D <drvpath>] [-V <vendorid>] [-P <productid>] [-n <serial>] [-d]\n", argv[0]);
		exit(1);
	}
	dev = usb_open(vid, pid, 0, 0);
	if (!dev)
		dev = usb_open(BAYCOMUSB_VENDORID, BAYCOMUSB_PRODUCTID_FPGALD, 0, 0);
	if (!dev) {
		fprintf(stderr, "Cannot open USB device with VendorID 0x%04x/ProductID 0x%04x\n", vid, pid);
		exit(1);
	}
	if (usb_getdevicedescriptor(dev, &devdesc)) {
		perror("usb_getdevicedescriptor");
		exit(1);
	}
	if ((devdesc.idVendor[0] | (devdesc.idVendor[1] << 8)) != BAYCOMUSB_VENDORID ||
	    (devdesc.idProduct[0] | (devdesc.idProduct[1] << 8)) != BAYCOMUSB_PRODUCTID_FPGALD) {
		if (adapter_reset(dev)) {
			perror("adapter_reset");
			exit(1);
		}
		usb_close(dev);
		/*sleep(3);*/
		usleep(3000000);
		if (!(dev = usb_open(BAYCOMUSB_VENDORID, BAYCOMUSB_PRODUCTID_FPGALD, 0, 0))) {
			fprintf(stderr, "Cannot open USB device\n");
			exit(1);
		}
	}
	if (usb_setconfiguration(dev, 1)) {
		perror("usb_setconfiguration");
		exit(1);
	}
	c = func(dev, serial);
	usb_close(dev);
	exit(abs(c));
}
