summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libcpu/bfin/serial/uart.c
blob: 617c59dab3005f4927201f91452a3111e76f5e33 (plain) (tree)
1
2
3
4
5
6
7
8
9
                            


   




                                                                
                                         
   
 






                               
                           

                 


                                

                                       

                              
        
                
 
                                                       
 

                                                                      
              
                                                         







                                                                      

                                                         








                        

                                            
                
 
                                                       





                                                                              




                                        

                                                                

                                         

                                        
               

 




                                                             

                                                            


                              
 











                                                    
 
 






                                                           
   
 


                                                                            
 
                                                                   
 
                                                                    
 



















                                                                               

 






                           

                                                                  
                



                   
                                                       
                              




















                                       
   

                                                         
                                     


                                        
          
                                        

                                                 







                                         














                                                                              







                                                                               

                                                                          

                                         



                 



                                                         
             
   






                                                                   






                                                                   
           

 
   

                                  




                                      
 
 







                                                                                

                               




                                       
     



                                  
 







                                                                      

 








                                                                              

                                                                    


                                           



                 
                                                                      











                                                                  












                                                                                


           





                                                                          

                                  















                                                                             

                                  

























                                                                                
   




                              
                           



                             

 











                                                                     
                                                                   
                                                 
                                                          




                                             
                                                        







                                                             
    




                                                                    
                                                             







                                                             

    














                                                                    
                                                                                
                                














                                                                       
 


                                                          
                    



            





                                         




                                       




















                                                                    
/*  UART driver for Blackfin
 */

/*
 *  Copyright (c) 2008 Kallisti Labs, Los Gatos, CA, USA
 *             written by Allan Hessenflow <allanh@kallisti.com>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.org/license/LICENSE.
 */

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/termiostypes.h>
#include <termios.h>
#include <stdlib.h>

#include <libcpu/uartRegs.h>
#include <libcpu/dmaRegs.h>
#include "uart.h"

/* flags */
#define BFIN_UART_XMIT_BUSY 0x01

static bfin_uart_config_t *uartsConfig;

static int pollRead(int minor)
{
  int c;
  uint32_t base;

  base = uartsConfig->channels[minor].uart_baseAddress;

  /* check to see if driver is using interrupts so this call will be
     harmless (though non-functional) in case some debug code tries to
     use it */
  if (!uartsConfig->channels[minor].uart_useInterrupts &&
      *((uint16_t volatile *) (base + UART_LSR_OFFSET)) & UART_LSR_DR)
    c = *((uint16_t volatile *) (base + UART_RBR_OFFSET));
  else
    c = -1;

  return c;
}

char bfin_uart_poll_read(rtems_device_minor_number minor)
{
  int c;

  do {
    c = pollRead(minor);
  } while (c == -1);

  return c;
}

void bfin_uart_poll_write(int minor, char c)
{
  uint32_t base;

  base = uartsConfig->channels[minor].uart_baseAddress;

  while (!(*((uint16_t volatile *) (base + UART_LSR_OFFSET)) & UART_LSR_THRE))
    ;
  *(uint16_t volatile *) (base + UART_THR_OFFSET) = c;
}

/*
 *  Console Termios Support Entry Points
 *
 */

static ssize_t pollWrite(int minor, const char *buf, size_t len)
{
  size_t count;
  for ( count = 0; count < len; count++ )
    bfin_uart_poll_write(minor, *buf++);

  return count;
}

/**
 * Routine to initialize the hardware. It initialize the DMA,
 * interrupt if required.
 * @param channel channel information
 */
