/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   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, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <libusb.h>

#include "versaloon_include.h"
#include "versaloon.h"
#include "versaloon_internal.h"
#include "usbtoxxx/usbtoxxx.h"

uint8_t *versaloon_buf;
uint8_t *versaloon_cmd_buf;
uint16_t versaloon_buf_size;

struct versaloon_pending_t versaloon_pending[VERSALOON_MAX_PENDING_NUMBER];
uint16_t versaloon_pending_idx;

libusb_device_handle *versaloon_usb_device_handle;
static uint32_t versaloon_usb_to = VERSALOON_TIMEOUT;

RESULT versaloon_init(void);
RESULT versaloon_fini(void);
RESULT versaloon_get_target_voltage(uint16_t *voltage);
RESULT versaloon_set_target_voltage(uint16_t voltage);
RESULT versaloon_delay_ms(uint16_t ms);
RESULT versaloon_delay_us(uint16_t us);

struct versaloon_interface_t versaloon_interface = {
	.init				= versaloon_init,
	.fini				= versaloon_fini,
	{	/* adaptors */
		{	/* target_voltage */
			.get		= versaloon_get_target_voltage,
			.set		= versaloon_set_target_voltage,
		},
		{	/* gpio */
			.init		= usbtogpio_init,
			.fini		= usbtogpio_fini,
			.config		= usbtogpio_config,
			.out		= usbtogpio_out,
			.in			= usbtogpio_in,
		},
		{	/* delay */
			.delayms	= versaloon_delay_ms,
			.delayus	= versaloon_delay_us,
		},
		{	/* swd */
			.init		= usbtoswd_init,
			.fini		= usbtoswd_fini,
			.config		= usbtoswd_config,
			.seqout		= usbtoswd_seqout,
			.seqin		= usbtoswd_seqin,
			.transact	= usbtoswd_transact,
		},
		{	/* jtag_raw */
			.init		= usbtojtagraw_init,
			.fini		= usbtojtagraw_fini,
			.config		= usbtojtagraw_config,
			.execute	= usbtojtagraw_execute,
		},
		.peripheral_commit = usbtoxxx_execute_command,
	},
	{	/* usb_setting */
		.vid			= VERSALOON_VID,
		.pid			= VERSALOON_PID,
		.ep_out			= VERSALOON_OUTP,
		.ep_in			= VERSALOON_INP,
		.interface		= VERSALOON_IFACE,
		.serialstring	= NULL,
		.buf_size		= 256,
	}
};

/* programmer_cmd */
static uint32_t versaloon_pending_id;
static versaloon_callback_t versaloon_callback;
static void *versaloon_extra_data;
static struct versaloon_want_pos_t *versaloon_want_pos;

void versaloon_set_pending_id(uint32_t id)
{
	versaloon_pending_id = id;
}
void versaloon_set_callback(versaloon_callback_t callback)
{
	versaloon_callback = callback;
}
void versaloon_set_extra_data(void *p)
{
	versaloon_extra_data = p;
}

void versaloon_free_want_pos(void)
{
	uint16_t i;
	struct versaloon_want_pos_t *tmp, *free_tmp;

	tmp = versaloon_want_pos;
	while (tmp != NULL) {
		free_tmp = tmp;
		tmp = tmp->next;
		free(free_tmp);
	}
	versaloon_want_pos = NULL;

	for (i = 0; i < dimof(versaloon_pending); i++) {
		tmp = versaloon_pending[i].pos;
		while (tmp != NULL) {
			free_tmp = tmp;
			tmp = tmp->next;
			free(free_tmp);
		}
		versaloon_pending[i].pos = NULL;
	}
}

RESULT versaloon_add_want_pos(uint16_t offset, uint16_t size, uint8_t *buff)
{
	struct versaloon_want_pos_t *new_pos = NULL;

	new_pos = malloc(sizeof(*new_pos));
	if (NULL == new_pos) {
		LOG_ERROR(ERRMSG_NOT_ENOUGH_MEMORY);
		return ERRCODE_NOT_ENOUGH_MEMORY;
	}
	new_pos->offset = offset;
	new_pos->size = size;
	new_pos->buff = buff;
	new_pos->next = NULL;

	if (NULL == versaloon_want_pos)
		versaloon_want_pos = new_pos;
	else {
		struct versaloon_want_pos_t *tmp = versaloon_want_pos;

		while (tmp->next != NULL)
			tmp = tmp->next;
		tmp->next = new_pos;
	}

	return ERROR_OK;
}

