//-----------------------------------
// File:          dbgu.c
// Function:      Routines for displaying on DBGU port
// Supported chip(s):
//    - AT91SAM7XC128
//    - AT91SAM7XC256
// Supported toolchain(s):
//    - IAR Embedded Workbench
// Date created:  28 April 2006
// Created by:    JJo
//-----------------------------------

//-----------------------------------
// Includes

#include "dbgu.h"

#ifdef USE_DBGU

//-----------------------------------
// Inline functions

/**
 * Name:    AT91F_DBGU_CfgMode
 * Purpose: Configures the mode register of the DBGU
 * Inputs:
 *  - DBGU address
 *  - Selected mode
 */
static inline void AT91F_DBGU_CfgMode(AT91PS_DBGU pDbgu, unsigned int mode) {

  pDbgu->DBGU_MR = mode;
}

/**
 * Name:    AT91F_DBGU_BaudRate
 * Purpose: Calculates the clock divisor value given the main clock frequency
 *          and the desired baud rate.
 * Inputs:
 *  - Main clock frequency
 *  - Desired baud rate
 * Output: Clock divisor value.
 */
static inline unsigned int AT91F_DBGU_CalculateCD(unsigned int mck,
                                                  unsigned int baudRate) {

  // Local variables
  unsigned int clockDivisor;

  // Round to closest value
  clockDivisor = mck / (baudRate * 8);

  if (clockDivisor%2 == 0) {

    clockDivisor /= 2;
  }
  else {

    clockDivisor = clockDivisor / 2 + 1;
  }

  return clockDivisor;
}

/**
 * Name:      AT91F_DBGU_SetBaudRate
 * Purpose:   Sets the clock divisor value for the DBGU port
 * Inputs:
 *  - DBGU address
 *  - Clock divisor value
 */
static inline void AT91F_DBGU_SetCD(AT91PS_DBGU pDbgu, unsigned int cd) {

  pDbgu->DBGU_BRGR = cd;
}

/**
 * Name:      AT91F_DBGU_EnableTx
 * Purpose:   Enable transmitter on the DBGU port
 * Input:
 *  - DBGU address
 */
static inline void AT91F_DBGU_EnableTx(AT91PS_DBGU pDbgu) {

  pDbgu->DBGU_CR = AT91C_US_TXEN;
}

/**
 * Name:      AT91F_DBGU_EnableRx
 * Purpose:   Enable receiver on the DBGU port
 * Input:
 *  - DBGU address
 */
static inline void AT91F_DBGU_EnableRx(AT91PS_DBGU pDbgu) {

  pDbgu->DBGU_CR = AT91C_US_RXEN;
}


/**
 * Name:      AT91F_DBGU_TxReady
 * Purpose:   Indicates whether the DBGU transmitter is ready or not
 * Input:
 *  - DBGU address
 * Output: 1 if the transmitter is ready, 0 otherwise.
 */
static inline unsigned int AT91F_DBGU_TxReady(AT91PS_DBGU pDbgu) {

  return ((pDbgu->DBGU_CSR & AT91C_US_TXRDY) >> 1);
}

/**
 * Name:      AT91F_DBGU_RxReady
 * Purpose:   Indicates whether the DBGU receiver has received a character
 * Input:
 *  - DBGU address
 * Output: 1 if a character has been received, 0 otherwise.
 */
static inline unsigned int AT91F_DBGU_RxReady(AT91PS_DBGU pDbgu) {

  return (pDbgu->DBGU_CSR & AT91C_US_RXRDY);
}

/**
 * Name:      AT91F_DBGU_TxEmpty
 * Purpose:   Indicates whether there are characters being processed by the transmitter.
 * Input:
 *  - DBGU address
 * Output: 1 if the transmit buffers are empty, 0 otherwise.
 */
static inline unsigned int AT91F_DBGU_TxEmpty(AT91PS_DBGU pDbgu) {

  return ((pDbgu->DBGU_CSR & AT91C_US_TXEMPTY) >> 9);
}

/**
 * Name:      AT91F_DBGU_PutChar
 * Purpose:   Outputs a character on the DBGU line.
 * Inputs:
 *  - DBGU address
 *  - Character to send
 */
static inline void AT91F_DBGU_PutChar(AT91PS_DBGU pDbgu, char c) {

  pDbgu->DBGU_THR = c;
}

/**
 * Name:      AT91F_DBGU_GetChar
 * Purpose:   Gets a character from the DBGU line.
 * Inputs:
 *  - DBGU address
 * Output:    Received character.
 */
static inline char AT91F_DBGU_GetChar(AT91PS_DBGU pDbgu) {

  return pDbgu->DBGU_RHR;
}

/**
 * Name:      AT91F_DBGU_Close
 * Purpose:   Return the DBGU configuration to reset state
 * Input:
 *  - DBGU address
 */
static inline void AT91F_DBGU_Close(AT91PS_DBGU pDBGU) {

    pDBGU->DBGU_BRGR = 0 ;
    pDBGU->DBGU_MR = 0  ;
    pDBGU->DBGU_IDR = 0xFFFFFFFF ;
    AT91F_PDC_Close((AT91PS_PDC) &(pDBGU->DBGU_RPR));
    pDBGU->DBGU_CR = AT91C_US_TXDIS | AT91C_US_RXDIS | AT91C_US_RSTTX | AT91C_US_RSTRX ;
}

//-----------------------------------
// Functions

