/*
 * (C) Copyright 2012 Henrik Nordstrom <henrik@henriknordstrom.net>
 *
 * display information about sunxi boot headers
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "common.h"
#include "types.h"

/* boot_file_head copied from mksunxiboot */
/* boot head definition from sun4i boot code */
typedef struct boot_file_head
{
	u32  jump_instruction;   // one intruction jumping to real code
	u8   magic[8];           // ="eGON.BT0" or "eGON.BT1",  not C-style string.
	u32  check_sum;          // generated by PC
	u32  length;             // generated by PC
	u32  pub_head_size;      // the size of boot_file_head_t
	u8   pub_head_vsn[4];    // the version of boot_file_head_t
	u8   file_head_vsn[4];   // the version of boot0_file_head_t or boot1_file_head_t
	u8   Boot_vsn[4];        // Boot version
	u8   eGON_vsn[4];        // eGON version
	u8   platform[8];        // platform information
} boot_file_head_t;

typedef struct brom_file_head
{
	u32  jump_instruction;   // one intruction jumping to real code
	u8   magic[8];           // ="eGON.BRM",  not C-style string.
	u32  length;             // generated by PC
	u8   Boot_vsn[4];        // Boot version
	u8   eGON_vsn[4];        // eGON version
	u8   platform[8];        // platform information
} brom_file_head_t;

typedef struct _boot_dram_para_t {
    __u32 dram_baseaddr;
    __u32 dram_clk;
    __u32 dram_type;
    __u32 dram_rank_num;
    __u32 dram_chip_density;
    __u32 dram_io_width;
    __u32 dram_bus_width;
    __u32 dram_cas;
    __u32 dram_zq;
    __u32 dram_odt_en;
    __u32 dram_size;
    __u32 dram_tpr0;
    __u32 dram_tpr1;
    __u32 dram_tpr2;
    __u32 dram_tpr3;
    __u32 dram_tpr4;
    __u32 dram_tpr5;
    __u32 dram_emr1;
    __u32 dram_emr2;
    __u32 dram_emr3;
} boot_dram_para_t;

typedef struct _normal_gpio_cfg {
    __u8 port;
    __u8 port_num;
    __u8 mul_sel;
    __u8 pull;
    __u8 drv_level;
    __u8 data;
    __u8 reserved[2];
} normal_gpio_cfg;

typedef struct _boot0_private_head_t {
    __u32 prvt_head_size;
    char prvt_head_vsn[4];
    boot_dram_para_t dram_para;
    __s32 uart_port;
    normal_gpio_cfg uart_ctrl[2];
    __s32 enable_jtag;
    normal_gpio_cfg jtag_gpio[5];
    normal_gpio_cfg storage_gpio[32];
    __u8 storage_data[256];
} boot0_private_head_t;

typedef struct _boot0_file_head_t {
    boot_file_head_t boot_head;
    boot0_private_head_t prvt_head;
} boot0_file_head_t;

typedef struct _boot_core_para_t {
    __u32 user_set_clock;
    __u32 user_set_core_vol;
    __u32 vol_threshold;
} boot_core_para_t;

typedef struct _boot1_private_head_t {
    __u32 prvt_head_size;
    __u8 prvt_head_vsn[4];
    __s32 uart_port;
    normal_gpio_cfg uart_ctrl[2];
    boot_dram_para_t dram_para;
    char script_buf[32768];
    boot_core_para_t core_para;
    __s32 twi_port;
    normal_gpio_cfg twi_ctrl[2];
    __s32 debug_enable;
    __s32 hold_key_min;
    __s32 hold_key_max;
    __u32 work_mode;
    __u32 storage_type;
    normal_gpio_cfg storage_gpio[32];
    __u8 storage_data[256];
} boot1_private_head_t;

typedef struct _boot1_file_head_t {
    boot_file_head_t boot_head;
    boot1_private_head_t prvt_head;
} boot1_file_head_t;

/* STORAGE DATA on SD loaders */
typedef struct _boot_sdcard_info_t {
    __s32 card_ctrl_num;
    __s32 boot_offset;
    __s32 card_no[4];
    __s32 speed_mode[4];
    __s32 line_sel[4];
    __s32 line_count[4];
} boot_sdcard_info_t;

