/*
 * This file is part of the flashrom project.
 *
 * Copyright (C) 2009 Paul Fox <pgf@laptop.org>
 * Copyright (C) 2009 Carl-Daniel Hailfinger
 *
 * 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; version 2 of the License.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#if FT2232_SPI_SUPPORT == 1

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "flash.h"
#include "spi.h"
#include <ftdi.h>

/*
 * The 'H' chips can run internally at either 12MHz or 60MHz.
 * The non-H chips can only run at 12MHz.
 */
#define CLOCK_5X 1

/*
 * In either case, the divisor is a simple integer clock divider.
 * If CLOCK_5X is set, this divisor divides 30MHz, else it divides 6MHz.
 */
#define DIVIDE_BY 3  /* e.g. '3' will give either 10MHz or 2MHz SPI clock. */

#define BITMODE_BITBANG_NORMAL	1
#define BITMODE_BITBANG_SPI	2

static struct ftdi_context ftdic_context;

int send_buf(struct ftdi_context *ftdic, const unsigned char *buf, int size)
{
	int r;
	r = ftdi_write_data(ftdic, (unsigned char *) buf, size);
	if (r < 0) {
		fprintf(stderr, "ftdi_write_data: %d, %s\n", r,
				ftdi_get_error_string(ftdic));
		return 1;
	}
	return 0;
}

int get_buf(struct ftdi_context *ftdic, const unsigned char *buf, int size)
{
	int r;
	r = ftdi_read_data(ftdic, (unsigned char *) buf, size);
	if (r < 0) {
		fprintf(stderr, "ftdi_read_data: %d, %s\n", r,
				ftdi_get_error_string(ftdic));
		return 1;
	}
	return 0;
}

int ft2232_spi_init(void)
{
	int f;
	struct ftdi_context *ftdic = &ftdic_context;
	unsigned char buf[512];
	char *portpos = NULL;
	int ft2232_type = FTDI_FT4232H;
	enum ftdi_interface ft2232_interface = INTERFACE_B;

	if (ftdi_init(ftdic) < 0) {
		fprintf(stderr, "ftdi_init failed\n");
		return EXIT_FAILURE; // TODO
	}

	if (programmer_param && !strlen(programmer_param)) {
		free(programmer_param);
		programmer_param = NULL;
	}
	if (programmer_param) {
		if (strstr(programmer_param, "2232"))
			ft2232_type = FTDI_FT2232H;
		if (strstr(programmer_param, "4232"))
			ft2232_type = FTDI_FT4232H;
		portpos = strstr(programmer_param, "port=");
		if (portpos) {
			portpos += 5;
			switch (toupper(*portpos)) {
			case 'A':
				ft2232_interface = INTERFACE_A;
				break;
			case 'B':
				ft2232_interface = INTERFACE_B;
				break;
			default:
				fprintf(stderr, "Invalid interface specified, "
					"using default.\n");
			}
		}
		free(programmer_param);
	}
	printf_debug("Using device type %s ",
		     (ft2232_type == FTDI_FT2232H) ? "2232H" : "4232H");
	printf_debug("interface %s\n",
		     (ft2232_interface == INTERFACE_A) ? "A" : "B");

	f = ftdi_usb_open(ftdic, 0x0403, ft2232_type);

	if (f < 0 && f != -5) {
		fprintf(stderr, "Unable to open FTDI device: %d (%s)\n", f,
				ftdi_get_error_string(ftdic));
		exit(-1); // TODO
	}

	if (ftdi_set_interface(ftdic, ft2232_interface) < 0) {
		fprintf(stderr, "Unable to select interface: %s\n",
				ftdic->error_str);
	}

	if (ftdi_usb_reset(ftdic) < 0) {
		fprintf(stderr, "Unable to reset FTDI device\n");
	}

	if (ftdi_set_latency_timer(ftdic, 2) < 0) {
		fprintf(stderr, "Unable to set latency timer\n");
	}

	if (ftdi_write_data_set_chunksize(ftdic, 512)) {
		fprintf(stderr, "Unable to set chunk size\n");
	}

	if (ftdi_set_bitmode(ftdic, 0x00, BITMODE_BITBANG_SPI) < 0) {
		fprintf(stderr, "Unable to set bitmode to SPI\n");
	}

#if CLOCK_5X
	printf_debug("Disable divide-by-5 front stage\n");
	buf[0] = 0x8a;		/* Disable divide-by-5. */
	if (send_buf(ftdic, buf, 1))
		return -1;
#define MPSSE_CLK 60.0

#else

#define MPSSE_CLK 12.0

#endif
	printf_debug("Set clock divisor\n");
	buf[0] = 0x86;		/* command "set divisor" */
	/* valueL/valueH are (desired_divisor - 1) */
	buf[1] = (DIVIDE_BY - 1) & 0xff;
	buf[2] = ((DIVIDE_BY - 1) >> 8) & 0xff;
	if (send_buf(ftdic, buf, 3))
		return -1;

	printf("SPI clock is %fMHz\n",
	       (double)(MPSSE_CLK / (((DIVIDE_BY - 1) + 1) * 2)));

	/* Disconnect TDI/DO to TDO/DI for loopback. */
	printf_debug("No loopback of TDI/DO TDO/DI\n");
	buf[0] = 0x85;
	if (send_buf(ftdic, buf, 1))
		return -1;

	printf_debug("Set data bits\n");
	/* Set data bits low-byte command:
	 *  value: 0x08  CS=high, DI=low, DO=low, SK=low
	 *    dir: 0x0b  CS=output, DI=input, DO=output, SK=output
	 */
#define CS_BIT 0x08
	buf[0] = SET_BITS_LOW;
	buf[1] = CS_BIT;
	buf[2] = 0x0b;
	if (send_buf(ftdic, buf, 3))
		return -1;

	// printf_debug("\nft2232 chosen\n");

	buses_supported = CHIP_BUSTYPE_SPI;
	spi_controller = SPI_CONTROLLER_FT2232;

	return 0;
}

