/*This file has been prepared for Doxygen automatic documentation generation.*/
/*! \file *********************************************************************
 *
 * \brief FreeRTOS serial port for AVR32 UC3.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All AVR32 devices can be used.
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 *****************************************************************************/

/* Copyright (c) 2007, 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.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 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.
 */


/*
  BASIC INTERRUPT DRIVEN SERIAL PORT DRIVER FOR USART0.
*/

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"

/* Demo application includes. */
#include "serial.h"
#include <avr32/io.h>
#include "board.h"
#include "gpio.h"

/*-----------------------------------------------------------*/

/* Constants to setup and access the USART. */
#define serINVALID_COMPORT_HANDLER        ( ( xComPortHandle ) 0 )
#define serINVALID_QUEUE                  ( ( QueueHandle_t ) 0 )
#define serHANDLE                         ( ( xComPortHandle ) 1 )
#define serNO_BLOCK                       ( ( TickType_t ) 0 )

/*-----------------------------------------------------------*/

/* Queues used to hold received characters, and characters waiting to be
transmitted. */
static QueueHandle_t xRxedChars;
static QueueHandle_t xCharsForTx;

/*-----------------------------------------------------------*/

/* Forward declaration. */
static void vprvSerialCreateQueues( unsigned portBASE_TYPE uxQueueLength,
        QueueHandle_t *pxRxedChars,
        QueueHandle_t *pxCharsForTx );

/*-----------------------------------------------------------*/

#if __GNUC__
__attribute__((__noinline__))
#elif __ICCAVR32__
#pragma optimize = no_inline
#endif
static portBASE_TYPE prvUSART0_ISR_NonNakedBehaviour( void )
{
  /* Now we can declare the local variables. */
  signed char     cChar;
  portBASE_TYPE     xHigherPriorityTaskWoken = pdFALSE;
  unsigned long     ulStatus;
  volatile avr32_usart_t  *usart0 = &AVR32_USART0;
  portBASE_TYPE retstatus;

  /* What caused the interrupt? */
  ulStatus = usart0->csr & usart0->imr;

  if (ulStatus & AVR32_USART_CSR_TXRDY_MASK)
  {
    /* The interrupt was caused by the THR becoming empty.  Are there any
       more characters to transmit? */
    /* Because FreeRTOS is not supposed to run with nested interrupts, put all OS
      calls in a critical section . */
    portENTER_CRITICAL();
    retstatus = xQueueReceiveFromISR(xCharsForTx, &cChar, &xHigherPriorityTaskWoken);
    portEXIT_CRITICAL();
    if (retstatus == pdTRUE)
    {
      /* A character was retrieved from the queue so can be sent to the
         THR now. */
      usart0->thr = cChar;
    }
    else
    {
      /* Queue empty, nothing to send so turn off the Tx interrupt. */
      usart0->idr = AVR32_USART_IDR_TXRDY_MASK;
    }
  }

  if (ulStatus & AVR32_USART_CSR_RXRDY_MASK)
  {
    /* The interrupt was caused by the receiver getting data. */
    cChar = usart0->rhr; //TODO

    /* Because FreeRTOS is not supposed to run with nested interrupts, put all OS
      calls in a critical section . */
    portENTER_CRITICAL();
	xQueueSendFromISR(xRxedChars, &cChar, &xHigherPriorityTaskWoken);
    portEXIT_CRITICAL();
  }

  /* The return value will be used by portEXIT_SWITCHING_ISR() to know if it
  should perform a vTaskSwitchContext(). */
  return ( xHigherPriorityTaskWoken );
}


/*
 * USART0 interrupt service routine.
 */
#if __GNUC__
__attribute__((__naked__))
#elif __ICCAVR32__
#pragma shadow_registers = full   // Naked.
#endif
static void vUSART0_ISR( void )
{

 /* This ISR can cause a context switch, so the first statement must be a
  call to the portENTER_SWITCHING_ISR() macro.  This must be BEFORE any
  variable declarations. */
  portENTER_SWITCHING_ISR();
  prvUSART0_ISR_NonNakedBehaviour();
 /* Exit the ISR.  If a task was woken by either a character being received
  or transmitted then a context switch will occur. */
  portEXIT_SWITCHING_ISR();
}
/*-----------------------------------------------------------*/



