/** * @file * * @ingroup tms570 * * @brief Serial communication interface (SCI) functions definitions. */ /* * Copyright (c) 2014 Premysl Houdek * * Google Summer of Code 2014 at * Czech Technical University in Prague * Zikova 1903/4 * 166 36 Praha 6 * Czech Republic * * Based on LPC24xx and LPC1768 BSP * by embedded brains GmbH and others * * 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 #include #define TMS570_SCI_BUFFER_SIZE 1 /** * @brief Table including all serial drivers * * Definitions of all serial drivers */ tms570_sci_context driver_context_table[] = { { .base = RTEMS_TERMIOS_DEVICE_CONTEXT_INITIALIZER("TMS570 SCI1"), .device_name = "/dev/console", /* TMS570 UART peripheral use subset of LIN registers which are equivalent * to SCI ones */ .regs = (volatile tms570_sci_t *) &TMS570_LIN, .irq = TMS570_IRQ_SCI_LEVEL_0, }, { .base = RTEMS_TERMIOS_DEVICE_CONTEXT_INITIALIZER("TMS570 SCI2"), .device_name = "/dev/ttyS1", .regs = &TMS570_SCI, .irq = TMS570_IRQ_SCI2_LEVEL_0, } }; void tms570_sci_initialize(tms570_sci_context *ctx) { uint32_t rx_pin = 1 << 1; uint32_t tx_pin = 1 << 2; /* Resec SCI peripheral */ ctx->regs->GCR0 = TMS570_SCI_GCR0_RESET * 0; ctx->regs->GCR0 = TMS570_SCI_GCR0_RESET * 1; /* Clear all interrupt sources */ ctx->regs->CLEARINT = 0xffffffff; /* Map all interrupts to SCI INT0 line */ ctx->regs->CLEARINTLVL = 0xffffffff; ctx->regs->GCR1 = TMS570_SCI_GCR1_TXENA * 0 | TMS570_SCI_GCR1_RXENA * 0 | TMS570_SCI_GCR1_CONT * 0 | /* continue operation when debugged */ TMS570_SCI_GCR1_LOOP_BACK * 0 | TMS570_SCI_GCR1_POWERDOWN * 0 | TMS570_SCI_GCR1_SLEEP * 0 | TMS570_SCI_GCR1_SWnRST * 0 | /* reset state */ TMS570_SCI_GCR1_CLOCK * 1 | /* internal clock */ TMS570_SCI_GCR1_TIMING_MODE * 1 | TMS570_SCI_GCR1_COMM_MODE * 0; /* Setup connection of SCI peripheral Rx and Tx pins */ ctx->regs->PIO0 = rx_pin * 1 | tx_pin * 1; /* Rx and Tx pins are not GPIO */ ctx->regs->PIO3 = rx_pin * 0 | tx_pin * 0; /* Default output low */ ctx->regs->PIO1 = rx_pin * 0 | tx_pin * 0; /* Input when not used by SCI */ ctx->regs->PIO6 = rx_pin * 0 | tx_pin * 0; /* No open drain */ ctx->regs->PIO7 = rx_pin * 0 | tx_pin * 0; /* Pull-up/down enabled */ ctx->regs->PIO8 = rx_pin * 1 | tx_pin * 1; /* Select pull-up */ /* Bring device out of software reset */ ctx->regs->GCR1 |= TMS570_SCI_GCR1_SWnRST; } /** * @brief Serial drivers init function * * Initialize all serial drivers specified in driver_context_table * * @param[in] major * @param[in] minor * @param[in] arg * @retval RTEMS_SUCCESSFUL Initialization completed */ rtems_device_driver console_initialize( rtems_device_major_number major, rtems_device_minor_number minor, void *arg ) { rtems_status_code sc; #if CONSOLE_USE_INTERRUPTS const rtems_termios_device_handler *handler = &tms570_sci_handler_interrupt; #else const rtems_termios_device_handler *handler = &tms570_sci_handler_polled; #endif /* * Initialize the Termios infrastructure. If Termios has already * been initialized by another device driver, then this call will * have no effect. */ rtems_termios_initialize(); /* Initialize each device */ for ( minor = 0; minor < RTEMS_ARRAY_SIZE(driver_context_table); ++minor ) { tms570_sci_context *ctx = &driver_context_table[minor]; tms570_sci_initialize(ctx); /* * Install this device in the file system and Termios. In order * to use the console (i.e. being able to do printf, scanf etc. * on stdin, stdout and stderr), one device must be registered as * "/dev/console" (CONSOLE_DEVICE_NAME). */ sc = rtems_termios_device_install( ctx->device_name, handler, NULL, &ctx->base ); if ( sc != RTEMS_SUCCESSFUL ) { bsp_fatal(BSP_FATAL_CONSOLE_NO_DEV); } } return RTEMS_SUCCESSFUL; } /** * @brief Reads chars from HW * * Reads chars from HW peripheral specified in driver context. * TMS570 does not have HW buffer for serial line so this function can * return only 0 or 1 char * * @param[in] ctx context of the driver * @param[out] buf read data buffer * @param[in] N size of buffer * @retval x Number of read chars from peripherals */ static int tms570_sci_read_received_chars( tms570_sci_context * ctx, char * buf, int N) { if ( N < 1 ) { return 0; } if ( ctx->regs->RD != 0 ) { buf[0] = ctx->regs->RD; return 1; } return 0; } /** * @brief Enables RX interrupt * * Enables RX interrupt source of SCI peripheral * specified in the driver context. * * @param[in] ctx context of the driver * @retval Void */ static void tms570_sci_enable_interrupts(tms570_sci_context * ctx) { ctx->regs->SETINT = TMS570_SCI_SETINT_SET_RX_INT; } /** * @brief Disables RX interrupt * * Disables RX interrupt source of SCI peripheral specified in the driver * context. * * @param[in] ctx context of the driver * @retval Void */ static void tms570_sci_disable_interrupts(tms570_sci_context * ctx) { ctx->regs->CLEARINT = TMS570_SCI_CLEARINT_CLR_RX_INT; } /** * @brief Check whether driver has put char in HW * * Check whether driver has put char in HW. * This information is read from the driver context not from a peripheral. * TMS570 does not have write data buffer asociated with SCI * so the return can be only 0 or 1. * * @param[in] ctx context of the driver * @retval x */ static int tms570_sci_transmitted_chars(tms570_sci_context * ctx) { int ret; ret = ctx->tx_chars_in_hw; if ( ret == 1 ) { ctx->tx_chars_in_hw = 0; return 1; } return ret; } /** * @brief Set attributes of the HW peripheral * * Sets attributes of the HW peripheral (parity, baud rate, etc.) * * @param[in] base context of the driver * @param[in] t termios driver * @retval true peripheral setting is changed */ bool tms570_sci_set_attributes( rtems_termios_device_context *base, const struct termios *t ) { tms570_sci_context *ctx = (tms570_sci_context *) base; rtems_interrupt_lock_context lock_context; int32_t bauddiv; int32_t baudrate; uint32_t flr_tx_ready = TMS570_SCI_FLR_TX_EMPTY; /* * Test for TMS570_SCI_FLR_TXRDY is not necessary * because both SCITD and SCITXSHF has to be empty * to TX_EMPTY be asserted. But there is no interrupt * option for TX_EMPTY. Polling is used isntead. */ /* Baud rate */ baudrate = rtems_termios_baud_to_number(cfgetospeed(t)); rtems_termios_device_lock_acquire(base, &lock_context); while ( (ctx->regs->GCR1 & TMS570_SCI_GCR1_TXENA) && (ctx->regs->FLR & flr_tx_ready) != flr_tx_ready) { /* * There are pending characters in the hardware, * change in the middle of the character Tx leads * to disturb of the character and SCI engine */ rtems_interval tw; rtems_termios_device_lock_release(base, &lock_context); tw = rtems_clock_get_ticks_per_second(); tw = tw * 5 / baudrate + 1; rtems_task_wake_after( tw ); rtems_termios_device_lock_acquire(base, &lock_context); } ctx->regs->GCR1 &= ~( TMS570_SCI_GCR1_SWnRST | TMS570_SCI_GCR1_TXENA | TMS570_SCI_GCR1_RXENA ); ctx->regs->GCR1 &= ~TMS570_SCI_GCR1_STOP; /*one stop bit*/ ctx->regs->FORMAT = TMS570_SCI_FORMAT_CHAR(0x7); switch ( t->c_cflag & ( PARENB|PARODD ) ) { case ( PARENB|PARODD ): /* Odd parity */ ctx->regs->GCR1 &= ~TMS570_SCI_GCR1_PARITY; ctx->regs->GCR1 |= TMS570_SCI_GCR1_PARITY_ENA; break; case PARENB: /* Even parity */ ctx->regs->GCR1 |= TMS570_SCI_GCR1_PARITY; ctx->regs->GCR1 |= TMS570_SCI_GCR1_PARITY_ENA; break; default: case 0: case PARODD: /* No Parity */ ctx->regs->GCR1 &= ~TMS570_SCI_GCR1_PARITY_ENA; } /* Apply baudrate to the hardware */ baudrate *= 2 * 16; bauddiv = (BSP_PLL_OUT_CLOCK + baudrate / 2) / baudrate; ctx->regs->BRS = bauddiv? bauddiv - 1: 0; ctx->regs->GCR1 |= TMS570_SCI_GCR1_SWnRST | TMS570_SCI_GCR1_TXENA | TMS570_SCI_GCR1_RXENA; rtems_termios_device_lock_release(base, &lock_context); return true; } /** * @brief sci interrupt handler * * Handler checks which interrupt occured and provides nessesary maintenance * dequeue characters in termios driver whether character is send succesfully * enqueue characters in termios driver whether character is recieved * * @param[in] arg rtems_termios_tty * @retval Void */ static void tms570_sci_interrupt_handler(void * arg) { rtems_termios_tty *tty = arg; tms570_sci_context *ctx = rtems_termios_get_device_context(tty); char buf[TMS570_SCI_BUFFER_SIZE]; size_t n; /* * Check if we have received something. */ if ( (ctx->regs->FLR & TMS570_SCI_FLR_RXRDY ) == TMS570_SCI_FLR_RXRDY ) { n = tms570_sci_read_received_chars(ctx, buf, TMS570_SCI_BUFFER_SIZE); if ( n > 0 ) { /* Hand the data over to the Termios infrastructure */ rtems_termios_enqueue_raw_characters(tty, buf, n); } } /* * Check if we have something transmitted. */ if ( (ctx->regs->FLR & TMS570_SCI_FLR_TXRDY ) == TMS570_SCI_FLR_TXRDY ) { n = tms570_sci_transmitted_chars(ctx); if ( n > 0 ) { /* * Notify Termios that we have transmitted some characters. It * will call now the interrupt write function if more characters * are ready for transmission. */ rtems_termios_dequeue_characters(tty, n); } } } /** * @brief sci write function called from interrupt * * Nonblocking write function. Writes characters to HW peripheral * TMS570 does not have write data buffer asociated with SCI * so only one character can be written. * * @param[in] base context of the driver * @param[in] buf buffer of characters pending to send * @param[in] len size of the buffer * @retval Void */ static void tms570_sci_interrupt_write( rtems_termios_device_context *base, const char *buf, size_t len ) { tms570_sci_context *ctx = (tms570_sci_context *) base; if ( len > 0 ) { /* start UART TX, this will result in an interrupt when done */ ctx->regs->TD = *buf; /* character written - raise count*/ ctx->tx_chars_in_hw = 1; /* Enable TX interrupt (interrupt is edge-triggered) */ ctx->regs->SETINT = (1<<8); } else { /* No more to send, disable TX interrupts */ ctx->regs->CLEARINT = (1<<8); /* Tell close that we sent everything */ } } /** * @brief sci write function * * Blocking write function. Waits until HW peripheral is ready and then writes * character to HW peripheral. Writes all characters in the buffer. * * @param[in] base context of the driver * @param[in] buf buffer of characters pending to send * @param[in] len size of the buffer * @retval Void */ static void tms570_sci_poll_write( rtems_termios_device_context *base, const char *buf, size_t n ) { tms570_sci_context *ctx = (tms570_sci_context *) base; size_t i; /* Write */ for ( i = 0; i < n; ++i ) { while ( (ctx->regs->FLR & TMS570_SCI_FLR_TX_EMPTY ) == 0) { ; } ctx->regs->TD = buf[i]; } } /** * @brief See if there is recieved charakter to read * * read the RX flag from peripheral specified in context * * @param[in] ctx context of the driver * @retval 0 No character to read * @retval x Character ready to read */ static int TMS570_sci_can_read_char( tms570_sci_context * ctx ) { return ctx->regs->FLR & TMS570_SCI_FLR_RXRDY; } /** * @brief reads character from peripheral * * reads the recieved character from peripheral specified in context * * @param[in] ctx context of the driver * @retval x Character */ static char TMS570_sci_read_char( tms570_sci_context * ctx ) { return ctx->regs->RD; } /** * @brief sci read function * * check if there is recieved character to be read and reads it. * * @param[in] base context of the driver * @retval -1 No character to be read * @retval x Read character */ static int tms570_sci_poll_read(rtems_termios_device_context *base) { tms570_sci_context *ctx = (tms570_sci_context *) base; /* Check if a character is available */ if ( TMS570_sci_can_read_char(ctx) ) { return TMS570_sci_read_char(ctx); } else { return -1; } } /** * @brief initialization of the driver * * initialization of the HW peripheral specified in contex of the driver. * This function is called only once when opening the driver. * * @param[in] tty Termios control * @param[in] ctx context of the driver * @param[in] term Termios attributes * @param[in] args * @retval false Error occured during initialization * @retval true Driver is open and ready */ static bool tms570_sci_poll_first_open( rtems_termios_tty *tty, rtems_termios_device_context *ctx, struct termios *term, rtems_libio_open_close_args_t *args ) { bool ok; rtems_termios_set_best_baud(term, TMS570_SCI_BAUD_RATE); ok = tms570_sci_set_attributes(ctx, term); if ( !ok ) { return false; } return true; } /** * @brief initialization of the interrupt driven driver * * calls tms570_sci_poll_first_open function. * install and enables interrupts. * * @param[in] tty Termios control * @param[in] base context of the driver * @param[in] args * @retval false Error occured during initialization * @retval true Driver is open and ready */ static bool tms570_sci_interrupt_first_open( rtems_termios_tty *tty, rtems_termios_device_context *base, struct termios *term, rtems_libio_open_close_args_t *args ) { tms570_sci_context *ctx = (tms570_sci_context *) base; rtems_status_code sc; bool ret; ret = tms570_sci_poll_first_open(tty, base, term, args); if ( ret == false ) { return false; } /* Register Interrupt handler */ sc = rtems_interrupt_handler_install(ctx->irq, ctx->device_name, RTEMS_INTERRUPT_SHARED, tms570_sci_interrupt_handler, tty ); if ( sc != RTEMS_SUCCESSFUL ) { return false; } tms570_sci_enable_interrupts(ctx); return true; } /** * @brief closes sci peripheral * * @param[in] tty Termios control * @param[in] base context of the driver * @param[in] args * @retval false Error occured during initialization * @retval true Driver is open and ready */ static void tms570_sci_poll_last_close( rtems_termios_tty *tty, rtems_termios_device_context *base, rtems_libio_open_close_args_t *args ) { ; } /** * @brief closes sci peripheral of interrupt driven driver * * calls tms570_sci_poll_last_close and disables interrupts * * @param[in] tty Termios control * @param[in] base context of the driver * @param[in] args * @retval false Error occured during initialization * @retval true Driver is open and ready */ static void tms570_sci_interrupt_last_close( rtems_termios_tty *tty, rtems_termios_device_context *base, rtems_libio_open_close_args_t *args ) { tms570_sci_context *ctx = (tms570_sci_context *) base; rtems_interrupt_lock_context lock_context; rtems_interval tw; int32_t baudrate; /* Turn off RX interrupts */ rtems_termios_device_lock_acquire(base, &lock_context); tms570_sci_disable_interrupts(ctx); rtems_termios_device_lock_release(base, &lock_context); tw = rtems_clock_get_ticks_per_second(); baudrate = rtems_termios_baud_to_number(cfgetospeed(&tty->termios)); tw = tw * 10 / baudrate + 1; while ( ( ctx->regs->FLR & TMS570_SCI_FLR_TX_EMPTY ) == 0 ) { rtems_task_wake_after(tw); } /* uninstall ISR */ rtems_interrupt_handler_remove(ctx->irq, tms570_sci_interrupt_handler, tty); tms570_sci_poll_last_close(tty, base, args); } /** * @brief Struct containing definitions of polled driver functions. * * Encapsulates polled driver functions. * Use of this table is determited by not defining TMS570_USE_INTERRUPTS */ const rtems_termios_device_handler tms570_sci_handler_polled = { .first_open = tms570_sci_poll_first_open, .last_close = tms570_sci_poll_last_close, .poll_read = tms570_sci_poll_read, .write = tms570_sci_poll_write, .set_attributes = tms570_sci_set_attributes, .mode = TERMIOS_POLLED }; /** * @brief Struct containing definitions of interrupt driven driver functions. * * Encapsulates interrupt driven driver functions. * Use of this table is determited by defining TMS570_USE_INTERRUPTS */ const rtems_termios_device_handler tms570_sci_handler_interrupt = { .first_open = tms570_sci_interrupt_first_open, .last_close = tms570_sci_interrupt_last_close, .poll_read = NULL, .write = tms570_sci_interrupt_write, .set_attributes = tms570_sci_set_attributes, .mode = TERMIOS_IRQ_DRIVEN };