static void initializeHardware(bfin_uart_channel_t *channel)
{
  uint16_t divisor        = 0;
  uint32_t base           = 0;
  uint32_t tx_dma_base    = 0;

  if ( NULL == channel ) {
    return;
  }

  base        = channel->uart_baseAddress;
  tx_dma_base = channel->uart_txDmaBaseAddress;
  /**
   * RX based DMA and interrupt is not supported yet
   * uint32_t tx_dma_base    = 0;
   *
   * rx_dma_base = channel->uart_rxDmaBaseAddress;
   */


  *(uint16_t volatile *) (base + UART_IER_OFFSET) = 0;

  if ( 0 != channel->uart_baud) {
    divisor = (uint16_t) (uartsConfig->freq /
        (channel->uart_baud * 16));
  } else {
    divisor = (uint16_t) (uartsConfig->freq / (9600 * 16));
  }

  *(uint16_t volatile *) (base + UART_LCR_OFFSET) = UART_LCR_DLAB;
  *(uint16_t volatile *) (base + UART_DLL_OFFSET) = (divisor & 0xff);
  *(uint16_t volatile *) (base + UART_DLH_OFFSET) = ((divisor >> 8) & 0xff);

  *(uint16_t volatile *) (base + UART_LCR_OFFSET) = UART_LCR_WLS_8;

  *(uint16_t volatile *) (base + UART_GCTL_OFFSET) = UART_GCTL_UCEN;

  /**
   * To clear previous status
   * divisor is a temp variable here
   */
  divisor = *(uint16_t volatile *) (base + UART_LSR_OFFSET);
  divisor = *(uint16_t volatile *) (base + UART_RBR_OFFSET);
  divisor = *(uint16_t volatile *) (base + UART_IIR_OFFSET);

  if ( channel->uart_useDma ) {
    *(uint16_t  volatile *)(tx_dma_base + DMA_CONFIG_OFFSET) = 0;
    *(uint16_t  volatile *)(tx_dma_base + DMA_CONFIG_OFFSET) = DMA_CONFIG_DI_EN
        | DMA_CONFIG_SYNC ;
    *(uint16_t  volatile *)(tx_dma_base + DMA_IRQ_STATUS_OFFSET) |=
        DMA_IRQ_STATUS_DMA_DONE | DMA_IRQ_STATUS_DMA_ERR;

  } else {
    /**
    * We use polling or interrupts only sending one char at a time :(
    */
  }
}


/**
 * Set the UART attributes.
 * @param minor
 * @param termios
 * @return
 */
static int setAttributes(int minor, const struct termios *termios)
{
  uint32_t base;
  int baud;
  uint16_t divisor;
  uint16_t lcr;

  base = uartsConfig->channels[minor].uart_baseAddress;
  switch (termios->c_ospeed) {
    case B0:      baud = 0;      break;
    case B50:     baud = 50;     break;
    case B75:     baud = 75;     break;
    case B110:    baud = 110;    break;
    case B134:    baud = 134;    break;
    case B150:    baud = 150;    break;
    case B200:    baud = 200;    break;
    case B300:    baud = 300;    break;
    case B600:    baud = 600;    break;
    case B1200:   baud = 1200;   break;
    case B1800:   baud = 1800;   break;
    case B2400:   baud = 2400;   break;
    case B4800:   baud = 4800;   break;
    case B9600:   baud = 9600;   break;
    case B19200:  baud = 19200;  break;
    case B38400:  baud = 38400;  break;
    case B57600:  baud = 57600;  break;
    case B115200: baud = 115200; break;
    case B230400: baud = 230400; break;
    case B460800: baud = 460800; break;
    default: baud = -1; break;
  }
  if (baud > 0 && uartsConfig->channels[minor].uart_baud)
    baud = uartsConfig->channels[minor].uart_baud;
  switch (termios->c_cflag & CSIZE) {
  case CS5: lcr = UART_LCR_WLS_5; break;
  case CS6: lcr = UART_LCR_WLS_6; break;
  case CS7: lcr = UART_LCR_WLS_7; break;
  default:
  case CS8: lcr = UART_LCR_WLS_8; break;
  }
  switch (termios->c_cflag & (PARENB | PARODD)) {
    case PARENB:
      lcr |= UART_LCR_PEN | UART_LCR_EPS;
      break;
    case PARENB | PARODD:
      lcr |= UART_LCR_PEN;
      break;
    default:
      break;
  }
  if (termios->c_cflag & CSTOPB)
    lcr |= UART_LCR_STB;

  if (baud > 0) {
    divisor = (uint16_t) (uartsConfig->freq / (baud * 16));
    *(uint16_t volatile *) (base + UART_LCR_OFFSET) = lcr | UART_LCR_DLAB;
    *(uint16_t volatile *) (base + UART_DLL_OFFSET) = (divisor & 0xff);
    *(uint16_t volatile *) (base + UART_DLH_OFFSET) = ((divisor >> 8) & 0xff);
  }
  *(uint16_t volatile *) (base + UART_LCR_OFFSET) = lcr;

  return 0;
}

/**
 * Interrupt based uart tx routine. The routine writes one character at a time.
 *
 * @param minor Minor number to indicate uart number
 * @param buf Character buffer which stores characters to be transmitted.
 * @param len Length of buffer to be transmitted.
 * @return
 */
