/*
 * Copyright (C) 2010 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>

#include "usb.h"

int usb_boot(usb_handle * usb,
	     void *data1, unsigned sz1, void *data2, unsigned sz2)
{
	if (data1) {
		uint32_t msg_boot = 0xF0030002;
		uint32_t msg_getid = 0xF0030003;
		uint32_t msg_size = sz1;
		uint8_t id[81];
		int i;

#define OFF_CHIP	0x04
#define OFF_ID		0x0F
#define OFF_MPKH	0x26
		memset(id, 0xee, 81);
		fprintf(stderr, "reading ASIC ID\n");
		usb_write(usb, &msg_getid, sizeof(msg_getid));
		usb_read(usb, id, sizeof(id));

		fprintf(stderr, "CHIP: %02x%02x\n", id[OFF_CHIP + 0],
			id[OFF_CHIP + 1]);
		fprintf(stderr, "IDEN: ");
		for (i = 0; i < 20; i++)
			fprintf(stderr, "%02x", id[OFF_ID + i]);
		fprintf(stderr, "\nMPKH: ");
		for (i = 0; i < 32; i++)
			fprintf(stderr, "%02x", id[OFF_MPKH + i]);
		fprintf(stderr, "\nCRC0: %02x%02x%02x%02x\n",
			id[73], id[74], id[75], id[76]);
		fprintf(stderr, "CRC1: %02x%02x%02x%02x\n",
			id[77], id[78], id[79], id[80]);

		fprintf(stderr, "sending u-boot-spl to target... %08x\n",
			msg_boot);
		sleep(1);
		usb_write(usb, &msg_boot, sizeof(msg_boot));
		sleep(1);
		usb_write(usb, &msg_size, sizeof(msg_size));
		sleep(1);
		usb_write(usb, data1, sz1);
	}

	if (data2) {
		char command[64];
	       	char response[64];
		long rsz;

		sleep(1);
		fprintf(stderr, "sending download command to u-boot-spl\n");
		sprintf(command, "download:%08x", sz2);
		usb_write(usb, command, sizeof(command));
		fprintf(stderr, "waiting for \"DATA\" response to download: command\n");
		usb_read(usb, response, sizeof(response));
		if (strncmp("DATA", response, 4)) {
			fprintf(stderr, "unexpected response\"%s\"\n", response);
			return -1;
		}
		rsz = strtol(response + 4, 0, 16);
		if (rsz != sz2) {
			fprintf(stderr, "bad returned size\"%s\"\n", response);
			return -1;
		}
		sleep(1);
		fprintf(stderr, "transferring %d bytes of data\n", sz2);
		usb_write(usb, data2, sz2);
		fprintf(stderr, "write finished, waiting for \"OKAY\"\n");
		usb_read(usb, response, sizeof(response));
		if (strncmp("OKAY", response, 4) == 0) {
			fprintf(stderr, "data transfer successful\n");
		} else {
			fprintf(stderr, "unexpected response to data xfer\"%s\"\n", response);
			return -1;
		}
	}

	return 0;
}

int match_omap4_bootloader(usb_ifc_info * ifc)
{
	if (ifc->dev_vendor != 0x0451)
		return -1;
	if ((ifc->dev_product != 0xd010) && (ifc->dev_product != 0xd00f))
		return -1;
	return 0;
}

/*
 * same as above for now
 */
int match_u_boot_spl(usb_ifc_info * ifc)
{
	if (ifc->dev_vendor != 0x0525)
		return -1;
	if (ifc->dev_product != 0xFFFF)
		return -1;
	return 0;
}

void *load_file(const char *file, unsigned *sz)
{
	void *data;
	struct stat s;
	int fd;

	fd = open(file, O_RDONLY);
	if (fd < 0)
		return 0;

	if (fstat(fd, &s))
		goto fail;

	data = malloc(s.st_size);
	if (!data)
		goto fail;

	if (read(fd, data, s.st_size) != s.st_size) {
		free(data);
		goto fail;
	}

	close(fd);
	*sz = s.st_size;
	return data;

fail:
	close(fd);
	return 0;
}

extern unsigned char aboot_data[];
extern unsigned aboot_size;

int main(int argc, char **argv)
{
	void *data[2];
	unsigned sz[2];
	usb_handle *usb;
	int i;
	int once = 1;

	if (argc < 3) {
		fprintf(stderr, "usage: usbboot u-boot-spl.bin u-boot.bin\n");
		return 0;
	}

	for (i = 0; i < 2; i++) {
		data[i] = load_file(argv[i + 1], sz + i);
		if (data[i] == 0) {
			fprintf(stderr, "cannot load '%s'\n", argv[i + 1]);
			return -1;
		}
	}

	/*
	 * First talk to the omap4 mask rom boot loader
	 */
	for (;;) {
		usb = usb_open(match_omap4_bootloader);
		if (usb) {
			usb_boot(usb, data[0], sz[0], 0, 0);
			break;
		}
		if (once) {
			once = 0;
			fprintf(stderr, "waiting for OMAP44xx device...\n");
		}
		usleep(2500);
	}
	usb_close(usb);

	/*
	 * Second talk to u-boot-spl usb boot loader
	 * this is a new connection since there is no
	 * hand off between the mask rom usb code and
	 * u-boot-spl
	 */
	once = 1;
	for (;;) {
		usb = usb_open(match_u_boot_spl);
		if (usb)
			return usb_boot(usb, 0, 0, data[1], sz[1]);
		if (once) {
			once = 0;
			fprintf(stderr, "waiting for U-Boot-spl bootloader...\n");
		}
		usleep(2500);
	}

	return -1;
}