/**
 * Name:    dbgu_init
 * Purpose: Initializes the DBGU port
 */
void dbgu_init() {

  // Configure PMC and PIO
  AT91F_DBGU_CfgPMC();
  AT91F_DBGU_CfgPIO();

  // Configure mode, baud rate & disable interrupts
  AT91F_DBGU_CfgMode(AT91C_BASE_DBGU, DBGU_MODE);
  AT91F_DBGU_InterruptDisable(AT91C_BASE_DBGU, 0xFFFFFFFF);
  AT91F_DBGU_SetCD(AT91C_BASE_DBGU, AT91F_DBGU_CalculateCD(MCK, DBGU_BAUDRATE));

  // Enable transmitter
  AT91F_DBGU_EnableTx(AT91C_BASE_DBGU);
  AT91F_DBGU_EnableRx(AT91C_BASE_DBGU);
}

/**
 * Name:    dbgu_cleanup
 * Purpose: Cleanup the DBGU port, returning it to its reset configuration
 */
void dbgu_cleanup() {

  // Disable the DBGU in the PMC and PIO
  AT91F_DBGU_Close(AT91C_BASE_DBGU);
  AT91F_PMC_DisablePeriphClock(AT91C_BASE_PMC, ((unsigned int) 1 << AT91C_ID_SYS));
  AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, AT91C_PA28_DTXD | AT91C_PA27_DRXD);
}

/**
 * Name:    dbgu_putChar
 * Purpose: Outputs a character on the DBGU line.
 * Input:
 *  - Character to write
 */
static void dbgu_putChar(char c) {

  // Wait for transmitter to be ready
  while (!AT91F_DBGU_TxReady(AT91C_BASE_DBGU));

  // Send character and wait for transmission
  AT91F_DBGU_PutChar(AT91C_BASE_DBGU, c);
  while(!AT91F_DBGU_TxEmpty(AT91C_BASE_DBGU));
}

/**
 * Name:    dbgu_putString
 * Purpose: Ouputs a null-terminated string on the DBGU line.
 * Input:
 *  - String to write
 * Output: Number of characters written
 */
static unsigned int dbgu_putString(const char * string) {
  
  // Local variables
  unsigned int ctr_i;

  // Loop through all characters of the string
  ctr_i = 0;

  while (string[ctr_i] != '\0') {

    dbgu_putChar(string[ctr_i]);
    
    if (string[ctr_i] == '\n') {
      
      dbgu_putChar('\r');
    }

    ctr_i++;
  }

  return ctr_i;
}

/**
 * Name:      dbgu_putInteger
 * Purpose:   Outputs an unsigned integer value on the DBGU line
 * Input:
 *  - Value to display
 */
static void dbgu_putInteger(unsigned int value) {

  if ((value / 10) > 0) {

    dbgu_putInteger(value / 10);
  }
  
  dbgu_putChar(value%10 + '0');
}

/**
 * Name:      dbgu_putHexa
 * Purpose:   Outputs an hexadecimal value on the DBGU line
 * Inputs:
 *  - Pointer to the data to send
 *  - Size (in bytes) of the data
 *  - FALSE for big-endian, TRUE for little endian (the later works only for size = x*4)
 */
static void dbgu_putHexa(const void * vData, unsigned int size, unsigned int isLittleEndian) {

  // Local variables
  const char hexa[] = "0123456789ABCDEF";
  char * pData = (char *) vData;

  if (isLittleEndian && size%4 == 0) {

    for (unsigned int i=0; i < size/4; i++) {

      for (int j=3; j >= 0; j--) {

        dbgu_putChar(hexa[pData[i*4+j] >> 4]);
        dbgu_putChar(hexa[pData[i*4+j] & 0x0F]);
      }
    }
  }
  else {

    // Loop through data, displaying bytes
    for (unsigned int i=0; i < size; i++) {
  
      dbgu_putChar(hexa[pData[i] >> 4]);
      dbgu_putChar(hexa[pData[i] & 0x0F]);
    }
  }
}

/**
 * Name:      dbgu_printf
 * Purpose:   Outputs a formatted string on the DBGU line
 *            Supported parameters: %c, %s, %d, %X
 * Inputs:
 *  - String to output
 *  - Additional parameters
 */
void dbgu_printf(const char * string, ...) {

  // Local variables
  va_list arguments;
  unsigned int ctr_i;
  unsigned int uint32;

  // Initialize variable argument list
  va_start(arguments, string);

  // Process characters
  ctr_i = 0;

  while (string[ctr_i] != '\0') {

    if (string[ctr_i] == '%') {
     
      ctr_i++;

      switch(string[ctr_i]) {
  
        case '%': dbgu_putChar('%'); break;
        case 'c': dbgu_putChar(va_arg(arguments, char)); break;
        case 's': dbgu_putString(va_arg(arguments, char *)); break;
        case 'd': dbgu_putInteger(va_arg(arguments, int)); break;
        case 'X': 
          uint32 = va_arg(arguments, unsigned int);
          dbgu_putHexa(&uint32, 4, FALSE);
          break;
      }
    }
    else {
  
      dbgu_putChar(string[ctr_i]);
      
      if (string[ctr_i] == '\n') {
      
        dbgu_putChar('\r');
      }
    }

    // Loop progression
    ctr_i++;
  }

  // Cleanup variable argument list
  va_end(arguments);  
}

#endif // USE_DBGU
