/**************************************************************************
 *
 * \file
 *
 * \brief Management of the USB device generic HID Qtouch debug task.
 *
 * This file manages the USB device generic HID Qtouch debug task.
 *
 * Copyright (c) 2009 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * 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 microcontroller 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.
 *
 * \asf_license_stop
 *
 ***************************************************************************/


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

#include "conf_usb.h"

#if USB_DEVICE_FEATURE == true

#include "board.h"
#include "usb_drv.h"
#include "gpio.h"
#include "pdca.h"
#include "intc.h"
#include "usart.h"
#include "usb_descriptors.h"
#include "usb_standard_request.h"
#include "cycle_counter.h"
#include "fifo.h"
#include "power_clocks_lib.h"

#include "qtouch_debug_frame.h"
#include "device_hid_qtouch_debug_task.h"

#include <string.h>


//_____ M A C R O S ________________________________________________________


//_____ D E F I N I T I O N S ______________________________________________
#define RX_LED AVR32_PIN_PA20
#define TX_LED AVR32_PIN_PA21

#define USART               (&AVR32_USART1)
#define USART_BAUDRATE      115200
#define USART_RX_PIN        AVR32_USART1_RXD_0_0_PIN
#define USART_RX_FUNCTION   AVR32_USART1_RXD_0_0_FUNCTION
#define USART_TX_PIN        AVR32_USART1_TXD_0_0_PIN
#define USART_TX_FUNCTION   AVR32_USART1_TXD_0_0_FUNCTION
#define USART_IRQ           AVR32_USART1_IRQ


#define USART_RX_BUFFER     (EP_IN_LENGTH*2) // Unit is in characters.

#define AVR32_PDCA_PID_USART_TX       AVR32_PDCA_PID_USART1_TX
#define PDCA_CHANNEL_USART            0

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


static bool write_hid_message = false;
static int write_hid_message_index=0;

static struct hid_frames ack_message;

static bool send_nack = false;
static bool send_ack  = false;


static U16  sof_cnt;

static void hid_report_out  (void);
static void hid_report_in   (void);

struct hid_frames out_buffer; //sizeof must be EP_OUT_LENGTH

static int recieved_data=0; // Number of data recieved from USART


extern int jump_bootloader;
extern pcl_freq_param_t pcl_freq_param;

static fifo_desc_t   fifo_desc_usart;
static uint8_t       fifo_rx[USART_RX_BUFFER];

void usb_write_message(void* ptr_cram);


#if (defined __GNUC__) && (defined __AVR32__)
__attribute__((__interrupt__))
#elif (defined __ICCAVR32__)
__interrupt
#endif
static void int_handler_usart(void)
{
  if( USART->csr & AVR32_USART_RXRDY_MASK )
  {
    U8 i, status;
    i = (USART->rhr & AVR32_USART_RHR_RXCHR_MASK) >> AVR32_USART_RHR_RXCHR_OFFSET;
    status = fifo_push_uint8(&fifo_desc_usart, i);
    if( status==FIFO_ERROR_OVERFLOW )
    {
      // error
      gpio_clr_gpio_pin(RX_LED);
      gpio_clr_gpio_pin(TX_LED);
    }
  }
}

//! @brief This function initializes USART connected to main UC3 device
//! present on the kit.
//!
void init_rs232(unsigned long baudrate, long pba_hz)
{
  static const gpio_map_t USART_GPIO_MAP =
  {
    {USART_RX_PIN, USART_RX_FUNCTION},
    {USART_TX_PIN, USART_TX_FUNCTION}
  };

  // Options for debug USART.
  usart_options_t dbg_usart_options =
  {
    .baudrate = baudrate,
    .charlength = 8,
    .paritytype = USART_NO_PARITY,
    .stopbits = USART_1_STOPBIT,
    .channelmode = USART_NORMAL_CHMODE
  };

  // Setup GPIO for debug USART.
  gpio_enable_module(USART_GPIO_MAP,
                     sizeof(USART_GPIO_MAP) / sizeof(USART_GPIO_MAP[0]));

  // Initialize it in RS232 mode.
  usart_init_rs232(USART, &dbg_usart_options, pba_hz);
}


