/*
 * Freescale UUT driver
 *
 * Copyright 2008-2013 Freescale Semiconductor, Inc.
 * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */
#include <common.h>
#include <asm/arch/vf610_qspi.h>

#define pr_debug printf
#define pr_info printf

#define UTP_FLAG_COMMAND	0x00000001
#define UTP_FLAG_DATA		0x00000002
#define UTP_FLAG_STATUS		0x00000004
#define UTP_FLAG_REPORT_BUSY	0x10000000

struct fsg_common;
enum utp_media_type {
	NOMEDIA = 0,
	SD,
	NAND,
	NOR,
	SPI,
	QSPI,
};

struct utp_pipe_info {
	enum utp_media_type media_type;
	int media_id;
	unsigned long int index;
	size_t blksz;
	unsigned long int addr;
} pinfo = {
	.media_type = NOMEDIA,
	.media_id = 0,
	.index = 0,
	.blksz = 0,
};

static u64 get_be64(u8 *buf)
{
	return ((u64)get_unaligned_be32(buf) << 32) |
		get_unaligned_be32(buf + 4);
}

static int utp_init(struct fsg_dev *fsg)
{
	/* the max message is 64KB */
	utp_context.buffer = vmalloc(0x10000);
	if (!utp_context.buffer) {
		printf("not enough memory!\n");
		return -EIO;
	}
	utp_context.utp_version = 0x1ull;
	fsg->utp = &utp_context;
	return 0;
}

static void utp_exit(struct fsg_dev *fsg)
{
	vfree(utp_context.buffer);
}

static struct utp_user_data *utp_user_data_alloc(size_t blksz, size_t size)
{
	struct utp_user_data *uud;
	size_t malloc_size = size + sizeof(*uud) + blksz;

	uud = malloc(malloc_size);
	if (!uud)
		return uud;
	memset(uud, 0, malloc_size);
	uud->data.size = size + sizeof(uud->data);
	return uud;
}

static void utp_user_data_free(struct utp_user_data *uud)
{
	kfree(uud);
}

/* Will be called when the host wants to get the sense data */
static int utp_get_sense(struct fsg_common *common)
{
	struct fsg_dev *fsg;

	fsg = common->fsg;
	if (UTP_CTX(fsg)->processed == 0)
		return -1;

	UTP_CTX(fsg)->processed = 0;
	return 0;
}

static int utp_do_read(struct fsg_common *common, void *data, size_t size)
{
	struct fsg_buffhd	*bh;
	struct fsg_dev		*fsg;
	int			rc;
	u32			amount_left;
	unsigned int		amount;

	/* Get the starting Logical Block Address and check that it's
	 * not too big */

	fsg = common->fsg;
	amount_left = size;
	if (unlikely(amount_left == 0))
		return -EIO;		/* No default reply*/

	pr_debug("%s: sending %d\n", __func__, size);
	for (;;) {
		/* Figure out how much we need to read:
		 * Try to read the remaining amount.
		 * But don't read more than the buffer size.
		 * And don't try to read past the end of the file.
		 * Finally, if we're not at a page boundary, don't read past
		 *	the next page.
		 * If this means reading 0 then we were asked to read past
		 *	the end of file. */
		amount = min((unsigned int) amount_left, FSG_BUFLEN);

		/* Wait for the next buffer to become available */
		bh = common->next_buffhd_to_fill;
		while (bh->state != BUF_STATE_EMPTY) {
			rc = sleep_thread(common);
			if (rc)
				return rc;
		}

		/* If we were asked to read past the end of file,
		 * end with an empty buffer. */
		if (amount == 0) {
			bh->inreq->length = 0;
			bh->state = BUF_STATE_FULL;
			break;
		}

		/* Perform the read */
		pr_info("Copied to %p, %d bytes started from %d\n",
				bh->buf, amount, size - amount_left);
		/* from upt buffer to file_storeage buffer */
		memcpy(bh->buf, data + size - amount_left, amount);
		amount_left  -= amount;
		common->residue -= amount;

		bh->inreq->length = amount;
		bh->state = BUF_STATE_FULL;

		/* Send this buffer and go read some more */
		bh->inreq->zero = 0;

		/* USB Physical transfer: Data from device to host */
		start_transfer(fsg, fsg->bulk_in, bh->inreq,
				&bh->inreq_busy, &bh->state);

		common->next_buffhd_to_fill = bh->next;

		if (amount_left <= 0)
			break;
	}

	return size - amount_left;
}

