/* UART driver for Blackfin */ /* * Copyright (c) 2008 Kallisti Labs, Los Gatos, CA, USA * written by Allan Hessenflow * * 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 #include #include #include #include #include #include #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_cflag & CBAUD) { 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; }