//!
//! @brief This function initializes the hardware/software resources
//! required for device generic HID task.
//!
void device_generic_hid_task_init(void)
{
  sof_cnt = 0;
  int i;

  // PDCA channel options
  static const pdca_channel_options_t PDCA_OPTIONS =
  {
    .addr   = (void *) serial_tx_frame   ,    // memory address
    .pid    = AVR32_PDCA_PID_USART_TX,        // select peripheral - data are transmit on USART TX line.
    .size   = 0 ,                             // transfer counter
    .r_addr = NULL,                           // next memory address
    .r_size = 0,                              // next transfer counter
    .transfer_size = PDCA_TRANSFER_SIZE_BYTE  // select size of the transfer
  };

  // Init USB HID Ack Message
  ack_message.protocol_id=PROTOCOL_ID;
  ack_message.spare_byte1=0x00;
  ack_message.spare_byte2=0x00;
  ack_message.packetCount=0;
  ack_message.packetNumber=0;
  ack_message.packetNumber=0;
  for(i=0;i<PAYLOAD_SIZE;i++) {
      ack_message.payload[i]=0x00;
   }

  // Initialize the PDCA channel with the pdca_options for the USART
  pdca_init_channel(PDCA_CHANNEL_USART, &PDCA_OPTIONS);

  // Initialize the FIFO module
  fifo_init( &fifo_desc_usart, &fifo_rx, USART_RX_BUFFER );

  // Register the USART interrupt handler to the interrupt controller.
  // Highest priority is required for the USART, since we do not want to loose
  // any characters.
  Disable_global_interrupt();
  INTC_register_interrupt(&int_handler_usart, USART_IRQ, AVR32_INTC_INT3);
  Enable_global_interrupt();

  // Clear all leds.
  gpio_set_gpio_pin(RX_LED);
  gpio_set_gpio_pin(TX_LED);

#ifndef FREERTOS_USED
  #if USB_HOST_FEATURE == true
  // If both device and host features are enabled, check if device mode is engaged
  // (accessing the USB registers of a non-engaged mode, even with load operations,
  // may corrupt USB FIFO data).
  if (Is_usb_device())
  #endif  // USB_HOST_FEATURE == true
    Usb_enable_sof_interrupt();
#endif  // FREERTOS_USED

}

//! @brief Entry point of the HID generic communication task
//! This function manages IN/OUT repport management.
//!
void device_generic_hid_task(void)
{
   if(!Is_device_enumerated())          // Check USB HID is enumerated
      return;

   hid_check_vbus(); // Check if we vbus is on and
   hid_report_out(); // received data from USB
   hid_report_in();  // Send data to USB

   fill_tx_buffer();
}

//! @brief check if USB VBUS active.
//! If VBUS is not active, put the device in Stop mode until VBUS become active
//!
void hid_check_vbus(void)
{
  static bool b_startup=true;
  static U8   startup_state=0;

  if( Is_usb_vbus_low() )
    {
       U32 i;
       volatile avr32_pm_t *pm = &AVR32_PM;
       gpio_set_gpio_pin(RX_LED);
       gpio_set_gpio_pin(TX_LED);
       for( i=0 ; i<2 ; i++ )
       {
          gpio_tgl_gpio_pin(RX_LED);
          gpio_tgl_gpio_pin(TX_LED);
          cpu_delay_ms(100, pcl_freq_param.cpu_f);
       }

       pm->AWEN.usb_waken = 1;
       SLEEP(AVR32_PM_SMODE_STOP);
       pm->AWEN.usb_waken = 0;

       b_startup = true;
       startup_state = 0;

       for( i=0 ; i<2 ; i++ )
       {
          gpio_tgl_gpio_pin(RX_LED);
          gpio_tgl_gpio_pin(TX_LED);
          cpu_delay_ms(100, pcl_freq_param.cpu_f);
       }
    }

    if( b_startup )
    {
       if( startup_state==0 )
       {
         // Initialize usart comm
         init_rs232(USART_BAUDRATE, pcl_freq_param.pba_f);
         gpio_enable_pin_pull_up(USART_RX_PIN);
         USART->ier = AVR32_USART_IER_RXRDY_MASK;
         gpio_clr_gpio_pin(RX_LED);
       }
       else if( startup_state==1 ) gpio_clr_gpio_pin(TX_LED);
       else if( startup_state==2 ) gpio_set_gpio_pin(RX_LED);
       else if( startup_state==3 ) gpio_set_gpio_pin(TX_LED);
       else if( startup_state==4 ) gpio_clr_gpio_pin(RX_LED);
       else if( startup_state==5 ) gpio_clr_gpio_pin(TX_LED);
       else if( startup_state==6 ) gpio_set_gpio_pin(RX_LED);
       else if( startup_state==7 ) gpio_set_gpio_pin(TX_LED);
       else if( startup_state==8 ) b_startup=false;
       startup_state++;
       cpu_delay_ms(100, FOSC0);
    }
}