int ft2232_spi_send_command(unsigned int writecnt, unsigned int readcnt,
		const unsigned char *writearr, unsigned char *readarr)
{
	struct ftdi_context *ftdic = &ftdic_context;
	static unsigned char *buf = NULL;
	/* failed is special. We use bitwise ops, but it is essentially bool. */
	int i = 0, ret = 0, failed = 0;
	int bufsize;
	static int oldbufsize = 0;

	if (writecnt > 65536 || readcnt > 65536)
		return SPI_INVALID_LENGTH;

	/* buf is not used for the response from the chip. */
	bufsize = max(writecnt + 9, 260 + 9);
	/* Never shrink. realloc() calls are expensive. */
	if (bufsize > oldbufsize) {
		buf = realloc(buf, bufsize);
		if (!buf) {
			fprintf(stderr, "Out of memory!\n");
			exit(1);
		}
		oldbufsize = bufsize;
	}

	/*
	 * Minimize USB transfers by packing as many commands as possible
	 * together. If we're not expecting to read, we can assert CS#, write,
	 * and deassert CS# all in one shot. If reading, we do three separate
	 * operations.
	 */
	printf_debug("Assert CS#\n");
	buf[i++] = SET_BITS_LOW;
	buf[i++] = 0 & ~CS_BIT; /* assertive */
	buf[i++] = 0x0b;

	if (writecnt) {
		buf[i++] = 0x11;
		buf[i++] = (writecnt - 1) & 0xff;
		buf[i++] = ((writecnt - 1) >> 8) & 0xff;
		memcpy(buf + i, writearr, writecnt);
		i += writecnt;
	}

	/*
	 * Optionally terminate this batch of commands with a
	 * read command, then do the fetch of the results.
	 */
	if (readcnt) {
		buf[i++] = 0x20;
		buf[i++] = (readcnt - 1) & 0xff;
		buf[i++] = ((readcnt - 1) >> 8) & 0xff;
		ret = send_buf(ftdic, buf, i);
		failed = ret;
		/* We can't abort here, we still have to deassert CS#. */
		if (ret)
			fprintf(stderr, "send_buf failed before read: %i\n",
				ret);
		i = 0;
		if (ret == 0) {
			/*
			 * FIXME: This is unreliable. There's no guarantee that
			 * we read the response directly after sending the read
			 * command. We may be scheduled out etc.
			 */
			ret = get_buf(ftdic, readarr, readcnt);
			failed |= ret;
			/* We can't abort here either. */
			if (ret)
				fprintf(stderr, "get_buf failed: %i\n", ret);
		}
	}

	printf_debug("De-assert CS#\n");
	buf[i++] = SET_BITS_LOW;
	buf[i++] = CS_BIT;
	buf[i++] = 0x0b;
	ret = send_buf(ftdic, buf, i);
	failed |= ret;
	if (ret)
		fprintf(stderr, "send_buf failed at end: %i\n", ret);

	return failed ? -1 : 0;
}

int ft2232_spi_read(struct flashchip *flash, uint8_t *buf, int start, int len)
{
	/* Maximum read length is 64k bytes. */
	return spi_read_chunked(flash, buf, start, len, 64 * 1024);
}

int ft2232_spi_write_256(struct flashchip *flash, uint8_t *buf)
{
	int total_size = 1024 * flash->total_size;
	int i;

	spi_disable_blockprotect();
	/* Erase first. */
	printf("Erasing flash before programming... ");
	if (erase_flash(flash)) {
		fprintf(stderr, "ERASE FAILED!\n");
		return -1;
	}
	printf("done.\n");
	printf_debug("total_size is %d\n", total_size);
	for (i = 0; i < total_size; i += 256) {
		int l, r;
		if (i + 256 <= total_size)
			l = 256;
		else
			l = total_size - i;

		if ((r = spi_nbyte_program(i, &buf[i], l))) {
			fprintf(stderr, "%s: write fail %d\n", __func__, r);
			return 1;
		}

		while (spi_read_status_register() & JEDEC_RDSR_BIT_WIP)
			/* loop */;
	}

	return 0;
}

#endif