RESULT versaloon_add_pending(uint8_t type, uint8_t cmd, uint16_t actual_szie,
	uint16_t want_pos, uint16_t want_size, uint8_t *buffer, uint8_t collect)
{
#if PARAM_CHECK
	if (versaloon_pending_idx >= VERSALOON_MAX_PENDING_NUMBER) {
		LOG_BUG(ERRMSG_INVALID_INDEX, versaloon_pending_idx,
			"versaloon pending data");
		return ERROR_FAIL;
	}
#endif

	versaloon_pending[versaloon_pending_idx].type = type;
	versaloon_pending[versaloon_pending_idx].cmd = cmd;
	versaloon_pending[versaloon_pending_idx].actual_data_size = actual_szie;
	versaloon_pending[versaloon_pending_idx].want_data_pos = want_pos;
	versaloon_pending[versaloon_pending_idx].want_data_size = want_size;
	versaloon_pending[versaloon_pending_idx].data_buffer = buffer;
	versaloon_pending[versaloon_pending_idx].collect = collect;
	versaloon_pending[versaloon_pending_idx].id = versaloon_pending_id;
	versaloon_pending_id = 0;
	versaloon_pending[versaloon_pending_idx].extra_data = versaloon_extra_data;
	versaloon_extra_data = NULL;
	versaloon_pending[versaloon_pending_idx].callback = versaloon_callback;
	versaloon_callback = NULL;
	versaloon_pending[versaloon_pending_idx].pos = versaloon_want_pos;
	versaloon_want_pos = NULL;
	versaloon_pending_idx++;

	return ERROR_OK;
}

RESULT versaloon_send_command(uint16_t out_len, uint16_t *inlen)
{
	int ret;
	int transferred;

#if PARAM_CHECK
	if (NULL == versaloon_buf) {
		LOG_BUG(ERRMSG_INVALID_BUFFER, TO_STR(versaloon_buf));
		return ERRCODE_INVALID_BUFFER;
	}
	if ((0 == out_len) || (out_len > versaloon_interface.usb_setting.buf_size)) {
		LOG_BUG(ERRMSG_INVALID_PARAMETER, __func__);
		return ERRCODE_INVALID_PARAMETER;
	}
#endif

	ret = libusb_bulk_transfer(versaloon_usb_device_handle,
			versaloon_interface.usb_setting.ep_out,
			versaloon_buf, out_len, &transferred, versaloon_usb_to);
	if (0 != ret || transferred != out_len) {
		LOG_ERROR(ERRMSG_FAILURE_OPERATION, "send usb data");
		return ERRCODE_FAILURE_OPERATION;
	}

	if (inlen != NULL) {
		ret = libusb_bulk_transfer(versaloon_usb_device_handle,
			versaloon_interface.usb_setting.ep_in,
			versaloon_buf, versaloon_interface.usb_setting.buf_size,
			&transferred, versaloon_usb_to);
		if (0 == ret) {
			*inlen = (uint16_t)transferred;
			return ERROR_OK;
		} else {
			LOG_ERROR(ERRMSG_FAILURE_OPERATION, "receive usb data");
			return ERROR_FAIL;
		}
	} else
		return ERROR_OK;
}

