/*
 * serialPort_helper.c
 *
 * history:
 *   2023-12-12 - RS - changed the references to the UART baseaddresses according to 2023.2 xparameters
 *   2022-02-18 - WK - cleanup, added robustness
 *   2021-08-19 - WK - fixed adding character to buffer for versal (stopped hanging for new char)
 *   2021-08-10 - WK - added UART base address for Versal
 *   2016-05-03 - WK - Created
 */

#include "serialPort_helper.h"

// constants defining the use of the UART (registers listed in TRM B.33)
//
// buffers for the UART
#define UART_BUFFER_SIZE 64
unsigned char sendBuffer[UART_BUFFER_SIZE];  // Buffer for Transmitting Data

#ifdef PRELOAD_COMMANDS
    unsigned char recvBuffer[] = PRELOAD_COMMANDS;  // Buffer for Receiving Data
#else
    unsigned char recvBuffer[UART_BUFFER_SIZE];  // Buffer for Receiving Data
#endif


//*********************** Functions for Using the PS UART *************************************

char getCharFromPSUART(int echo) {
      static int      read_ptr  = 0;
      static int      write_ptr = 0;
      static int      volume    = 0;
	  static int      already_preloaded = 0;	// causes loading from the SYMBOL only once
      unsigned char   c;                        // generic character variable
      unsigned long   *UART_read_addr;          // pointer directly to UART read  register
      unsigned long   *UART_write_addr;         // pointer directly to UART write register

// either preload the commands or map the UART read and write addresses
#ifdef PRELOAD_COMMANDS
      if (!already_preloaded) {
    	  volume = 0;
      	  while (recvBuffer[volume] != '\0') { volume++; }
      	  write_ptr = volume;
      	  already_preloaded = 1;
      }
#else
      UART_read_addr = (long unsigned int*)(STDIN_BASEADDRESS);          // pointer directly to UART read  register
      UART_write_addr = (long unsigned int*)(STDOUT_BASEADDRESS);        // pointer directly to UART write register
#endif

// when the PRE_LOAD symbol is absent, get the next character from the UART
#ifndef PRELOAD_COMMANDS

     // is there more room in the buffer for data from the UART?
     while (volume < UART_BUFFER_SIZE) {       // if space remains in the buffer
    // read a byte from the UART based on the CPU type
	#if (CPU_TYPE==72)
        //c = XUartPsv_RecvByte(STDIN_BASEADDRESS);   // hangs if no character is present
         c = *UART_read_addr;                    // attempt to read from the UART
	#elif (CPU_TYPE==13)
        // microblaze
	#elif (CPU_TYPE==53)
         c = XUartPs_RecvByte(STDIN_BASEADDRESS);
	#endif
         // read the character from the approprate source and put it in the receive buffer
         if ((c > 31) || (c == '\r')) {         // if it is valid data (printing character or cr/lf), then
            recvBuffer[write_ptr++] = c;        // save it in the buffer
            write_ptr = write_ptr % UART_BUFFER_SIZE;    // wrap if necessary - requires UART_BUFFER_SIZE to be a power of 2
            volume++;                           // one more piece of data in the buffer
         } else {
            break;                              // exit the while loop
         }
     }
#endif

      // return the next piece of valid data to the caller
      if (volume > 0) {                         // there is something to return
         c = recvBuffer[read_ptr++];            // pull the character from the buffer
         read_ptr = read_ptr % UART_BUFFER_SIZE;// wrap if necessary - requires UART_BUFFER_SIZE to be a power of 2
         volume--;                              // one less piece of data in the buffer
         // should we echo the character back when it was pulled from the buffer?
#if (CPU_TYPE==72)
         //if (echo) { *UART_write_addr = c; } // manual approach
         if (echo) { XUartPsv_SendByte(STDOUT_BASEADDRESS,c); }
#elif (CPU_TYPE==53)
         if (echo) { XUartPs_SendByte(STDOUT_BASEADDRESS,c); }
#elif (CPU_TYPE==9)
	// todo: write the echo routine for the Zynq7000
#elif (CPU_TYPE==13)
	// todo: write the echo routine for the MicroBlaze
#endif
         return c;                              // return the datum to the caller
      }

      // no data left in buffer, return failure code
      return (-1);                              // tell the caller that nothing was available (default condition)
}


//*********************** Functions for Using the UARTlite ************************************