static int utp_do_write(struct fsg_common *common, void *data, size_t size)
{
	struct fsg_buffhd	*bh;
	struct fsg_dev		*fsg;
	int			get_some_more;
	u32			amount_left_to_req, amount_left_to_write;
	unsigned int		amount;
	int			rc;
	loff_t			offset;

	fsg = common->fsg;
	/* Carry out the file writes */
	get_some_more = 1;
	amount_left_to_req = amount_left_to_write = size;

	if (unlikely(amount_left_to_write == 0))
		return -EIO;

	offset = 0;
	while (amount_left_to_write > 0) {

		/* Queue a request for more data from the host */
		bh = common->next_buffhd_to_fill;
		if (bh->state == BUF_STATE_EMPTY && get_some_more) {

			/* Figure out how much we want to get:
			 * Try to get the remaining amount.
			 * But don't get more than the buffer size.
			 * And don't try to go past the end of the file.
			 * If we're not at a page boundary,
			 *	don't go past the next page.
			 * If this means getting 0, then we were asked
			 *	to write past the end of file.
			 * Finally, round down to a block boundary. */
			amount = min(amount_left_to_req, FSG_BUFLEN);

			if (amount == 0) {
				get_some_more = 0;
				/* cry now */
				continue;
			}

			/* Get the next buffer */
			amount_left_to_req -= amount;
			if (amount_left_to_req == 0)
				get_some_more = 0;

			/* amount is always divisible by 512, hence by
			 * the bulk-out maxpacket size */
			bh->outreq->length = bh->bulk_out_intended_length =
					amount;
			bh->outreq->short_not_ok = 1;
			start_transfer(fsg, fsg->bulk_out, bh->outreq,
					&bh->outreq_busy, &bh->state);
			common->next_buffhd_to_fill = bh->next;
			continue;
		}

		/* Write the received data to the backing file */
		bh = common->next_buffhd_to_drain;
		if (bh->state == BUF_STATE_EMPTY && !get_some_more)
			break;			/* We stopped early */
		if (bh->state == BUF_STATE_FULL) {
			common->next_buffhd_to_drain = bh->next;
			bh->state = BUF_STATE_EMPTY;

			/* Did something go wrong with the transfer? */
			if (bh->outreq->status != 0)
				/* cry again, COMMUNICATION_FAILURE */
				break;

			amount = bh->outreq->actual;

			/* Perform the write */
			memcpy(data + offset, bh->buf, amount);

			offset += amount;
			amount_left_to_write -= amount;
			common->residue -= amount;

			/* Did the host decide to stop early? */
			if (bh->outreq->actual != bh->outreq->length) {
				common->short_packet_received = 1;
				break;
			}
			continue;
		}

		/* Wait for something to happen */
		rc = sleep_thread(common);
		if (rc)
			return rc;
	}

	return -EIO;
}

static inline void utp_set_sense(struct fsg_common *common, u16 code, u64 reply)
{
	struct fsg_dev *fsg;

	fsg = common->fsg;
	UTP_CTX(fsg)->processed = true;
	UTP_CTX(fsg)->sdinfo = reply & 0xFFFFFFFF;
	UTP_CTX(fsg)->sdinfo_h = (reply >> 32) & 0xFFFFFFFF;
	UTP_CTX(fsg)->sd = (UTP_SENSE_KEY << 16) | code;
}

static void utp_poll(struct fsg_common *common)
{
	UTP_SS_PASS(common);
	return;
}

static int utp_exec(struct fsg_common *common,
		    char *command,
		    int cmdsize,
		    unsigned long long payload)
{
	struct fsg_dev *fsg = common->fsg;
	struct utp_user_data *uud = NULL, *uud2r;
	struct utp_context *ctx = UTP_CTX(fsg);

	uud2r = utp_user_data_alloc(0, cmdsize + 1);
	uud2r->data.flags = UTP_FLAG_COMMAND;
	uud2r->data.payload = payload;
	strncpy(uud2r->data.command, command, cmdsize);

	uud = utp_interpret_message(uud2r);
	utp_user_data_free(uud2r);

	if (command[0] == '!')	/* there will be no response */
		return 0;
#ifdef DEBUG
	pr_info("UUD:\n\tFlags = %02X\n", uud->data.flags);
	if (uud->data.flags & UTP_FLAG_DATA) {
		pr_info("\tbufsize = %d\n", uud->data.bufsize);
		print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_NONE,
			16, 2, uud->data.data, uud->data.bufsize, true);
	}
	if (uud->data.flags & UTP_FLAG_REPORT_BUSY)
		pr_info("\tBUSY\n");