static ssize_t uart_interruptWrite(int minor, const char *buf, size_t len)
{
  uint32_t              base      = 0;
  bfin_uart_channel_t*  channel   = NULL;

  /**
   * Sanity Check
   */
  if (
    NULL == buf || NULL == channel || NULL == uartsConfig
      || minor < 0 || 0 == len
  ) {
    return 0;
  }

  channel = &(uartsConfig->channels[minor]);

  if ( NULL == channel || channel->flags &  BFIN_UART_XMIT_BUSY ) {
    return 0;
  }

  base = channel->uart_baseAddress;

  channel->flags |= BFIN_UART_XMIT_BUSY;
  channel->length = 1;
  *(uint16_t volatile *) (base + UART_THR_OFFSET) = *buf;
  *(uint16_t volatile *) (base + UART_IER_OFFSET) = UART_IER_ETBEI;

  return 0;
}

/**
 * This function implements RX ISR
 */
void bfinUart_rxIsr(void *_arg)
{
  /**
   * TODO: UART RX ISR implementation.
   */
}

/**
 * This function implements TX ISR. The function gets called when the TX FIFO is
 * empty. It clears the interrupt and dequeues the character. It only tx one
 * character at a time.
 *
 * TODO: error handling.
 * @param _arg gets the channel information.
 */
void bfinUart_txIsr(void *_arg)
{
  bfin_uart_channel_t*  channel = NULL;
  uint32_t              base    = 0;

  /**
   * Sanity check
   */
  if (NULL == _arg) {
    /** It should never be NULL */
    return;
  }

  channel = (bfin_uart_channel_t *) _arg;

  base = channel->uart_baseAddress;

  *(uint16_t volatile *) (base + UART_IER_OFFSET) &= ~UART_IER_ETBEI;
  channel->flags &= ~BFIN_UART_XMIT_BUSY;

  rtems_termios_dequeue_characters(channel->termios, channel->length);
}

/**
 * interrupt based DMA write Routine. It configure the DMA to write len bytes.
 * The DMA supports 64K data only.
 *
 * @param minor Identification number of the UART.
 * @param buf Character buffer pointer
 * @param len length of data items to be written
 * @return data already written
 */
static ssize_t uart_DmaWrite(int minor, const char *buf, size_t len)
{
  uint32_t              base        = 0;
  bfin_uart_channel_t*  channel     = NULL;
  uint32_t              tx_dma_base = 0;

  /**
   * Sanity Check
   */
  if ( NULL == buf || 0 > minor || NULL == uartsConfig || 0 == len ) {
    return 0;
  }

  channel = &(uartsConfig->channels[minor]);

  /**
   * Sanity Check and check for transmit busy.
   */
  if ( NULL == channel || BFIN_UART_XMIT_BUSY & channel->flags ) {
    return 0;
  }

  base        = channel->uart_baseAddress;
  tx_dma_base = channel->uart_txDmaBaseAddress;

  channel->flags |= BFIN_UART_XMIT_BUSY;
  channel->length = len;

  *(uint16_t volatile *) (tx_dma_base + DMA_CONFIG_OFFSET) &= ~DMA_CONFIG_DMAEN;
  *(uint32_t volatile *) (tx_dma_base + DMA_START_ADDR_OFFSET) = (uint32_t)buf;
  *(uint16_t volatile *) (tx_dma_base + DMA_X_COUNT_OFFSET) = channel->length;
  *(uint16_t volatile *) (tx_dma_base + DMA_X_MODIFY_OFFSET) = 1;
  *(uint16_t volatile *) (tx_dma_base + DMA_CONFIG_OFFSET) |= DMA_CONFIG_DMAEN;
  *(uint16_t volatile *) (base + UART_IER_OFFSET) = UART_IER_ETBEI;

  return 0;
}

/**
 * RX DMA ISR.
 * The polling route is used for receiving the characters. This is a place
 * holder for future implementation.
 * @param _arg
 */
void bfinUart_rxDmaIsr(void *_arg)
{
/**
 * TODO: Implementation of RX DMA
 */
}

/**
 * This function implements TX dma ISR. It clears the IRQ and dequeues a char
 * The channel argument will have the base address. Since there are two uart
 * and both the uarts can use the same tx dma isr.
 *
 * TODO: 1. Error checking 2. sending correct length ie after looking at the
 * number of elements the uart transmitted.
 *
 * @param _arg argument passed to the interrupt handler. It contains the
 * channel argument.
 */