/*
 * Init the serial port for the Minimal implementation.
 */
xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
  static const gpio_map_t USART0_GPIO_MAP =
  {
    { AVR32_USART0_RXD_0_PIN, AVR32_USART0_RXD_0_FUNCTION },
    { AVR32_USART0_TXD_0_PIN, AVR32_USART0_TXD_0_FUNCTION }
  };

  xComPortHandle    xReturn = serHANDLE;
  volatile avr32_usart_t  *usart0 = &AVR32_USART0;
  int                           cd; /* USART0 Clock Divider. */

  /* Create the rx and tx queues. */
  vprvSerialCreateQueues( uxQueueLength, &xRxedChars, &xCharsForTx );

  /* Configure USART0. */
  if( ( xRxedChars != serINVALID_QUEUE ) &&
      ( xCharsForTx != serINVALID_QUEUE ) &&
      ( ulWantedBaud != ( unsigned long ) 0 ) )
  {
    portENTER_CRITICAL();
    {
      /**
       ** Reset USART0.
       **/
      /* Disable all USART0 interrupt sources to begin... */
      usart0->idr = 0xFFFFFFFF;

      /* Reset mode and other registers that could cause unpredictable
         behaviour after reset */
      usart0->mr = 0; /* Reset Mode register. */
      usart0->rtor = 0; /* Reset Receiver Time-out register. */
      usart0->ttgr = 0; /* Reset Transmitter Timeguard register. */

      /* Shutdown RX and TX, reset status bits, reset iterations in CSR, reset NACK
         and turn off DTR and RTS */
      usart0->cr = AVR32_USART_CR_RSTRX_MASK   |
                   AVR32_USART_CR_RSTTX_MASK   |
                   AVR32_USART_CR_RXDIS_MASK   |
                   AVR32_USART_CR_TXDIS_MASK   |
                   AVR32_USART_CR_RSTSTA_MASK  |
                   AVR32_USART_CR_RSTIT_MASK   |
                   AVR32_USART_CR_RSTNACK_MASK |
                   AVR32_USART_CR_DTRDIS_MASK  |
                   AVR32_USART_CR_RTSDIS_MASK;

      /**
       ** Configure USART0.
       **/
      /* Enable USART0 RXD & TXD pins. */
      gpio_enable_module( USART0_GPIO_MAP, sizeof( USART0_GPIO_MAP ) / sizeof( USART0_GPIO_MAP[0] ) );

      /* Set the USART0 baudrate to be as close as possible to the wanted baudrate. */
      /*
       *             ** BAUDRATE CALCULATION **
       *
       *                 Selected Clock                       Selected Clock
       *     baudrate = ----------------   or     baudrate = ----------------
       *                    16 x CD                              8 x CD
       *
       *       (with 16x oversampling)              (with 8x oversampling)
       */
      if ( ulWantedBaud < (configCPU_CLOCK_HZ/16)  ){
        /* Use 8x oversampling */
        usart0->mr |= (1<<AVR32_USART_MR_OVER_OFFSET);
        cd = configCPU_CLOCK_HZ / (8*ulWantedBaud);

        if (cd < 2) {
          return serINVALID_COMPORT_HANDLER;
        }
        usart0->brgr = (cd << AVR32_USART_BRGR_CD_OFFSET);
      } else {
        /* Use 16x oversampling */
        usart0->mr &= ~(1<<AVR32_USART_MR_OVER_OFFSET);
        cd =  configCPU_CLOCK_HZ / (16*ulWantedBaud);

        if (cd > 65535) {
          /* Baudrate is too low */
          return serINVALID_COMPORT_HANDLER;
        }
      }
      usart0->brgr = (cd << AVR32_USART_BRGR_CD_OFFSET);

      /* Set the USART0 Mode register: Mode=Normal(0), Clk selection=MCK(0),
         CHRL=8,  SYNC=0(asynchronous), PAR=None, NBSTOP=1, CHMODE=0, MSBF=0,
         MODE9=0, CKLO=0, OVER(previously done when setting the baudrate),
         other fields not used in this mode. */
      usart0->mr |= ((8-5) << AVR32_USART_MR_CHRL_OFFSET  ) |
                    (   4  << AVR32_USART_MR_PAR_OFFSET   ) |
                    (   1  << AVR32_USART_MR_NBSTOP_OFFSET);

      /* Write the Transmit Timeguard Register */
      usart0->ttgr = 0;

      // Register the USART0 interrupt handler to the interrupt controller and
      // enable the USART0 interrupt.
      INTC_register_interrupt((__int_handler)&vUSART0_ISR, AVR32_USART0_IRQ, INT1);

      /* Enable USART0 interrupt sources (but not Tx for now)... */
      usart0->ier = AVR32_USART_IER_RXRDY_MASK;

      /* Enable receiver and transmitter... */
      usart0->cr |= AVR32_USART_CR_TXEN_MASK | AVR32_USART_CR_RXEN_MASK;
    }
    portEXIT_CRITICAL();
  }
  else
  {
    xReturn = serINVALID_COMPORT_HANDLER;
  }

  return xReturn;
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialGetChar( xComPortHandle pxPort, signed char *pcRxedChar, TickType_t xBlockTime )
{
  /* The port handle is not required as this driver only supports UART0. */
  ( void ) pxPort;

  /* Get the next character from the buffer.  Return false if no characters
  are available, or arrive before xBlockTime expires. */
  if( xQueueReceive( xRxedChars, pcRxedChar, xBlockTime ) )
  {
    return pdTRUE;
  }
  else
  {
    return pdFALSE;
  }
}
/*-----------------------------------------------------------*/

