/*
 * dfu-programmer
 *
 * $Id: commands.c,v 1.6 2006/06/25 00:01:37 schmidtw Exp $
 *
 * 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 <stdio.h>
#include <string.h>

#include "config.h"
#include "commands.h"
#include "arguments.h"
#include "intel_hex.h"
#include "atmel.h"


static int execute_erase( struct usb_dev_handle *device,
                          int interface,
                          struct programmer_arguments args )
{
    int result = 0;

    if( 2 < debug ) {
        fprintf( stderr, "%s: erase %d bytes\n", __FUNCTION__,
                         args.memory_size );
    }

    result = atmel_erase_flash( device, interface, ATMEL_ERASE_ALL );
    if( 0 != result )
        return result;

    return atmel_blank_check( device, interface, 0, args.top_memory_address );
}


static int execute_flash( struct usb_dev_handle *device,
                          int interface,
                          struct programmer_arguments args )
{
    char *hex_data = NULL;
    int   usage = 0;
    int   retval = -1;
    int   result = 0;
    char  *buffer = NULL;
    int i;

    buffer = (char *) malloc( args.memory_size );
    if( NULL == buffer ) {
        fprintf( stderr, "Request for %d bytes of memory failed.\n",
                 args.memory_size );
        goto error;
    }

    memset( buffer, 0, args.memory_size );

    hex_data = intel_hex_to_buffer( args.com_flash_data.file,
                                    args.memory_size, 0xff, &usage );
    if( NULL == hex_data ) {
        fprintf( stderr,
                 "Something went wrong with creating the memory image.\n" );
        goto error;
    }

    if( 2 < debug ) {
        fprintf( stderr, "%s: write %d/%d bytes\n", __FUNCTION__,
                         usage, args.memory_size );
    }

    result = atmel_flash( device, interface, 0, args.top_memory_address,
                          hex_data );

    if( args.memory_size != result ) {
        fprintf( stderr, "Error while flashing. (%d)\n", result );
        goto error;
    }

    if( 0 == args.com_flash_data.suppress_validation ) {
        fprintf( stderr, "Validating...\n" );

        result = atmel_read_flash( device, interface, 0,
                                   args.top_memory_address, buffer,
                                   args.memory_size );

        if( args.memory_size != result ) {
            fprintf( stderr, "Error while validating.\n" );
            goto error;
        }

        if( 0 != memcmp(hex_data, buffer, args.memory_size) ) {
            fprintf( stderr, "Image did not validate.\n" );
            goto error;
        }
    }

    if( 0 == args.quiet ) {
        fprintf( stderr, "%d bytes used (%.02f%%)\n", usage,
                         ((float)(usage*100)/(float)(args.top_memory_address)) );
    }

    retval = 0;

error:
    if( NULL != buffer ) {
        free( buffer );
        buffer = NULL;
    }

    if( NULL != hex_data ) {
        free( hex_data );
        hex_data = NULL;
    }

    return retval;
}


static int execute_start_app( struct usb_dev_handle *device,
                              int interface,
                              struct programmer_arguments args )
{
    return atmel_start_app( device, interface );
}


static int execute_get( struct usb_dev_handle *device,
                        int interface,
                        struct programmer_arguments args )
{
    struct atmel_device_info info;
    char *message = NULL;
    short value = 0;
    int status;
    int controller_error = 0;

    if( device_8051 == args.device_type ) {
        status = atmel_read_config_8051( device, interface, &info );
    } else {
        status = atmel_read_config_avr( device, interface, &info );
    }

    if( 0 != status ) {
        fprintf( stderr, "Error reading %s config information.\n",
                         args.device_type_string );
        return status;
    }

    switch( args.com_get_data.name ) {
        case get_bootloader:
            value = info.bootloaderVersion;
            message = "Bootloader Version";
            break;
        case get_ID1:
            value = info.bootID1;
            message = "Device boot ID 1";
            break;
        case get_ID2:
            value = info.bootID2;
            message = "Device boot ID 2";
            break;
        case get_BSB:
            value = info.bsb;
            message = "Boot Status Byte";
            if( device_8051 != args.device_type ) {
                controller_error = 1;
            }
            break;
        case get_SBV:
            value = info.sbv;
            message = "Software Boot Vector";
            if( device_8051 != args.device_type ) {
                controller_error = 1;
            }
            break;
        case get_SSB:
            value = info.ssb;
            message = "Software Security Byte";
            if( device_8051 != args.device_type ) {
                controller_error = 1;
            }
            break;
        case get_EB:
            value = info.eb;
            message = "Extra Byte";
            if( device_8051 != args.device_type ) {
                controller_error = 1;
            }
            break;
        case get_manufacturer:
            value = info.manufacturerCode;
            message = "Manufacturer Code";
            break;
        case get_family:
            value = info.familyCode;
            message = "Family Code";
            break;
        case get_product_name:
            value = info.productName;
            message = "Product Name";
            break;
        case get_product_rev:
            value = info.productRevision;
            message = "Product Revision";
            break;
        case get_HSB:
            value = info.hsb;
            message = "Hardware Security Byte";
            break;
    }

    if( 0 != controller_error ) {
        fprintf( stderr, "%s requires 8051 based controller\n",
                         message );
        return -1;
    }

    if( value < 0 ) {
        fprintf( stderr, "The requested device info is unavailable.\n" );
        return -2;
    }

    fprintf( stdout, "%s%s0x%02x (%d)\n", 
             ((0 == args.quiet) ? message : ""),
             ((0 == args.quiet) ? ": " : ""),
             value, value );
    return 0;
}


static int execute_dump( struct usb_dev_handle *device,
                         int interface,
                         struct programmer_arguments args )
{
    int i = 0;
    char  *buffer = NULL;

    buffer = (char *) malloc( (args.memory_size) );
    if( NULL == buffer ) {
        fprintf( stderr, "Request for %d bytes of memory failed.\n",
                 args.memory_size );
        goto error;
    }

    if( 2 < debug ) {
        fprintf( stderr, "%s: dump %d bytes\n", __FUNCTION__,
                         args.memory_size );
    }

    if( args.memory_size != atmel_read_flash(device, interface, 0,
                                  args.top_memory_address, buffer,
                                  args.memory_size) )
    {
        fprintf( stderr, "Error while validating.\n" );
        return -1;
    }

    for( i = 0; i < args.memory_size; i++ ) {
        fprintf( stdout, "%c", buffer[i] );
    }

    fflush( stdout );

error:
    if( NULL != buffer ) {
        free( buffer );
        buffer = NULL;
    }

    return 0;
}


static int execute_configure( struct usb_dev_handle *device,
                              int interface,
                              struct programmer_arguments args )
{
    int value = args.com_configure_data.value;
    int name = args.com_configure_data.name;

    if( device_8051 != args.device_type ) {
        fprintf( stderr, "target doesn't support configure operation.\n" );
        return -1;
    }

    if( (0xff & value) != value ) {
        fprintf( stderr, "Value to configure must be in range 0-255.\n" );
        return -1;
    }

    if( 0 != atmel_set_config(device, interface, name, value) )
    {
        fprintf( stderr, "Configuration set failed.\n" );
        return -1;
    }

    return 0;
}


static int execute_dnload( struct usb_dev_handle *device,
			   int interface,
			   struct programmer_arguments args )
{
    if( device_SAM7 != args.device_type || device_NEO1973 != args.device_type) {
    	fprintf( stderr, "target doesn't support dfu download operation.\n" );
	return -1;
    }

    if ( 0 > sam7dfu_do_dnloa(device, interface, 256, /* FIXME */
    			       args.com_flash_data.file) )
    {
	fprintf( stderr, "Download failed.\n" );
	return -1;
    }
}


static int execute_upload( struct usb_dev_handle *device,
			   int interface,
			   struct programmer_arguments args )
{
    if( device_SAM7 != args.device_type || device_NEO1973 != args.device_type) {
    	fprintf( stderr, "target doesn't support dfu upload operation.\n" );
	return -1;
    }

    if ( 0 > sam7dfu_do_upload(device, interface, 256, /* FIXME */
    			       args.com_flash_data.file) )
    {
	fprintf( stderr, "Download failed.\n" );
	return -1;
    }
}


int execute_command( struct usb_dev_handle *device,
                     int interface,
                     struct programmer_arguments args )
{
    switch( args.command ) {
        case com_erase:
            return execute_erase( device, interface, args );
        case com_flash:
            return execute_flash( device, interface, args );
        case com_start_app:
            return execute_start_app( device, interface, args );
        case com_get:
            return execute_get( device, interface, args );
        case com_dump:
            return execute_dump( device, interface, args );
        case com_configure:
            return execute_configure( device, interface, args );
	case com_dnload:
	    return execute_dnload( device, interface, args );
	case com_upload:
	    return execute_upload( device, interface, args );
        default:
            fprintf( stderr, "Not supported at this time.\n" );
    }

    return -1;
}