#endif

	if (uud->data.flags & UTP_FLAG_DATA) {
		memcpy(ctx->buffer, uud->data.data, uud->data.bufsize);
		UTP_SS_SIZE(common, uud->data.bufsize);
	} else if (uud->data.flags & UTP_FLAG_REPORT_BUSY) {
		ctx->counter = 0xFFFF;
		UTP_SS_BUSY(common, ctx->counter);
	} else if (uud->data.flags & UTP_FLAG_STATUS) {
		printk(KERN_WARNING "%s: exit with status %d\n", __func__,
				uud->data.status);
		UTP_SS_EXIT(common, uud->data.status);
	} else {
		pr_debug("%s: pass\n", __func__);
		/* Workaround for ocassional push failures */
		UTP_SS_BUSY(common, 10);
	}
	utp_user_data_free(uud);
	return 0;
}

static int utp_send_status(struct fsg_common *common)
{
	struct fsg_buffhd	*bh;
	struct fsg_dev		*fsg = common->fsg;
	u8			status = USB_STATUS_PASS;
	struct bulk_cs_wrap	*csw;
	int 			rc;

	/* Wait for the next buffer to become available */
	bh = common->next_buffhd_to_fill;
	while (bh->state != BUF_STATE_EMPTY) {
		rc = sleep_thread(common);
		if (rc)
			return rc;
	}

	if (common->phase_error) {
		DBG(fsg, "sending phase-error status\n");
		status = USB_STATUS_PHASE_ERROR;

	} else if ((UTP_CTX(fsg)->sd & 0xFFFF) != UTP_REPLY_PASS) {
		status = USB_STATUS_FAIL;
	}

	csw = bh->buf;

	/* Store and send the Bulk-only CSW */
	csw->Signature = __constant_cpu_to_le32(USB_BULK_CS_SIG);
	csw->Tag = common->tag;
	csw->Residue = cpu_to_le32(common->residue);
	csw->Status = status;

	bh->inreq->length = USB_BULK_CS_WRAP_LEN;
	bh->inreq->zero = 0;
	start_transfer(fsg, fsg->bulk_in, bh->inreq,
			&bh->inreq_busy, &bh->state);
	common->next_buffhd_to_fill = bh->next;
	return 0;
}

static int utp_handle_message(struct fsg_common *common,
			      u8 *cdb_data,
			      int default_reply)
{
	struct utp_msg *m = (struct utp_msg *)cdb_data;
	struct fsg_dev *fsg = common->fsg;
	void *data = NULL;
	int r;
	struct utp_user_data *uud2r, *uud;
	unsigned long long param;
	unsigned long tag;

	if (m->f0 != 0xF0)
		return default_reply;

	tag = get_unaligned_be32((void *)&m->utp_msg_tag);
	param = get_be64((void *)&m->param);
	pr_debug("Type 0x%x, tag 0x%08lx, param %llx\n",
			m->utp_msg_type, tag, param);

	switch ((enum utp_msg_type)m->utp_msg_type) {

	case UTP_POLL:
		if (get_be64((void *)&m->param) == 1) {
			pr_debug("%s: version request\n", __func__);
			UTP_SS_EXIT(common, UTP_CTX(fsg)->utp_version);
			break;
		}
		utp_poll(common);
		break;
	case UTP_EXEC:
		pr_debug("%s: EXEC\n", __func__);
		data = kzalloc(common->data_size, GFP_KERNEL);
		/* copy data from usb buffer to utp buffer */
		utp_do_write(common, data, common->data_size);
		utp_exec(common, data, common->data_size, param);
		kfree(data);
		break;
	case UTP_GET: /* data from device to host */
		pr_debug("%s: GET, %d bytes\n", __func__, common->data_size);
		r = utp_do_read(common, UTP_CTX(fsg)->buffer, common->data_size);
		UTP_SS_PASS(common);
		break;
	case UTP_PUT: /* data from host to device */
		pr_debug("%s: PUT, %d bytes\n", __func__, common->data_size);
		uud2r = utp_user_data_alloc(pinfo.blksz, common->data_size);
		if (!uud2r) {
			printf("utp alloc fail\n");
			return -ENOMEM;
		}
		uud2r->data.bufsize = common->data_size;
		uud2r->data.flags = UTP_FLAG_DATA;
		utp_do_write(common, uud2r->data.data, common->data_size);
		/* don't know what will be written */
		utp_interpret_message(uud2r);
		utp_user_data_free(uud2r);
		/*
		 * Return PASS or FAIL according to uuc's status
		 * Please open it if need to check uuc's status
		 * and use another version uuc
		 */
#if 0
		struct utp_user_data *uud = NULL;
		struct utp_context *ctx;
		WAIT_ACTIVITY(write);
		ctx = UTP_CTX(fsg);
		mutex_lock(&ctx->lock);

		if (!list_empty(&ctx->write))
			uud = list_first_entry(&ctx->write,
					struct utp_user_data, link);

		mutex_unlock(&ctx->lock);
		if (uud) {
			if (uud->data.flags & UTP_FLAG_STATUS) {
				printk(KERN_WARNING "%s: exit with status %d\n",
					 __func__, uud->data.status);
				UTP_SS_EXIT(fsg, uud->data.status);
			} else {
				pr_debug("%s: pass\n", __func__);
				UTP_SS_PASS(fsg);
			}
			utp_user_data_free(uud);
		} else{
			UTP_SS_PASS(fsg);
		}
#endif
		/* workaround, normally would SS_PASS */
		if (common->data_size == 65536)
		{
			pr_debug("%s: Sending UTP_SS_BUSY\n", __func__);
			UTP_SS_BUSY(common, UTP_CTX(fsg)->counter);
		}
		else
		{
			pr_debug("%s: Sending UTP_SS_PASS\n", __func__);
			UTP_SS_PASS(common);
		}
		break;
	}

	utp_send_status(common);
	return -1;
}