// identify the base address of the UART depending on whether this is a UARTLite for a MicroBlaze system or a UART in the Zynq7000 PS
#if (CPU_TYPE==13)                          /* microblaze */
#define UART_BASE_ADDRESS                   XPAR_UARTLITE_0_BASEADDR
#elif (CPU_TYPE==7000)                      /* Zynq 7000 */
#define UART_BASE_ADDRESS                   XPAR_PSU_UART_0_BASEADDR
#elif (CPU_TYPE==53)                        /* MPSoC/RFSoC */
#define UART_BASE_ADDRESS                   XPAR_UART0_BASEADDR
#elif (CPU_TYPE==72)                        /* Versal */
#define UART_BASE_ADDRESS                   XPAR_XUARTPSV_0_BASEADDR
#elif (CPU_TYPE==9)							/* Zynq-7000 */
#define UART_BASE_ADDRESS                   0x0000 /* todo: what is this address? */
#endif

#define UART_READ_WRITE_REGISTER_OFFSET     0x30

char getCharFromUART(int echo) {
   
#if (CPU_TYPE==72)
   return getCharFromPSUART(echo);
#endif   
   
   static int      read_ptr  = 0;
   static int      write_ptr = 0;
   static int      volume    = 0;
   unsigned char   c;                           // generic character variable
   unsigned long   *UART_read_addr  = 0;        // pointer directly to UART read  register
   unsigned long   *UART_write_addr  = 0;       // pointer directly to UART write register

   // point to the specific location in memory where the UART should read from
   UART_read_addr = UART_write_addr = (long unsigned int *)(UART_BASE_ADDRESS + UART_READ_WRITE_REGISTER_OFFSET);

   // is there more room in the buffer for data from the UART?
   while (volume < UART_BUFFER_SIZE) {          // if space remains in the buffer
      c = *UART_read_addr;                      // attempt to read from the UART
      if ((c > 31) || (c == '\r')) {            // if it is valid data (printing character or cr/lf), then
         recvBuffer[write_ptr++] = c;           // save it in the buffer
         write_ptr = write_ptr % UART_BUFFER_SIZE;  // wrap if necessary - requires UART_BUFFER_SIZE to be a power of 2
         volume++;                              // one more piece of data in the buffer
      } else {
         c = 0;                                 // ensure that c is nulled on return
         break;                                 // exit the while loop
      }
   }

   // return the next piece of valid data to the caller
   if (volume > 0) {                            // there is something to return
      c = recvBuffer[read_ptr++];               // pull the character from the buffer
      read_ptr = read_ptr % UART_BUFFER_SIZE;   // wrap if necessary - requires UART_BUFFER_SIZE to be a power of 2
      volume--;                                 // one less piece of data in the buffer
      if (echo) { *UART_write_addr = c; }       // should we echo the character back when it was pulled from the buffer?
      return c;                                 // return the datum to the caller
   }

   // no data left in buffer, return failure code
   return (-1);                                 // tell the caller that nothing was available (default condition)
}

/*
 * Place a single character to the UART
 */
void putCharToUART(char c) {

#if (CPU_TYPE==13)
   XUartLite_SendByte(UART_BASE_ADDRESS, c);
#else
   // local variables
   unsigned long *UART_write_addr  = 0;         // pointer directly to UART write register

   // point to the proper register
   UART_write_addr = (long unsigned int *)(UART_BASE_ADDRESS + UART_READ_WRITE_REGISTER_OFFSET);

   // write it!
   *UART_write_addr = c;
#endif
}

//<copyright-disclaimer-start>
//<copyright-disclaimer-start>
//  **************************************************************************************************************
//  * © 2024 Advanced Micro Devices, Inc. All rights reserved.                                                   *
//  * DISCLAIMER                                                                                                 *
//  * The information contained herein is for informational purposes only, and is subject to change              *
//  * without notice. While every precaution has been taken in the preparation of this document, it              *
//  * may contain technical inaccuracies, omissions and typographical errors, and AMD is under no                *
//  * obligation to update or otherwise correct this information.  Advanced Micro Devices, Inc. makes            *
//  * no representations or warranties with respect to the accuracy or completeness of the contents of           *
//  * this document, and assumes no liability of any kind, including the implied warranties of noninfringement,  *
//  * merchantability or fitness for particular purposes, with respect to the operation or use of AMD            *
//  * hardware, software or other products described herein.  No license, including implied or                   *
//  * arising by estoppel, to any intellectual property rights is granted by this document.  Terms and           *
//  * limitations applicable to the purchase or use of AMD’s products are as set forth in a signed agreement     *
//  * between the parties or in AMD's Standard Terms and Conditions of Sale. GD-18                               *
//  *                                                                                                            *
//  **************************************************************************************************************
//<copyright-disclaimer-end>
//<copyright-disclaimer-end>
