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

#include <string.h>

#define UFPB_CMD_INIT        0x00
#define UFPB_CMD_GETSTATUS   0x01
#define UFPB_CMD_RESET       0x02
#define UFPB_CMD_ENABLE12V   0x03
#define UFPB_CMD_ENABLEVPP   0x04
#define UFPB_CMD_ENABLEMDX   0x05
#define UFPB_CMD_BOOTFZTAT   0x06
#define UFPB_CMD_DLUKERNEL   0x07
#define UFPB_CMD_TRANSPARENT 0x08
#define UFPB_CMD_SETBPS      0x09
#define UFPB_CMD_READBYTE    0x0a
#define UFPB_CMD_WRITEBYTE   0x0b
#define UFPB_CMD_RESETLOW    0x0e
#define UFPB_CMD_RESETHIGH   0x0f

extern int verbose;


int ufpb_send(int fd, unsigned char data)
{
	return serial_send(fd, data);
}

int ufpb_recv(int fd, unsigned char *buf)
{
	return serial_recv(fd, buf);
}

int ufpb_request_write(int fd, int request, unsigned char *buf, int nobytes)
{
	int k;

	ufpb_send(fd, 0xaa);
	ufpb_send(fd, request);
	ufpb_send(fd, nobytes);
	
	for (k = 0; k < nobytes; k++) {
		ufpb_send(fd, buf[k]);
	}
	
	ufpb_send(fd, 0x55);

	return 0;
}

int ufpb_reply_read(int fd, int request, unsigned char *buf, int maxbytes)
{
	int k, n, bad = 0;
	unsigned char data;
	int notail = 0;

	ufpb_recv(fd, &data);

	if (data != (0xe0 | request)) {
		bad = 1;
	}

	ufpb_recv(fd, &data);

	n = data;

	if (n > maxbytes) {
		n = maxbytes;
		notail = 1;
	}

	for (k = 0; k < n; k++) {
		ufpb_recv(fd, &buf[k]);
	}
	
	if (!notail) {

		ufpb_recv(fd, &data);

		if (data != 0x00) {
			bad = 1;
		}
	}

	if (bad) {
		return -1;
	}

	return n;
}

int ufpb_cmds_read(int fd, unsigned short address, unsigned char *data)
{
	unsigned char a[2];

	a[0] = address >> 8;
	a[1] = address & 0xff;

	if (ufpb_request_write(fd, UFPB_CMD_READBYTE, a, 2) < 0) {
		fprintf(stderr, "unable to write UFPB_CMD_READBYTE request\n");
		return -1;
	}
	
	if (ufpb_reply_read(fd, UFPB_CMD_READBYTE, data, 1) < 0) {
		fprintf(stderr, "unable to read UFPB_CMD_READBYTE reply\n");
		return -1;
	}

	return 0;
}

int ufpb_cmds_write(int fd, unsigned short address, unsigned char data, int x)
{
	unsigned char req[4], rep[2];

	req[0] = address >> 8;
	req[1] = address & 0xff;
	req[2] = data;
	req[3] = x; /* unknown */

	if (ufpb_request_write(fd, UFPB_CMD_WRITEBYTE, req, 4) < 0) {
		return -1;
	}
	
	if (ufpb_reply_read(fd, UFPB_CMD_WRITEBYTE, rep, 2) < 0) {
		return -1;
	}

	return 0;
}