#define BROM_MAGIC                     "eGON.BRM"
#define BOOT0_MAGIC                     "eGON.BT0"
#define BOOT1_MAGIC                     "eGON.BT1"

union {
	boot_file_head_t boot;
	boot0_file_head_t boot0;
	boot1_file_head_t boot1;
	brom_file_head_t brom;
} boot_hdr;

typedef enum {
	ALLWINNER_UNKNOWN_LOADER=0,
	ALLWINNER_SD_LOADER,
	ALLWINNER_NAND_LOADER
} loader_type;

void fail(char *msg) {
	perror(msg);
	exit(1);
}

void pprintf(void *addr, const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	printf("%8x:\t", (unsigned)((char *)addr - (char *)&boot_hdr));
	vprintf(fmt, ap);
	va_end(ap);
}

void print_brom_file_head(brom_file_head_t *hdr)
{
	pprintf(&hdr->magic,		"Magic     : %.8s\n", hdr->magic);
	pprintf(&hdr->length,		"Length    : %u\n", hdr->length);
	pprintf(&hdr->Boot_vsn,		"BOOT ver  : %.4s\n", hdr->Boot_vsn);
	pprintf(&hdr->eGON_vsn,		"eGON ver  : %.4s\n", hdr->eGON_vsn);
	pprintf(&hdr->platform,		"Chip?     : %.8s\n", hdr->platform);
}

void print_boot_file_head(boot_file_head_t *hdr)
{
	pprintf(&hdr->magic,		"Magic     : %.8s\n", hdr->magic);
	pprintf(&hdr->length,		"Length    : %u\n", hdr->length);
	pprintf(&hdr->pub_head_size,	"HSize     : %u\n", hdr->pub_head_size);
	pprintf(&hdr->pub_head_vsn,	"HEAD ver  : %.4s\n", hdr->pub_head_vsn);
	pprintf(&hdr->file_head_vsn,	"FILE ver  : %.4s\n", hdr->file_head_vsn);
	pprintf(&hdr->Boot_vsn,		"BOOT ver  : %.4s\n", hdr->Boot_vsn);
	pprintf(&hdr->eGON_vsn,		"eGON ver  : %.4s\n", hdr->eGON_vsn);
	pprintf(&hdr->platform,		"platform  : %c%c%c%c%c%c%c%c\n", hdr->platform[0], hdr->platform[1], hdr->platform[2], hdr->platform[3], hdr->platform[4], hdr->platform[5], hdr->platform[6], hdr->platform[7]);
}

void print_boot_dram_para(boot_dram_para_t *dram)
{
	pprintf(&dram->dram_baseaddr,	"DRAM base : %p\n", (void *)(uintptr_t)dram->dram_baseaddr);
	pprintf(&dram->dram_clk,	"DRAM clk  : %d\n", dram->dram_clk);
	pprintf(&dram->dram_type,	"DRAM type : %d\n", dram->dram_type);
	pprintf(&dram->dram_rank_num,	"DRAM rank : %d\n", dram->dram_rank_num);
	pprintf(&dram->dram_chip_density,"DRAM den  : %d\n", dram->dram_chip_density);
	pprintf(&dram->dram_io_width,	"DRAM iow  : %d\n", dram->dram_io_width);
	pprintf(&dram->dram_bus_width,	"DRAM busw : %d\n", dram->dram_bus_width);
	pprintf(&dram->dram_cas,	"DRAM cas  : %d\n", dram->dram_cas);
	pprintf(&dram->dram_zq,		"DRAM zq   : %d\n", dram->dram_zq);
	pprintf(&dram->dram_odt_en,	"DRAM odt  : 0x%x\n", dram->dram_odt_en);
	pprintf(&dram->dram_size,	"DRAM size : %d\n", dram->dram_size);
	pprintf(&dram->dram_tpr0,	"DRAM tpr0 : 0x%x\n", dram->dram_tpr0);
	pprintf(&dram->dram_tpr1,	"DRAM tpr1 : 0x%x\n", dram->dram_tpr1);
	pprintf(&dram->dram_tpr2,	"DRAM tpr2 : 0x%x\n", dram->dram_tpr2);
	pprintf(&dram->dram_tpr3,	"DRAM tpr3 : 0x%x\n", dram->dram_tpr3);
	pprintf(&dram->dram_tpr4,	"DRAM tpr4 : 0x%x\n", dram->dram_tpr4);
	pprintf(&dram->dram_tpr5,	"DRAM tpr5 : 0x%x\n", dram->dram_tpr5);
	pprintf(&dram->dram_emr1,	"DRAM emr1 : 0x%x\n", dram->dram_emr1);
	pprintf(&dram->dram_emr2,	"DRAM emr2 : 0x%x\n", dram->dram_emr2);
	pprintf(&dram->dram_emr3,	"DRAM emr3 : 0x%x\n", dram->dram_emr3);
}

