/*
 * diag_epi3.c: Matthew Millman
 *
 * Common routines for programming EPI3 personalities
 */
 
/* 
 * Header files
 */
 
#include <common.h>
#include <config.h>
#include <command.h>
#include <epi3.h>
#include <diag.h>
#include <malloc.h>
#include <bootmenu.h>
#include <pci.h>
#include <i2c.h>

#if defined(CONFIG_X210_ATRD)
extern void x210_set_base_flash_page(void);
#endif

#ifndef CONFIG_SYS_NO_FLASH

void diag_epi3_print_personality(epi3Personality *personality)
{
    diag_printf_always("  Version .............. : %u\n", personality->epiVersion); 
    diag_printf_always("  Serial ............... : %s\n", personality->epiSerial);
    diag_printf_always("  Manufacturer ......... : %c\n", ppr3_mfgs[personality->epiManufacturer]);
    diag_printf_always("  Year ................. : %u\n", personality->epiYear);
    diag_printf_always("  Month ................ : %u\n", personality->epiMonth + 1);
    diag_printf_always("  Board ID ............. : %u\n", personality->epiBoardId);
		
    if (personality->epiPcbRev & 0x8000)
    {
        u16 temp;
			
        temp = personality->epiPcbRev - 0x8000;
			
        if (temp < num_proto_names)
        {
			
            diag_printf_always("  PCB Rev .............. : %s\n", ppr3_proto_names[temp]);
        }
        else
        {
            diag_printf_always("  PCB Rev .............. : (too high)\n");
        }
    }
    else
    {	
        u16 temp;
			
        temp = personality->epiPcbRev;
			
        if (temp < num_rev_names)
        {
            diag_printf_always("  PCB Rev .............. : %s\n", ppr3_rev_names[temp]);
        }
        else
        {
            diag_printf_always("  PCB Rev .............. : (too high)\n");
        }	
    }
		
    diag_printf_always("  MAC Addr ............. : %02X%02X%02X%02X%02X%02X\n", 
                       (int)personality->epiMacAddress[0],
                       (int)personality->epiMacAddress[1],
                       (int)personality->epiMacAddress[2],
                       (int)personality->epiMacAddress[3],
                       (int)personality->epiMacAddress[4],
                       (int)personality->epiMacAddress[5]);
		
    diag_printf_always("  844 BOM .............. : %-4s\n", personality->epi844Bom);
		
    diag_printf_always("  OEM .................. : %c\n", ppr3_oems[personality->epiOEM]);
    diag_printf_always("  HW Mod level ......... : %u\n", personality->HWModLevel);
    diag_printf_always("  Checksum ............. : %04X\n", personality->epiChecksum);
}


int diag_epi3_display_baseboard_history(void)
{
    epi3Personality *	personality;
	
    personality  = (epi3Personality *)CONFIG_EPI3_LOCATION;
	
    while ((u32)personality < (CONFIG_EPI3_LOCATION + CONFIG_EPI3_SIZE))
    {
	
        if (personality->epiMagicNumber != 0x55 || personality->epiVersion != 0x01)
        {
            diag_printf_always("\nEnd of EPI3 History\n");
            break;
        }
		
        diag_printf_always("\nEPI3 Information in position %u (%s):\n", 
                           ((u32)personality - CONFIG_EPI3_LOCATION) / sizeof(epi3Personality),
                           personality == epi3_getcurrent() ? "Active" : "Historic");
		
        diag_epi3_print_personality(personality);
		
		
		
        personality++;
    }
	
    return 0;
}