u32 utp_run(char *cmd)
{
	return run_command(cmd, 0);
}

struct utp_user_data *utp_handle_command(char *cmd, u64 payload)
{
	u32			flags, status;
	size_t			size;
	struct utp_user_data	*answer;
	unsigned long int	dev;
	unsigned long int	skip;
	unsigned long int	addr;
	char			argbuf[20];
	char			*tmp;

	/* defaults */
	status = 0;
	flags = 0;
	size = 0;

	if (cmd[0] == '$') {
		status = utp_run(cmd + 2);
		if (status)
			flags = UTP_FLAG_STATUS;
	} else if (strncmp(cmd, "pipesd", 6) == 0) {
		pinfo.media_type = SD;
		pinfo.index = 0;
		pinfo.blksz = 512; /* TODO: find sd card block size */

		cmd += 7;
		tmp = strsep(&cmd, " ");
		printf(tmp);
		dev = simple_strtoul(tmp+4, NULL, 10);
		printf("dev: %lu\n", dev);
		pinfo.media_id = dev;
		sprintf(argbuf, "mmc dev %lu", dev);

		tmp = strsep(&cmd, " ");
		printf(tmp);
		skip = simple_strtoul(tmp+5, NULL, 10);
		pinfo.index += skip;
		utp_run(argbuf);
	} else if (strncmp(cmd, "pipeqspi", 8) == 0) {
		pinfo.media_type = QSPI;
		cmd += 9;
		tmp = strsep(&cmd, " ");
		if (strncmp(tmp, "addr=", 5) == 0) {
			addr = simple_strtoul(tmp+5, NULL, 16);
			printf("addr: 0x%x\n", addr);
			pinfo.addr = addr;
		} else {
			pinfo.addr = (pinfo.media_id ? QSPI1_FLASH_BASE_ADDR
						     : QSPI0_FLASH_BASE_ADDR);
		}
	} else if (strncmp(cmd, "qspiinit", 8) == 0) {
		pinfo.media_type = QSPI;
		pinfo.index = 0;
		pinfo.blksz = 0x200;
		sprintf(argbuf, cmd);
		cmd += 7;
		tmp = strsep(&cmd, " ");
		printf(tmp);
                dev = simple_strtoul(tmp+4, NULL, 10);
                printf("dev: %lu\n", dev);
		pinfo.media_id = dev;
		printf("%s\n", cmd);
		utp_run(argbuf);
	}
	else if (strncmp (cmd, "ubootcmd", 8) == 0) {
		sprintf(argbuf, cmd + 8);
		utp_run(argbuf);
	}
	else if (strncmp(cmd, "nandinit", 8) == 0) {
		 pinfo.media_type = NAND;
		 pinfo.index = 0;
		 pinfo.blksz = 2048;
		 sprintf(argbuf, cmd);
		 cmd += 9;
		 tmp = strsep(&cmd, " ");
		 if (strncmp(tmp, "addr=", 5) == 0) {
                        addr = simple_strtoul(tmp+5, NULL, 16);
                        printf("addr: 0x%x\n", addr);
			sprintf(argbuf, "nandinit 0x%x", addr);
			printf(argbuf);
			printf("%s\n", cmd);
			utp_run(argbuf);
                        }
		else
		{
			printf(" Pass the proper address\n");
		}

	}
	else if ( strncmp(cmd, "pipenand", 8) == 0 )
	{
		pinfo.media_type = NAND;
		cmd += 9;
                tmp = strsep(&cmd, " ");
                if (strncmp(tmp, "addr=", 5) == 0) {
                        addr = simple_strtoul(tmp+5, NULL, 16);
                        printf("addr: 0x%x\n", addr);
                        pinfo.addr = addr;
			}
		else
		{
			printf(" Please pass the proper address\n");
		}
	}