//! @brief Get data report from Host
//!
void hid_report_out(void)
{
   void* ptr_cram=& out_buffer;
   int none_read_byte;

   if(Is_usb_out_received(EP_HID_GENERIC_OUT))
   {
      // data recieved
      gpio_tgl_gpio_pin(RX_LED);
      Usb_reset_endpoint_fifo_access(EP_HID_GENERIC_OUT);
      none_read_byte = usb_read_ep_rxpacket(EP_HID_GENERIC_OUT, ptr_cram, EP_OUT_LENGTH, (void**)&ptr_cram);
      Usb_ack_out_received_free(EP_HID_GENERIC_OUT);
      if(none_read_byte==0) {
        // previous Message hasn't be read
        if((pdca_get_transfer_status(PDCA_CHANNEL_USART)&PDCA_TRANSFER_COMPLETE)==0) {
          // Flush Message and send Nack message
          send_nack = true;
        }
        else if(get_message(out_buffer)==MESSAGE_READY) {
           pdca_load_channel(PDCA_CHANNEL_USART, (void *)serial_tx_frame,((serial_tx_frame[1]<<8)| serial_tx_frame[2])+1 );
           pdca_enable(PDCA_CHANNEL_USART);
           // Store packet Cound in send_ack, this will be use to send ack.
           send_ack = out_buffer.packetCount;
        }
      }
      // The Data has been read and stored in out_buffer
   }

   //** Check if we received DFU mode command from host
   if(jump_bootloader)
   {
      gpio_set_gpio_pin(LED0_GPIO);
      gpio_set_gpio_pin(LED1_GPIO);
      gpio_set_gpio_pin(LED2_GPIO);
      gpio_set_gpio_pin(LED3_GPIO);
      Usb_detach();                           // Detach actual generic HID application
      cpu_delay_ms(100, pcl_freq_param.cpu_f);// Wait some time before
      //start_boot();                         // Jumping to bootloader Not available yet
      SLEEP(AVR32_PM_SMODE_STOP);             // Stop mode instead of jump bootloader
   }
}


//! @brief Send data report to USB HID Host
//!
void hid_report_in(void)
{
  // No message to transmit yet
  void* ptr_cram;

  if(!Is_usb_write_enabled(EP_HID_GENERIC_IN))
      return;                                // Not ready to send report

  // Send report
  if(write_hid_message==true) {
    gpio_tgl_gpio_pin(TX_LED);

    ptr_cram = & (hid_rx_frame[write_hid_message_index++]);

    usb_write_message(ptr_cram);

    // If Message Fully send
    if(write_hid_message_index>=hid_rx_frame[0].packetNumber) {
       write_hid_message=false;
       recieved_data=0;
    }
  }
  // Send nack message if send_nack flag is true
  else if (send_nack == true) {
    ack_message.spare_byte2  = NACK_MESSAGE;
    ack_message.packetNumber = 0;
    ack_message.packetCount  = 0;
    usb_write_message(&ack_message);
    send_nack = false;

  }
  // Send ack message if send_ack flag is not equal to Zero
  // The send_ack value is the packet number of message to be acknowledged
  else if (send_ack != 0) {
    // Send Ack Message only if PDCA transfert is done
    if((pdca_get_transfer_status(PDCA_CHANNEL_USART)&PDCA_TRANSFER_COMPLETE)==0) {
      ack_message.spare_byte2  = ACK_MESSAGE;
      ack_message.packetNumber = send_ack;
      ack_message.packetCount  = send_ack;
      usb_write_message(&ack_message);
      send_ack = 0;
    }
  }
}

//! @brief Write message to USB HID Host
//!
void usb_write_message(void* ptr_cram)
{
  int data_to_transfer=EP_IN_LENGTH;
  while (data_to_transfer) {
    while (!Is_usb_in_ready(EP_HID_GENERIC_IN));
        Usb_reset_endpoint_fifo_access(EP_HID_GENERIC_IN);
        data_to_transfer = usb_write_ep_txpacket(EP_HID_GENERIC_IN, ptr_cram, data_to_transfer, (const void**)&ptr_cram);
        Usb_ack_in_ready_send(EP_HID_GENERIC_IN);
    }
}

//! @brief Send data report to USB HIDHost
//!
void fill_tx_buffer(void)
{
  U8 c;
  int size;
  if(recieved_data>2) {
    // Do not forget to add MSG_Start byte in length
    size = (serial_rx_frame[1]<<8|serial_rx_frame[2])+1;
  }
  else {
    size=sizeof(serial_rx_frame);
  }

  while((fifo_get_used_size(&fifo_desc_usart)>0)&&(recieved_data<size)) {
    if(fifo_pull_uint8(&fifo_desc_usart, &c)==FIFO_OK) {
      serial_rx_frame[recieved_data++] = c;
    }
    else {
      gpio_clr_gpio_pin(RX_LED);
      gpio_clr_gpio_pin(TX_LED);
    }

    // Resynch until new char is ready
    if((recieved_data>=1)&&(serial_rx_frame[0])!=0x1B) {
      recieved_data=0;
      size=sizeof(serial_rx_frame);
    }
  }

  //can send messge until previous is not fully sent
  if((recieved_data>=size)&&(write_hid_message==false)) {
    send_message(serial_rx_frame,size);
    write_hid_message=true;
    write_hid_message_index=0;
  }
}


//!
//! @brief usb_sof_action
//!
//! This function increments the sof_cnt counter each time
//! the USB Start-of-Frame interrupt subroutine is executed (1 ms).
//! Useful to manage time delays
//!
void usb_sof_action(void)
{
  sof_cnt++;
}


#endif  // USB_DEVICE_FEATURE == true