void print_normal_gpio_cfg(normal_gpio_cfg *gpio, int count)
{
	int i;
	for (i = 0; i < count; i++) {
		if (gpio[i].port)
			pprintf(&gpio[i], " GPIO %d   : port=%c%d, sel=%d, pull=%d, drv=%d, data=%d, reserved=%02x,%02x\n", i, 'A'+gpio[i].port-1, gpio[i].port_num, gpio[i].mul_sel, gpio[i].pull, gpio[i].drv_level, gpio[i].data, gpio[i].reserved[0], gpio[i].reserved[1]);
	}
}

void print_boot_sdcard_info(boot_sdcard_info_t *info)
{
	pprintf(&info->card_ctrl_num,	" CARD Ctrl Num: %d\n", info->card_ctrl_num);
	pprintf(&info->boot_offset,	" BOOT Offset: %08x\n", info->boot_offset);

	for (int i = 0; i < 4; i++) {
		if (info->card_no[i] == -1)
			continue;
		pprintf(&info->card_no[i],    " CARD No  : %d (%d)\n", info->card_no[i], i);
		pprintf(&info->speed_mode[i], "  Speed   : %d\n", info->speed_mode[i]);
		pprintf(&info->line_sel[i],   "  Line sel: %d\n", info->line_sel[i]);
		pprintf(&info->line_count[i], "  Line cnt: %d\n", info->line_count[i]);
	}
}

void print_boot0_private_head(boot0_private_head_t *hdr, loader_type type)
{
	pprintf(&hdr->prvt_head_size,	"FHSize    : %u\n", hdr->prvt_head_size);
	pprintf(&hdr->prvt_head_vsn,	"FILE ver  : %.4s\n", hdr->prvt_head_vsn);
	print_boot_dram_para(&hdr->dram_para);
	pprintf(&hdr->uart_port,	"UART port : %d\n", hdr->uart_port);
	print_normal_gpio_cfg(hdr->uart_ctrl, 2);
	pprintf(&hdr->enable_jtag,	"JTAG en   : %d\n", hdr->enable_jtag);
	print_normal_gpio_cfg(hdr->jtag_gpio, 5);
	pprintf(&hdr->storage_gpio,	"STORAGE   :\n");
	print_normal_gpio_cfg(hdr->storage_gpio, 32);
	int i = 0;
	if (type == ALLWINNER_SD_LOADER) {
		print_boot_sdcard_info((boot_sdcard_info_t *)hdr->storage_data);
		i = sizeof(boot_sdcard_info_t);
	}
	for (int n = 0; i < 256; i++, n++) {
		if (n % 16 == 0) {
			if (n) {
				printf("\n");
			}
			pprintf(&hdr->storage_data[i], " DATA %02x  :", i);
		}
		printf(" %02x", hdr->storage_data[i]);
	}
	printf("\n");
}

void print_script(void *UNUSED(script))
{
}

void print_core_para(boot_core_para_t *core)
{
	pprintf(&core->user_set_clock,	"Set Clock : %d\n", core->user_set_clock);
	pprintf(&core->user_set_core_vol, "Set Core Vol: %d\n", core->user_set_core_vol);
	pprintf(&core->vol_threshold,	"Vol Threshold: %d\n", core->vol_threshold);
}