	answer = utp_user_data_alloc(0, size);
	if (flags & UTP_FLAG_STATUS)
		answer->data.status = status;
	return answer;
}

int utp_pipe_nand(u8 *data, size_t bufsize)
{
	pr_debug("got data chunk, size: %zx\n", bufsize);
	unsigned int blkcnt, dest;
	char argbuf[100];

	blkcnt = (bufsize / pinfo.blksz) + (((bufsize % pinfo.blksz) != 0) ? 1 : 0);
	dest = pinfo.addr + pinfo.blksz * pinfo.index;
	pr_debug("blkcnt: %x, index: %lx, bufsize: %zx\n", blkcnt, pinfo.index, bufsize);

	sprintf(argbuf, "nandwrite %x 0x%lx %x",dest, data, bufsize);
	pr_debug("%s\n", argbuf);
	utp_run(argbuf);
	pr_debug("after write to nand flash\n");
	pinfo.index += blkcnt;

}

int utp_pipe_sd(u8 *data, size_t bufsize)
{
	pr_debug("got data chunk, size: %zx\n", bufsize);
	unsigned int blkcnt, dest;
	char argbuf[100];

	blkcnt = (bufsize / pinfo.blksz) + (((bufsize % pinfo.blksz) != 0) ? 1 : 0);
	pr_debug("blkcnt: %x, index: %lx, bufsize: %zx\n", blkcnt, pinfo.index, bufsize);

	sprintf(argbuf, "mmc write %x %lx %x", data, pinfo.index, blkcnt);
	utp_run(argbuf);
	pr_debug("after write to sd\n");
	pinfo.index += blkcnt;

	return 0;
}

int utp_pipe_qspi(u8 *data, size_t bufsize)
{
	pr_debug("got data chunk, size: %zx\n", bufsize);
	unsigned int blkcnt, dest, len;
	char argbuf[100];

	blkcnt = (bufsize / pinfo.blksz) + (((bufsize % pinfo.blksz) != 0) ? 1 : 0);
	dest = pinfo.addr + pinfo.blksz * pinfo.index;
	len = bufsize;

	pr_debug("dest: %x, data: %lx, len: %zx\n", dest, data, len);

	sprintf(argbuf, "qspiwrite %x %lx %x",dest, data, len);
	pr_debug("%s\n", argbuf);
	utp_run(argbuf);
	pr_debug("after write to qspi\n");
	pinfo.index += blkcnt;

	return 0;
}

struct utp_user_data *utp_interpret_message(struct utp_user_data *uud2r)
{
	struct utp_user_data *answer;
	struct utp_message *msg = &uud2r->data;

	if (msg->flags & UTP_FLAG_COMMAND) {
		answer = utp_handle_command(msg->command, msg->payload);
		if (answer)
			return answer;
	} else if (msg->flags & UTP_FLAG_DATA) {
		switch(pinfo.media_type) {
		case NAND:
			utp_pipe_nand(msg->data, msg->bufsize);
			break;
		case SD:
			utp_pipe_sd(msg->data, msg->bufsize);
		break;
		case QSPI:
			utp_pipe_qspi(msg->data, msg->bufsize);
		default:
		break;
		}
	}
	return NULL;
}
