/*This file is prepared for Doxygen automatic documentation generation.*/
//! \file *********************************************************************
//!
//! \brief This file contains the function declarations for usb host dfu task application
//!
//! - Compiler:           IAR EWAVR and GNU GCC for AVR
//! - Supported devices:  AT90USB1287, AT90USB1286, AT90USB647, AT90USB646
//!
//! \author               Atmel Corporation: http://www.atmel.com \n
//!                       Support and FAQ: http://support.atmel.no/
//!
//! ***************************************************************************

/* Copyright (c) 2009 Atmel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an Atmel
 * AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY AND
 * SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
 */

//_____  I N C L U D E S ___________________________________________________

#include "config.h"
#include "conf_usb.h"

#include "modules/file_system/fat.h"
#include "modules/file_system/fs_com.h"
#include "modules/file_system/navigation.h"
#include "modules/file_system/file.h"
#include "modules/file_system/nav_utils.h"

#include "host_dfu_task.h"
#include "modules/usb/host_chap9/usb_host_task.h"
#include "modules/usb/host_chap9/usb_host_enum.h"
#include "lib_mcu/usb/usb_drv.h"
#include "lib_mcu/flash/flash.h"


//_____ M A C R O S ________________________________________________________


#ifndef LOG_STR_CODE
#define LOG_STR_CODE(str)
#else
U8 code log_dfu_connect[]="DFU Connected";
#endif

//_____ D E C L A R A T I O N S ____________________________________________


U8  dfu_connected=0;

static   U8 buf[MAX_DATA_PER_RECORD];
static   U8 prog_buf[MAX_DATA_PER_RECORD+FLASH_PAGE_SIZE];
static   U8 check_buf[MAX_DATA_PER_RECORD+FLASH_PAGE_SIZE];
static   U8 dfu_status;

static   U16 end_addr;
static   U16 start_addr;
static   U8 pgm_index=0;
static   U8 pgm_nb_data=0;


U8 code firmware_name[]=FIRMWARE_NAME;

//!
//! @brief This function initializes the Host dfu application
//!
//! @param none
//!
//! @return none
void host_dfu_task_init(void)
{
   Joy_init();
   Leds_init();
   dfu_connected=0;
}

//! @brief This function manages the HOST dfu application
//!
//!
//! @param none
//!
//! @return none
void host_dfu_task(void)
{
   if(Is_host_ready())    //Enumeration successfull, device is operationnal
   {
      if(Is_new_device_connection_event())        //New device connected and enumerated...
      {
         // Check host is connected to ATMEL bootloader
         if(Get_VID()==ATMEL_VID && Get_PID()==AT90DFU_PID)
         {
            LOG_STR_CODE(log_dfu_connect);
            dfu_connected=1;
            Led2_on();
         }
         else
         {
            dfu_connected=0;
         }
      }
      if(dfu_connected)
      {
         if(Is_joy_up()||Is_joy_left())
         {
            nav_reset();
            nav_drive_set(0);
            if(nav_partition_mount())
            {
               if(goto_code_name((U8 code *)firmware_name,FALSE,FALSE))
               {
                  Led0_on();
                  Dfu_erase();
                  Led0_off();
                  dfu_load_hex();
               }
            }
         }
      }
   }

   //Device disconnection...
   if(Is_device_disconnection_event())
   {
      dfu_connected=0;
      Led2_off();
   }
}