void vSerialPutString( xComPortHandle pxPort, const signed char * const pcString, unsigned short usStringLength )
{
signed char *pxNext;

  /* NOTE: This implementation does not handle the queue being full as no
  block time is used! */

  /* The port handle is not required as this driver only supports UART0. */
  ( void ) pxPort;

  /* Send each character in the string, one at a time. */
  pxNext = ( signed char * ) pcString;
  while( *pxNext )
  {
    xSerialPutChar( pxPort, *pxNext, serNO_BLOCK );
    pxNext++;
  }
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialPutChar( xComPortHandle pxPort, signed char cOutChar, TickType_t xBlockTime )
{
volatile avr32_usart_t  *usart0 = &AVR32_USART0;

  /* Place the character in the queue of characters to be transmitted. */
  if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) != pdPASS )
  {
    return pdFAIL;
  }

  /* Turn on the Tx interrupt so the ISR will remove the character from the
  queue and send it.   This does not need to be in a critical section as
  if the interrupt has already removed the character the next interrupt
  will simply turn off the Tx interrupt again. */
  usart0->ier = (1 << AVR32_USART_IER_TXRDY_OFFSET);

  return pdPASS;
}
/*-----------------------------------------------------------*/

void vSerialClose( xComPortHandle xPort )
{
  /* Not supported as not required by the demo application. */
}
/*-----------------------------------------------------------*/

/*###########################################################*/

/*
 * Create the rx and tx queues.
 */
static void vprvSerialCreateQueues(  unsigned portBASE_TYPE uxQueueLength, QueueHandle_t *pxRxedChars, QueueHandle_t *pxCharsForTx )
{
  /* Create the queues used to hold Rx and Tx characters. */
  xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
  xCharsForTx = xQueueCreate( uxQueueLength + 1, ( unsigned portBASE_TYPE ) sizeof( signed char ) );

  /* Pass back a reference to the queues so the serial API file can
  post/receive characters. */
  *pxRxedChars = xRxedChars;
  *pxCharsForTx = xCharsForTx;
}
/*-----------------------------------------------------------*/