int diag_epi3mk_backend(int argc, char *argv[], epi3Personality *last_personality, epi3Personality *new_personality)
{
    char			macaddrtemp[3];
    int 			temp;
    int			i;

    if (last_personality)
        strncpy(new_personality->epiSerial, last_personality->epiSerial, EPI3_SERIAL_LEN);
    else 
        strncpy(new_personality->epiSerial, argv[1], EPI3_SERIAL_LEN);
	
    if (argc < 3 || !strcmp(argv[2], "-")) { /* Manufacturer code */
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->epiManufacturer = last_personality->epiManufacturer;
    }
    else {
        for (i = 0; i < PPR3_TOTAL_MFGS; i++) {
            if (*argv[2] == ppr3_mfgs[i]) {
				
                new_personality->epiManufacturer = i;
                break;
            }
        }
        if (i >= PPR3_TOTAL_MFGS) {
            diag_printf_always("Error: Invalid manufacturer code\n");
            return 1;
        }
    }
		
    if (argc < 4 || !strcmp(argv[3], "-")) { /* Year of mfg */
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->epiYear = last_personality->epiYear;
    }
    else {
        new_personality->epiYear = simple_strtoul(argv[3], NULL, 10);

        if (new_personality->epiYear > 99) {
            diag_printf_always("Error: Invalid year\n");
            return 1;
        }
    }
	
    if (argc < 5 || !strcmp(argv[4], "-")) { /* Month of mfg */
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->epiMonth = last_personality->epiMonth;
    }
    else {
        if (!simple_strtoul(argv[4], NULL, 10)) {
            diag_printf_always("Error: Can't enter zero as the month\n");
            return 1;
        }
        new_personality->epiMonth = simple_strtoul(argv[4], NULL, 10) - 1;
        if (new_personality->epiMonth > 11) {
            diag_printf_always("Error: Invalid month\n");
            return 1;
        }
    }

    if (argc < 6 || !strcmp(argv[5], "-")) { /* Board ID */
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->epiBoardId = last_personality->epiBoardId;
    }
    else {	
        new_personality->epiBoardId = simple_strtoul(argv[5], NULL, 10);
    }
	
    if (argc < 7 || !strcmp(argv[6], "-")) { /* PCB Rev */
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->epiPcbRev = last_personality->epiPcbRev;
    }
    else
    {	
        int valid = 0;
		
        for (i = 0; i < num_rev_names; i++) {
            if (!strcmp(argv[6], ppr3_rev_names[i])) {
                new_personality->epiPcbRev = i;
                valid = 1;
                break;
            }
        }
        for (i = 0; i < num_proto_names; i++) {
            if (!strcmp(argv[6], ppr3_proto_names[i])) {
                new_personality->epiPcbRev = i;
                new_personality->epiPcbRev |= 0x8000;
                valid = 1;
                break;
            }
        }
        if (!valid) {
            diag_printf_always("Error: Invalid PCB Rev\n");
            return 1;
        }
    } 
	
    if (argc < 8 || !strcmp(argv[7], "-")) { /* MAC Address */
        if (!last_personality)	{
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        memcpy(new_personality->epiMacAddress, last_personality->epiMacAddress, 6);
    }
    else {	
        if (strlen(argv[7]) != 12) {
            diag_printf_always("Error: Bad MAC address\n");
            return 1;
        }
        for (i = 0; i < 12; i++) {
            char c = *(argv[7] + i);
					
            if (!((c >= '0' && c <= '9') ||
                   (c >= 'a' && c <= 'f') ||
                   (c >= 'A' && c <= 'F'))) {
                diag_printf_always("Error: Bad MAC address\n");
                return 1;
                   }
        }
		
        for (i = 0; i < 6; i++) {
            macaddrtemp[0] = *(argv[7] + (i * 2));
            macaddrtemp[1] = *(argv[7] + ((i * 2) + 1));
            macaddrtemp[2] = 0;	
            new_personality->epiMacAddress[i] = simple_strtoul(macaddrtemp, NULL, 16);
        }
		
        if (new_personality->epiMacAddress[0] & 0x01)
        {
            diag_printf_always("Error: Bad MAC address (Address is multicast)\n");
            return 1;
        }
    }
	
    if (argc < 9 || !strcmp(argv[8], "-")) { /* 844 BOM */
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        strcpy(new_personality->epi844Bom, last_personality->epi844Bom);
    }
    else {	
        if (strlen(argv[8]) > 3) {
            diag_printf_always("Error: Bad 844 BOM code\n");
            return 1;
        }
        strcpy(new_personality->epi844Bom, argv[8]);
    }
	
    /* OEM Code */

    if (argc < 10 || !strcmp(argv[9], "-")) {
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->epiOEM = last_personality->epiOEM;
    }
    else {
        for (i = 0; i < PPR3_TOTAL_OEMS; i++) {
            if (*argv[9] == ppr3_oems[i]) {
                new_personality->epiOEM = i;
                break;
            }
        }
        if (i >= PPR3_TOTAL_OEMS) {
            diag_printf_always("Error: Invalid OEM code\n");
            return 1;
        }
    }
	
    /* HW Mod level */

    if (argc < 11 || !strcmp(argv[10], "-")) {
        if (!last_personality) {
            diag_printf_always("Error: No previous personality to get value from\n");
            return 1;
        }
        new_personality->HWModLevel = last_personality->HWModLevel;
    }
    else {
        temp = simple_strtoul(argv[10], NULL, 10);
				
        if (temp > EPI3_P_HW_MODLEV_MAX) {
            diag_printf_always("Error: Invalid HW Mod level\n");
            return 1;
        }
        new_personality->HWModLevel = (u16)(temp);
    }
	
    new_personality->epiChecksum = epi3_checksum(new_personality);
	
    return 0;
}


/**
 * Routine for writing/adding an EPI3 block
 * Part of the AlliedWare+ release system
 * argv[1]:Serial argv[2]:Manufacturer argv[3]:Year argv[4]:Month argv[5]:BoardID
 * argv[6]:PCBRev argv[7]:MAC argv[8]:844Bom argv[9]:820Bom argv[10]:OEM
 * argv[11]:HWModLevel argv[12-]:ChipRevisions
 *
 * @param cmdtp command internal data
 * @param flag command flag
 * @param argc number of arguments supplied to the command
 * @param argv arguments list
 * @return 0 on success, 1 otherwise
 */


int do_epi3_epi3mk(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    epi3Personality * 	new_personality;
    epi3Personality * 	last_personality;
    epi3Personality * 	epi3Block;

    char			buf[256];
	
    last_personality = epi3_getcurrent();
    new_personality = malloc(sizeof(epi3Personality));
    epi3Block = (epi3Personality *)CONFIG_EPI3_LOCATION;
	
    memset(new_personality, 0, sizeof(epi3Personality));

    new_personality->epiVersion = 0x01;
    new_personality->epiMagicNumber = 0x55;
	
    if (argc < 2) {
        diag_printf_always("Error: Too fewer parameters. Type 'help epi3mk' for more information\n");
        goto fail;
    }
	
    if (diag_epi3mk_backend(argc, argv, last_personality, new_personality))
        return 1;
	
    while (epi3Block->epiMagicNumber != 0xFF) {
		
        if (epi3_dataok(epi3Block)) {
            diag_printf_always("Error: EPI3 area corrupt\n");
            goto fail;
        }
		
        /* Try the next one */
        epi3Block++;
        /* Make sure we haven't reached the end */
        if ((u32)epi3Block >= CONFIG_EPI3_LOCATION + CONFIG_EPI3_SIZE) {
            /* No more slots */ 
            diag_printf_always("Error: Out of slots in EPI3 block (might need to erase block and start again?\n");
            goto fail;
        }
    }
	

    sprintf(buf, "prot off %08X %08X", CONFIG_EPI3_LOCATION, CONFIG_EPI3_LOCATION + CONFIG_EPI3_SIZE - 1);
    run_command(buf, 0);
	
    sprintf(buf, "cp.b %08X %08X %08X", (u32)new_personality, (u32)epi3Block, sizeof(epi3Personality));
    run_command(buf, 0);
	
    sprintf(buf, "prot on %08X %08X", CONFIG_EPI3_LOCATION, CONFIG_EPI3_LOCATION + CONFIG_EPI3_SIZE - 1);
    run_command(buf, 0);
	
    diag_printf_always("Successfully wrote EPI3 information to flash in position: %u\n", 
                       ((u32)epi3Block - CONFIG_EPI3_LOCATION) / sizeof(epi3Personality));

    if (epi3_dataok(epi3Block)) {
        diag_printf_always("Error: Written EPI3 block is not valid. EPI3 area is corrupt\n");
        goto fail;
    }
		
	
    free(new_personality);
	
    return 0;
	
fail:
        free(new_personality);
	return 1;
}

/**
 * Routine for showing the EPI3 block
 * Part of the AlliedWare+ release system
 *
 * @param cmdtp command internal data
 * @param flag command flag
 * @param argc number of arguments supplied to the command
 * @param argv arguments list
 * @return 0 on success, 1 otherwise
 */
  
int do_epi3_epi3show(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    diag_epi3_display_baseboard_history();
	
    return 0;
}

U_BOOT_CMD(
        epi3show, 1, 1, do_epi3_epi3show,
	"Show current EPI3 information",
        ""
          );

U_BOOT_CMD(
        epi3mk, 11, 2, do_epi3_epi3mk,
	"Make an EPI3 block",
        "[Serial] [Mfg] [Year] [Month] [BoardID] [PCBRev] [MAC] [844Bom] [OEM] [HWModLevel]\n\n"
                "Standard command usage:\n\n"
                "\tepi3mk 123456789 A 06 06 270 X2 001122334455 A U 1\n\n"
                "The 'Serial' parameter is required but ignored. The only case where this parameter is observed is when the\n"
                "personality area is blank (factory first program).\n\n"
                "If a parameter is specified as a dash '-' character, then the value is copied from the last valid personality.\n"
                "There is no need to insert the '-' character in place of values after any of those you wish to modify.\n"
                "For example, the command:\n\n"
                "\tepi3mk - - - - 270\n\n"
                "Will make a new personality, with all attributes unchanged apart from the specified BoardID."
          );


#endif /* CONFIG_SYS_NO_FLASH */

/*
 * increments a mac address handling wrapping
 */
static __attribute__((unused)) void mac_addr_inc(char mac[6])
{
	int i;

	/* terminating the loop at i == 2 stops us from going outside of our
	 * allocated OUI range
	 */
	for(i = 5 ; i > 2; i--) {
		if(mac[i] == 0xff) {
			mac[i] = 0;
		} else {
			mac[i] ++;
			break;
		}
	}
}

/**
 * Routine for getting the MAC address out of the EPI3 information, then setting it
 *
 */

void epi3_setmacaddr(void)
{
    epi3Personality * personality;
#ifdef CONFIG_ETH1ADDR
    char mac_copy[6];
#endif
	
    personality = epi3_getcurrent();
	
    if (!personality)
    {
        fprintf(stderr, "Epi3:  Failed to get MAC address out of EPI3 information.\n");
        fprintf(stderr, "Epi3:  This device may not have any valid personalities.\n");
        return;
    }

    eth_setenv_enetaddr("ethaddr", personality->epiMacAddress);
    printf("Epi3:  Station address is: %s\n", getenv("ethaddr"));

#ifdef CONFIG_ETH1ADDR
    memcpy(mac_copy, personality->epiMacAddress, 6);
    mac_addr_inc(mac_copy);
    eth_setenv_enetaddr("eth1addr", mac_copy);
    printf("Epi3:  Second station address is: %s\n", getenv("eth1addr"));
#endif
}


/**
 * Routine for Calculating the checksum of an EPI3 block
 *
 * @param epi3Address Address of EPI3 information to calculate the checksum of
 *
 */

u16 epi3_checksum(epi3Personality *epi3Address)
{
    u32		result;
    u32		nBytes;
    u32		byteCount;

    /* Checksum up to where the checksum is stored */
    nBytes = (u8 *)&(epi3Address->epiChecksum) - (u8 *)epi3Address;

    /* Simple "add the bytes and complement" checksum */
    result = 0;
	
    for (byteCount = 0; byteCount < nBytes; byteCount++)
    {
        result += *((u8 *)epi3Address + byteCount);
    }
    /* Complement */
    result = ~result;

    /* Checksum is stored as 16-bit, so mask top word - shouldn't be necessary */
    return (result & 0xffff);

}

/**
 * Routine for getting the current EPI3 block (if there is one)
 *
 * @return A valid EPI3 Block, if there is one, otherwise NULL.
 *
 */
 
epi3Personality *epi3_getcurrent(void)
{
    epi3Personality * personality;
	
    personality = (epi3Personality *)CONFIG_EPI3_LOCATION;
	
    while ((u32)personality < (CONFIG_EPI3_LOCATION + CONFIG_EPI3_SIZE))
    {
	
        if (epi3_dataok(personality) == -1)
            break;
		
        personality++;
    }
	
    personality--;
	
	
    if (personality < (epi3Personality *)CONFIG_EPI3_LOCATION)
        return NULL;
	
    return personality;
}


/**
 * Routine for verifying the checksum of an EPI3 block
 *
 * @param epi3Address Address of EPI3 information to verify the checksum of
 * @return 0 if successful, -1 on failure
 *
 */
int epi3_dataok(epi3Personality * epi3Address)
{
    u32		result;
    u32		nBytes;
    u32		byteCount;

    /* Checksum up to where the checksum is stored */
#if defined(CONFIG_X210_ATRD)
    x210_set_base_flash_page();
#endif

    nBytes = (u8 *)(&epi3Address->epiChecksum) - (u8 *)epi3Address;

    /* Simple "add the bytes and complement" checksum */
    result = 0;
    for (byteCount = 0; byteCount < nBytes; byteCount++)
    {
        result += *((u8 *)epi3Address + byteCount);
    }
    /* Complement */
    result = ~result;

    /* Checksum is stored as 16-bit, so mask top word - shouldn't be necessary */
    result &= 0xffff;

    if ((result == epi3Address->epiChecksum) &&
         (epi3Address->epiMagicNumber == 0x55))
        return 0;
    else
        return -1;

    return -1;
}