//! @brief This function program an intel hexfile to the on-chip flash
//! memory of an AT90USXxxx connected in DFU bootloader.
//!
//! @param none
//!
//! @return U8 status (TRUE=OK, FALSE=KO)
U8 dfu_load_hex(void)
{
   U8 record_type;
   U16 addr;
   U8 nb_data;
   U8 i;
   U8 stop=0;

   dfu_status=TRUE;
   file_open(FOPEN_MODE_R); // Open hex firmware file
   while (file_eof()==FALSE)
   {
      i=file_getc();
      while(i!=RECORD_MARK)
      {
         i=file_getc();
         if(file_eof())
         {
            stop=1;
            break;
         }
      }
      if(stop) break;
      // Build the hex record information from file
      nb_data=ascii_to_bin(file_getc());
      nb_data=nb_data<<4;
      nb_data+=ascii_to_bin(file_getc());
      addr=ascii_to_bin(file_getc());
      addr=addr<<4;
      addr+=ascii_to_bin(file_getc());
      addr=addr<<4;
      addr+=ascii_to_bin(file_getc());
      addr=addr<<4;
      addr+=ascii_to_bin(file_getc());
      record_type=ascii_to_bin(file_getc());
      record_type=record_type<<4;
      record_type+=ascii_to_bin(file_getc());
      for(i=0;i<nb_data;i++)
      {
         buf[i]=ascii_to_bin(file_getc());
         buf[i]=buf[i]<<4;
         buf[i]+=ascii_to_bin(file_getc());
      }
      // Decode record type
      switch(record_type)
      {
         case DATA_RECORD:
            if(addr!=end_addr+1 && pgm_index!=0) //None consecutive addr, first flush previous buffer
            {
               dfu_flush_prog();
            }
            // Add new data to prog buffer
            for(i=0;i<nb_data;i++)
            {
               prog_buf[pgm_index]=buf[i];
               pgm_index++;
            }
            // Update nb of data to load
            pgm_nb_data+=nb_data;
            // Compute last byte add to load
            if(pgm_nb_data>nb_data)
            {  // Data exist in buffer
               end_addr=end_addr+nb_data;
            }
            else // first load
            {
               end_addr=addr+nb_data-1;
               start_addr=addr;
            }
            //If enough data flush prog buffer
            if(pgm_index>=10*MAX_DATA_PER_RECORD)
            {
               dfu_flush_prog();
            }
            break;
         case PAGE_RECORD:
            if(pgm_index) // Prog buffer not empty ?
            {
               dfu_flush_prog();  // Then flush it
            }
            Dfu_set_page(buf[1]); // Send page change frame
         default:  //Ignore all other record types
            break;
      }
   }
   // Remaining data in buffer before quit?
   if(pgm_index>0)
   {
      dfu_flush_prog();
   }
   file_close();
   Led0_off();
   return dfu_status;
}

//! @brief This function sends a dfu USB programming frame to
//! an AT90USXxxx DFU bootloader.
//!
//! @param start: start Address
//! @param end: end   Address
//! @param *buf: pointer to data buffer
//!
//! @return none
void dfu_prog(U16 start,U16 end,U8 *buf)
{
   U8 i;
   U16 j;
   U8 padding;

   data_stage[0]=0x01;
   data_stage[1]=0;
   data_stage[2]=MSB(start);
   data_stage[3]=LSB(start);
   data_stage[4]=MSB(end);
   data_stage[5]=LSB(end);
   for(i=6;i<32;i++)
   {
      data_stage[i]=0;
   }
   padding=start%32;
   j=padding;
   while(j)
   {
      data_stage[i]=0x00;
      i++; j--;
   }
   j=end-start+1;
   while(j)
   {
      data_stage[i]=*buf;
      buf++;
      j--;i++;
   }
   Dfu_download(32+end-start+1+padding);
}

void dfu_read(U16 start,U16 end,U8 *buf)
{
   U16 j;
   U16 i;

   data_stage[0]=0x03;
   data_stage[1]=0;
   data_stage[2]=MSB(start);
   data_stage[3]=LSB(start);
   data_stage[4]=MSB(end);
   data_stage[5]=LSB(end);
   Dfu_download(6);
   i=end-start+1;
   Dfu_upload(i);
   for(j=0;j<i;j++)
   {
      *buf=data_stage[j];
      buf++;
   }
}

void dfu_flush_prog(void)
{
   U8 i;
   // All buffer in the same flash memory page ?
   if( MSB(start_addr)== MSB(end_addr))
   {
      dfu_prog(start_addr,end_addr,prog_buf);
   }
   else // No, program in two times
   {
      i=(end_addr%FLASH_PAGE_SIZE);
      dfu_prog(start_addr,end_addr-i-1,prog_buf);
      dfu_prog(end_addr-i,end_addr,&prog_buf[pgm_nb_data-i-1]);
   }
   // Verify on the fly...
   dfu_read(start_addr,end_addr,check_buf);
   for(i=0;i<pgm_nb_data;i++)
   {
      if(prog_buf[i]!=check_buf[i])
      {
         Leds_on();
         dfu_status=FALSE;
      }
   }
   Led0_toggle();
   pgm_index=0;
   pgm_nb_data=0;
}

U8 ascii_to_bin (U8 b)
{
b|='a'-'A'; // to_lower: '0'=>'0', 'A'=>'a', 'a'=>'a'
return ( (b <= '9') ? (b-'0') : (b+10-'a') );
}