void bfinUart_txDmaIsr(void *_arg)
{
  bfin_uart_channel_t*  channel     = NULL;
  uint32_t              tx_dma_base = 0;

  /**
   * Sanity check
   */
  if (NULL == _arg) {
    /** It should never be NULL */
    return;
  }

  channel = (bfin_uart_channel_t *) _arg;

  tx_dma_base = channel->uart_txDmaBaseAddress;

  if ((*(uint16_t volatile *) (tx_dma_base + DMA_IRQ_STATUS_OFFSET)
      & DMA_IRQ_STATUS_DMA_DONE)) {

    *(uint16_t volatile *) (tx_dma_base + DMA_IRQ_STATUS_OFFSET)
        		    |= DMA_IRQ_STATUS_DMA_DONE | DMA_IRQ_STATUS_DMA_ERR;
    channel->flags &= ~BFIN_UART_XMIT_BUSY;
    rtems_termios_dequeue_characters(channel->termios, channel->length);
  } else {
    /* UART DMA did not generate interrupt.
     * This routine must not be called.
     */
  }
}

/**
 * Function called during exit
 */
static void uart_exit(void)
{
  /**
   * TODO: Flushing of quques
   */
}

/**
 * Opens the device in different modes. The supported modes are
 * 1. Polling
 * 2. Interrupt
 * 3. DMA
 * At exit the uart_Exit function will be called to flush the device.
 *
 * @param major Major number of the device
 * @param minor Minor number of the device
 * @param arg
 * @return
 */
rtems_device_driver bfin_uart_open(rtems_device_major_number major,
    rtems_device_minor_number minor, void *arg) {
  rtems_status_code             sc    = RTEMS_NOT_DEFINED;
  rtems_libio_open_close_args_t *args = NULL;

  /**
   * Callback function for polling
   */
  static const rtems_termios_callbacks pollCallbacks = {
      NULL,                        /* firstOpen */
      NULL,                        /* lastClose */
      pollRead,                    /* pollRead */
      pollWrite,                   /* write */
      setAttributes,               /* setAttributes */
      NULL,                        /* stopRemoteTx */
      NULL,                        /* startRemoteTx */
      TERMIOS_POLLED               /* outputUsesInterrupts */
  };

  /**
   * Callback function for interrupt based transfers without DMA.
   * We use interrupts for writing only. For reading we use polling.
   */
  static const rtems_termios_callbacks interruptCallbacks = {
      NULL,                        /* firstOpen */
      NULL,                        /* lastClose */
      pollRead,                    /* pollRead */
      uart_interruptWrite,              /* write */
      setAttributes,               /* setAttributes */
      NULL,                        /* stopRemoteTx */
      NULL,                        /* startRemoteTx */
      TERMIOS_IRQ_DRIVEN           /* outputUsesInterrupts */
  };

  /**
   * Callback function for interrupt based DMA transfers.
   * We use interrupts for writing only. For reading we use polling.
   */
  static const rtems_termios_callbacks interruptDmaCallbacks = {
      NULL,                        /* firstOpen */
      NULL,                        /* lastClose */
      NULL,                        /* pollRead */
      uart_DmaWrite,              /* write */
      setAttributes,               /* setAttributes */
      NULL,                        /* stopRemoteTx */
      NULL,                        /* startRemoteTx */
      TERMIOS_IRQ_DRIVEN           /* outputUsesInterrupts */
  };

  if ( NULL == uartsConfig || 0 > minor || minor >= uartsConfig->num_channels) {
    return RTEMS_INVALID_NUMBER;
  }

  /**
   * Opens device for handling uart send request either by
   * 1. interrupt with DMA
   * 2. interrupt based
   * 3. Polling
   */
  if ( uartsConfig->channels[minor].uart_useDma ) {
    sc = rtems_termios_open(major, minor, arg, &interruptDmaCallbacks);
  } else {
    sc = rtems_termios_open(major, minor, arg,
        uartsConfig->channels[minor].uart_useInterrupts ?
            &interruptCallbacks : &pollCallbacks);
  }

  args = arg;
  uartsConfig->channels[minor].termios = args->iop->data1;

  atexit(uart_exit);

  return sc;
}

/**
* Uart initialization function.
* @param major major number of the device
* @param config configuration parameters
* @return rtems status code
*/
rtems_status_code bfin_uart_initialize(
  rtems_device_major_number major,
  bfin_uart_config_t *config
)
{
  rtems_status_code sc = RTEMS_NOT_DEFINED;
  int               i  = 0;

  rtems_termios_initialize();

  /*
   *  Register Device Names
   */
  uartsConfig = config;
  for (i = 0; i < config->num_channels; i++) {
    config->channels[i].termios = NULL;
    config->channels[i].flags = 0;
    initializeHardware(&(config->channels[i]));
    sc = rtems_io_register_name(config->channels[i].name, major, i);
    if (RTEMS_SUCCESSFUL != sc) {
      return sc;
    }
  }

  return sc;
}