#define VERSALOON_RETRY_CNT 10
RESULT versaloon_init(void)
{
	uint16_t ret = 0;
	uint8_t retry;
	uint32_t timeout_tmp;

	/* malloc temporary buffer */
	versaloon_buf = malloc(versaloon_interface.usb_setting.buf_size);
	if (NULL == versaloon_buf) {
		LOG_ERROR(ERRMSG_NOT_ENOUGH_MEMORY);
		return ERRCODE_NOT_ENOUGH_MEMORY;
	}

	/* connect to versaloon */
	timeout_tmp = versaloon_usb_to;
	/* not output error message when connectting */
	/* 100ms delay when connect */
	versaloon_usb_to = 100;
	for (retry = 0; retry < VERSALOON_RETRY_CNT; retry++) {
		versaloon_buf[0] = VERSALOON_GET_INFO;
		if ((ERROR_OK == versaloon_send_command(1, &ret)) && (ret >= 3))
			break;
	}
	versaloon_usb_to = timeout_tmp;
	if (VERSALOON_RETRY_CNT == retry) {
		versaloon_fini();
		LOG_ERROR(ERRMSG_FAILURE_OPERATION, "communicate with versaloon");
		return ERRCODE_FAILURE_OPERATION;
	}

	versaloon_buf[ret] = 0;
	versaloon_buf_size = versaloon_buf[0] + (versaloon_buf[1] << 8);
	versaloon_interface.usb_setting.buf_size = versaloon_buf_size;
	LOG_INFO("%s", versaloon_buf + 2);

	/* free temporary buffer */
	free(versaloon_buf);
	versaloon_buf = NULL;

	versaloon_buf = malloc(versaloon_interface.usb_setting.buf_size);
	if (NULL == versaloon_buf) {
		versaloon_fini();
		LOG_ERROR(ERRMSG_NOT_ENOUGH_MEMORY);
		return ERRCODE_NOT_ENOUGH_MEMORY;
	}
	versaloon_cmd_buf = malloc(versaloon_interface.usb_setting.buf_size - 3);
	if (NULL == versaloon_cmd_buf) {
		versaloon_fini();
		LOG_ERROR(ERRMSG_NOT_ENOUGH_MEMORY);
		return ERRCODE_NOT_ENOUGH_MEMORY;
	}
	if (ERROR_OK != usbtoxxx_init()) {
		LOG_ERROR(ERRMSG_FAILURE_OPERATION, "initialize usbtoxxx");
		return ERROR_FAIL;
	}
	return versaloon_get_target_voltage(&ret);
}

RESULT versaloon_fini(void)
{
	if (versaloon_usb_device_handle != NULL) {
		usbtoxxx_fini();
		versaloon_free_want_pos();

		versaloon_usb_device_handle = NULL;

		if (versaloon_buf != NULL) {
			free(versaloon_buf);
			versaloon_buf = NULL;
		}
		if (versaloon_cmd_buf != NULL) {
			free(versaloon_cmd_buf);
			versaloon_cmd_buf = NULL;
		}
	}

	return ERROR_OK;
}

RESULT versaloon_set_target_voltage(uint16_t voltage)
{
	usbtopwr_init(0);
	usbtopwr_config(0);
	usbtopwr_output(0, voltage);
	usbtopwr_fini(0);

	return usbtoxxx_execute_command();
}

RESULT versaloon_get_target_voltage(uint16_t *voltage)
{
	uint16_t inlen;

#if PARAM_CHECK
	if (NULL == versaloon_buf) {
		LOG_BUG(ERRMSG_INVALID_BUFFER, TO_STR(versaloon_buf));
		return ERRCODE_INVALID_BUFFER;
	}
	if (NULL == voltage) {
		LOG_BUG(ERRMSG_INVALID_PARAMETER, __func__);
		return ERRCODE_INVALID_PARAMETER;
	}
#endif

	versaloon_buf[0] = VERSALOON_GET_TVCC;

	if ((ERROR_OK != versaloon_send_command(1, &inlen)) || (inlen != 2)) {
		LOG_ERROR(ERRMSG_FAILURE_OPERATION, "communicate with versaloon");
		return ERRCODE_FAILURE_OPERATION;
	} else {
		*voltage = versaloon_buf[0] + (versaloon_buf[1] << 8);
		return ERROR_OK;
	}
}

RESULT versaloon_delay_ms(uint16_t ms)
{
	return usbtodelay_delay(ms | 0x8000);
}

RESULT versaloon_delay_us(uint16_t us)
{
	return usbtodelay_delay(us & 0x7FFF);
}