int ufpb_cmds_setbps(int fd, int bps, int to_cpu)
{
	unsigned char *buf = NULL;
	unsigned char reply;

	if (verbose > 1) {
		printf("setting bps to %u.\n", bps);
	}

	if ((bps == 115200) && !to_cpu) {
		buf = (unsigned char []){ 0x00, 0x07, 0x01, 0x00, 0x00 };
	}

	if ((bps == 9600) && !to_cpu) {
		buf = (unsigned char []){ 0x00, 0x03, 0x01, 0x00, 0x00 };
	}

	if (!buf) {
		goto bad;
	}

	if (ufpb_request_write(fd, UFPB_CMD_SETBPS, buf, 5) < 0) {
		goto bad;
	}

	if (ufpb_reply_read(fd, UFPB_CMD_SETBPS, &reply, 1) < 0) {
		goto bad;
	}

	if (reply != 0x55) {
		goto bad;
	}

	if (serial_setup(fd, bps) < 0) {
		goto bad;
	}

	if (ufpb_send(fd, 0x57) < 0) {
		goto bad;
	}

	if (ufpb_recv(fd, &reply) < 0) {
		goto bad;
	}

	if (reply != 0x59) {
		goto bad;
	}
	
	if (ufpb_recv(fd, &reply) < 0) {
		goto bad;
	}

	if (reply != 0x00) {
		goto bad;
	}
	
	if (verbose > 1) {
		printf("bps setup ok.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to setup bps to %u.\n", bps);
	
	return -1;
}

int ufpb_cmds_reset(int fd)
{
	if (verbose > 1) {
		printf("sending reset command.\n");
	}

	if (ufpb_request_write(fd, UFPB_CMD_RESET, NULL, 0) < 0) {
		goto bad;
	}
	
	if (ufpb_reply_read(fd, UFPB_CMD_RESET, NULL, 0) < 0) {
		goto bad;
	}

	if (verbose > 1) {
		printf("reset command sent.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to send reset command.\n");

	return -1;
}

int ufpb_cmds_reset_set(int fd, int high)
{
	int cmd;

	if (verbose > 1) {
		printf("sending set reset command.\n");
	}

	if (high) {
		cmd = UFPB_CMD_RESETHIGH;
	}
	else {
		cmd = UFPB_CMD_RESETLOW;
	}

	if (ufpb_request_write(fd, cmd, NULL, 0) < 0) {
		goto bad;
	}
	
	if (ufpb_reply_read(fd, cmd, NULL, 0) < 0) {
		goto bad;
	}

	if (verbose > 1) {
		printf("set reset command sent.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to send set reset command.\n");

	return -1;
}

int ufpb_cmds_pins_set(int fd, int cmd, char *name, int enabled)
{
	unsigned char buf = !enabled;

	if (verbose > 1) {
		printf("sending %s command.\n", name);
	}

	if (ufpb_request_write(fd, cmd, &buf, 1) < 0) {
		goto bad;
	}
	
	if (ufpb_reply_read(fd, cmd, &buf, 1) < 0) {
		goto bad;
	}

	if (verbose > 1) {
		printf("%s command sent.\n", name);
	}

	return 0;

  bad:
	fprintf(stderr, "unable to send %s command.\n", name);

	return -1;
}

int ufpb_cmds_12v_set(int fd, int enabled)
{
	return ufpb_cmds_pins_set(fd, UFPB_CMD_ENABLE12V,
				  "UFPB_CMD_ENABLE12V", enabled);
}

int ufpb_cmds_vpp_set(int fd, int enabled)
{
	return ufpb_cmds_pins_set(fd, UFPB_CMD_ENABLEVPP,
				  "UFPB_CMD_ENABLEVPP", enabled);
}

int ufpb_cmds_mdx_set(int fd, int enabled)
{
	return ufpb_cmds_pins_set(fd, UFPB_CMD_ENABLEMDX,
				  "UFPB_CMD_ENABLEMDX", enabled);
}

int ufpb_cmds_mdy_set(int fd, int enabled)
{
	if (verbose > 1) {
		printf("setting mdy.\n");
	}

	if (ufpb_cmds_write(fd, 0xffb3, 0xdf, 1) < 0) {
		goto bad;
	}

	if (verbose > 1) {
		printf("set mdy command sent.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to set mdy.\n");

	return -1;
}

int ufpb_cmds_bootfztat(int fd)
{
	unsigned char rep;

	if (verbose > 1) {
		printf("sending boot fztat command.\n");
	}

	if (ufpb_request_write(fd, UFPB_CMD_BOOTFZTAT, NULL, 0) < 0) {
		goto bad;
	}
	
	if (ufpb_reply_read(fd, UFPB_CMD_BOOTFZTAT, NULL, 0) < 0) {
		printf("ostboll\n");
		goto bad;
	}

	if (ufpb_recv(fd, &rep) < 0) {
		goto bad;
	}
#if 0
	if (rep != 0xb3) { //0xbf ??
		goto bad;
	}
#endif
	if (ufpb_recv(fd, &rep) < 0) {
		goto bad;
	}
#if 0
	if (rep != 0x00) { // 0xff ??
		goto bad;
	}
#endif
	if (verbose > 1) {
		printf("boot fztat command sent.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to send boot fztat command.\n");

	return -1;
}

int ufpb_cmds_negif(int fd)
{
	if (verbose > 1) {
		printf("negotiating interface.\n");
	}

	if (ufpb_cmds_write(fd, 0xff7e, 0x00, 0) < 0) {
		goto bad;
	}

	if (verbose > 1) {
		printf("negotiating interface done.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to negotiate interface.\n");

	return -1;
}


int ufpb_cmds_dlukernel(int fd, unsigned char *buf, int bufsize)
{
	unsigned char req[4];
	unsigned char rep;
	int k;

	if (verbose > 1) {
		printf("downloading %u ukernel bytes.\n", bufsize);
	}

	req[0] = 0x00;
	req[1] = 0x00;
	req[2] = bufsize >> 8;
	req[3] = bufsize & 0xff;

	if (ufpb_request_write(fd, UFPB_CMD_DLUKERNEL, req, 4) < 0) {
		goto bad;
	}

	if (ufpb_reply_read(fd, UFPB_CMD_DLUKERNEL, &rep, 1) < 0) {
		goto bad;
	}

	if (rep != 0x44) {
		goto bad;
	}

	for (k = 0; k < bufsize; k++) {
		if (ufpb_send(fd, buf[k]) < 0) {
			goto bad;
		}

		if (ufpb_recv(fd, &rep) < 0) {
			goto bad;
		}

		if (rep != buf[k]) {
			goto bad;
		}
	}
	
	if (ufpb_recv(fd, &rep) < 0) {
		goto bad;
	}

	if (rep != 0x4b) {
		goto bad;
	}
	
	if (ufpb_recv(fd, &rep) < 0) {
		goto bad;
	}

	if (rep != 0x00) {
		goto bad;
	}
	
	if (verbose > 1) {
		printf("dlukernel ok.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to download ukernel.\n");
	
	return -1;
}

int ufpb_cmds_transparent(int fd)
{
	if (verbose > 1) {
		printf("sending transparent command.\n");
	}

	if (ufpb_request_write(fd, UFPB_CMD_TRANSPARENT, NULL, 0) < 0) {
		goto bad;
	}
	
	if (ufpb_reply_read(fd, UFPB_CMD_TRANSPARENT, NULL, 0) < 0) {
		goto bad;
	}

	if (verbose > 1) {
		printf("transparent command sent.\n");
	}

	return 0;

  bad:
	fprintf(stderr, "unable to send transparent command.\n");

	return -1;
}

int ufpb_cmds_init(int fd, unsigned char *ukernel, int ukernelsize)
{
	unsigned char hi_version, lo_version;
	unsigned int version;
	unsigned char buf0[2];

	serial_drain(fd);

	/* try a hello and see if the ufpb replys */

	if (ufpb_send(fd, 0x09) < 0) {
		return -1;
	}

	if (ufpb_recv(fd, &buf0[0]) < 0) {
		return -1;
	}
	
	if (buf0[0] != 0x31) {
		return -1;
	}

	/* read out version field at address 0xfe + 0xff */

	if (verbose > 1) {
		printf("reading out ufpb_version.\n");
	}

	if (ufpb_cmds_read(fd, 0x00fe, &hi_version) < 0) {
		return -1;
	}

	if (ufpb_cmds_read(fd, 0x00ff, &lo_version) < 0) {
		return -1;
	}

	version = (hi_version << 8) | lo_version;

	if (verbose) {
		printf("ufpb_version = 0x%04x.\n", version);
	}

	if (version != 0x0300) {
		fprintf(stderr, "ufpb version not tested.\n");
		fprintf(stderr, "remove this test in ufpb_cmds.c ");
		fprintf(stderr, "and try again if you're brave.");
		return -1;
	}

	/* set bps to 115200 to speed up things */

	if (ufpb_cmds_setbps(fd, 115200, 0) < 0) {
		return -1;
	}

	/* send a reset command */

	if (ufpb_cmds_reset(fd) < 0) {
		return -1;
	}

	/* send a reset low command */

	if (ufpb_cmds_reset_set(fd, 0) < 0) {
		return -1;
	}

	/* send a enable 12v command */

	if (ufpb_cmds_12v_set(fd, 1) < 0) {
		return -1;
	}

	/* send a enable vpp command */

	if (ufpb_cmds_vpp_set(fd, 1) < 0) {
		return -1;
	}

	/* send a enable mdx command */

	if (ufpb_cmds_mdx_set(fd, 1) < 0) {
		return -1;
	}

	/* enable mdy */

	if (ufpb_cmds_mdy_set(fd, 1) < 0) {
		return -1;
	}

	/* send a reset high command */

	if (ufpb_cmds_reset_set(fd, 1) < 0) {
		return -1;
	}

	/* send a boot fztat command */

	if (ufpb_cmds_bootfztat(fd) < 0) {
		return -1;
	}

	/* negotiate interface */

	if (ufpb_cmds_negif(fd) < 0) {
		return -1;
	}

	if (verbose) {
		printf("downloading monitor software.\n");
	}

	/* download ukernel (monitor) */

	if (ufpb_cmds_dlukernel(fd, ukernel, ukernelsize) < 0) {
		return -1;
	}

	/* switch back to 9600 */

	if (ufpb_cmds_setbps(fd, 9600, 0) < 0) {
		return -1;
	}

	/* move over to transparent mode */

	if (ufpb_cmds_transparent(fd) < 0) {
		return -1;
	}

	return 0;
}