void print_boot1_private_head(boot1_private_head_t *hdr, loader_type type)
{
	pprintf(&hdr->prvt_head_size,	"FHSize    : %u\n", hdr->prvt_head_size);
	pprintf(&hdr->prvt_head_vsn,	"FILE ver  : %.4s\n", hdr->prvt_head_vsn);
	pprintf(&hdr->uart_port,	"UART port : %d\n", hdr->uart_port);
	print_normal_gpio_cfg(hdr->uart_ctrl, 2);
	print_boot_dram_para(&hdr->dram_para);
	print_script(&hdr->script_buf);
	print_core_para(&hdr->core_para);
	pprintf(&hdr->twi_port,		"TWI port  : %d\n", hdr->twi_port);
	print_normal_gpio_cfg(hdr->twi_ctrl, 2);
	pprintf(&hdr->debug_enable,	"Debug     : %d\n", hdr->debug_enable);
	pprintf(&hdr->hold_key_min,	"Hold key min : %d\n", hdr->hold_key_min);
	pprintf(&hdr->hold_key_max,	"Hold key max : %d\n", hdr->hold_key_max);
	pprintf(&hdr->work_mode,	"Work mode : %d\n", hdr->work_mode);
	pprintf(&hdr->storage_type,	"STORAGE   :\n");
	pprintf(&hdr->storage_type,	" type   : %d\n", hdr->storage_type);
	print_normal_gpio_cfg(hdr->storage_gpio, 32);
	int i = 0;
	if (type == ALLWINNER_SD_LOADER) {
		print_boot_sdcard_info((boot_sdcard_info_t *)hdr->storage_data);
		i = sizeof(boot_sdcard_info_t);
	}
	for (int n = 0; i < 256; i++, n++) {
		if (n % 16 == 0) {
			if (n) {
				printf("\n");
			}
			pprintf(&hdr->storage_data[i], " DATA %02x  :", i);
		}
		printf(" %02x", hdr->storage_data[i]);
	}
	printf("\n");
}

void print_boot0_file_head(boot0_file_head_t *hdr, loader_type type)
{
	print_boot_file_head(&hdr->boot_head);
	if (strncmp((char *)hdr->boot_head.file_head_vsn, "1230", 4) == 0)
		print_boot0_private_head(&hdr->prvt_head, type);
	else
		printf("Unknown boot0 header version\n");
}

void print_boot1_file_head(boot1_file_head_t *hdr, loader_type type)
{
	print_boot_file_head(&hdr->boot_head);
	if (strncmp((char *)hdr->boot_head.file_head_vsn, "1230", 4) == 0)
		print_boot1_private_head(&hdr->prvt_head, type);
	else
		printf("Unknown boot0 header version\n");
}

static void usage(const char *cmd)
{
	puts("sunxi-bootinfo " VERSION "\n");
	printf("Usage: %s [<filename>]\n", cmd);
	printf("       With no <filename> given, will read from stdin instead\n");
}

int main(int argc, char * argv[])
{
	FILE *in = stdin;
	loader_type type = ALLWINNER_UNKNOWN_LOADER;
	if (argc > 1 && strcmp(argv[1], "--type=sd") == 0) {
		type = ALLWINNER_SD_LOADER;
		argc--;
		argv++;
	}
	if (argc > 1 && strcmp(argv[1], "--type=nand") == 0) {
		type = ALLWINNER_NAND_LOADER;
		argc--;
		argv++;
	}
	if (argc > 1) {
		in = fopen(argv[1], "rb");
		if (!in) {
			if (*argv[1] == '-')
				usage(argv[0]);
			fail("open input");
		}
	}
	int len;

	len = fread(&boot_hdr, 1, sizeof(boot_hdr), in);
	if (len < (int)sizeof(boot_file_head_t))
		fail("Failed to read header:");
	if (strncmp((char *)boot_hdr.boot.magic, BOOT0_MAGIC, strlen(BOOT0_MAGIC)) == 0) {
		print_boot0_file_head(&boot_hdr.boot0, type);
	} else if (strncmp((char *)boot_hdr.boot.magic, BOOT1_MAGIC, strlen(BOOT1_MAGIC)) == 0) {
		print_boot1_file_head(&boot_hdr.boot1, type);
	} else if (strncmp((char *)boot_hdr.boot.magic, BROM_MAGIC, strlen(BROM_MAGIC)) == 0) {
		print_brom_file_head(&boot_hdr.brom);
	} else {
		fail("Invalid magic\n");
	}

	return 0;
}
