/** * @file * * This file contains the TTY driver for the National Semiconductor NS16550. * * This part is widely cloned and second sourced. It is found in a number * of "Super IO" controllers. * * This driver uses the termios pseudo driver. */ /* * COPYRIGHT (c) 1998 by Radstone Technology * * THIS FILE IS PROVIDED TO YOU, THE USER, "AS IS", WITHOUT WARRANTY OF ANY * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK * AS TO THE QUALITY AND PERFORMANCE OF ALL CODE IN THIS FILE IS WITH YOU. * * You are hereby granted permission to use, copy, modify, and distribute * this file, provided that this notice, plus the above copyright notice * and disclaimer, appears in all copies. Radstone Technology will provide * no support for this code. * * COPYRIGHT (c) 1989-2012. * On-Line Applications Research Corporation (OAR). * * 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 #if defined(BSP_FEATURE_IRQ_EXTENSION) #include #elif defined(BSP_FEATURE_IRQ_LEGACY) #include #elif defined(__PPC__) || defined(__i386__) #include #define BSP_FEATURE_IRQ_LEGACY #ifdef BSP_SHARED_HANDLER_SUPPORT #define BSP_FEATURE_IRQ_LEGACY_SHARED_HANDLER_SUPPORT #endif #endif static uint32_t NS16550_GetBaudDivisor(ns16550_context *ctx, uint32_t baud) { uint32_t clock; uint32_t baudDivisor; uint32_t err; uint32_t actual; uint32_t newErr; if (ctx->clock != 0) { clock = ctx->clock; } else { clock = 115200; } baudDivisor = clock / (baud * 16); if (ctx->has_precision_clock_synthesizer) { uint32_t i; err = baud; baudDivisor = 0x0001ffff; for (i = 2; i <= 0x10000; i *= 2) { uint32_t fout; uint32_t fin; fin = i - 1; fout = (baud * fin * 16) / clock; actual = (clock * fout) / (16 * fin); newErr = actual > baud ? actual - baud : baud - actual; if (newErr < err) { err = newErr; baudDivisor = fin | (fout << 16); } } } else if (ctx->has_fractional_divider_register) { uint32_t fractionalDivider = 0x10; uint32_t mulVal; uint32_t divAddVal; err = baud; clock /= 16 * baudDivisor; for (mulVal = 1; mulVal < 16; ++mulVal) { for (divAddVal = 0; divAddVal < mulVal; ++divAddVal) { actual = (mulVal * clock) / (mulVal + divAddVal); newErr = actual > baud ? actual - baud : baud - actual; if (newErr < err) { err = newErr; fractionalDivider = (mulVal << 4) | divAddVal; } } } (*ctx->set_reg)( ctx->port, NS16550_FRACTIONAL_DIVIDER, fractionalDivider ); } else if (ctx->calculate_baud_divisor != NULL) { baudDivisor = ctx->calculate_baud_divisor(ctx, baud); } return baudDivisor; } /* * ns16550_enable_interrupts * * This routine initializes the port to have the specified interrupts masked. */ static void ns16550_enable_interrupts( ns16550_context *ctx, int mask ) { (*ctx->set_reg)(ctx->port, NS16550_INTERRUPT_ENABLE, mask); } static void ns16550_clear_and_set_interrupts( ns16550_context *ctx, uint8_t clear, uint8_t set ) { rtems_interrupt_lock_context lock_context; ns16550_get_reg get_reg = ctx->get_reg; ns16550_set_reg set_reg = ctx->set_reg; uintptr_t port = ctx->port; uint8_t val; rtems_termios_device_lock_acquire(&ctx->base, &lock_context); val = (*get_reg)(port, NS16550_INTERRUPT_ENABLE); val &= ~clear; val |= set; (*set_reg)(port, NS16550_INTERRUPT_ENABLE, val); rtems_termios_device_lock_release(&ctx->base, &lock_context); } /* * ns16550_probe */ bool ns16550_probe(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; uintptr_t pNS16550; uint8_t ucDataByte; uint32_t ulBaudDivisor; ns16550_set_reg setReg; ns16550_get_reg getReg; ctx->modem_control = SP_MODEM_IRQ; pNS16550 = ctx->port; setReg = ctx->set_reg; getReg = ctx->get_reg; /* Clear the divisor latch, clear all interrupt enables, * and reset and * disable the FIFO's. */ (*setReg)(pNS16550, NS16550_LINE_CONTROL, 0x0); ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR ); /* Set the divisor latch and set the baud rate. */ ulBaudDivisor = NS16550_GetBaudDivisor(ctx, ctx->initial_baud); ctx->baud_divisor = ulBaudDivisor; ucDataByte = SP_LINE_DLAB; (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucDataByte); /* XXX */ (*setReg)(pNS16550,NS16550_DIVISOR_LATCH_L,(uint8_t)(ulBaudDivisor & 0xffU)); (*setReg)( pNS16550,NS16550_DIVISOR_LATCH_M, (uint8_t)(( ulBaudDivisor >> 8 ) & 0xffU ) ); /* Clear the divisor latch and set the character size to eight bits */ /* with one stop bit and no parity checking. */ ucDataByte = EIGHT_BITS; ctx->line_control = ucDataByte; if (ctx->has_precision_clock_synthesizer) { uint8_t fcr; /* * Enable precision clock synthesizer. This must be done with DLAB == 1 in * the line control register. */ fcr = (*getReg)(pNS16550, NS16550_FIFO_CONTROL ); fcr |= 0x10; (*setReg)(pNS16550, NS16550_FIFO_CONTROL, fcr); (*setReg)(pNS16550, NS16550_SCRATCH_PAD, (uint8_t)(ulBaudDivisor >> 24)); (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucDataByte ); (*setReg)(pNS16550, NS16550_SCRATCH_PAD, (uint8_t)(ulBaudDivisor >> 16)); } else { (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucDataByte); } /* Enable and reset transmit and receive FIFOs. TJA */ ucDataByte = SP_FIFO_ENABLE; (*setReg)(pNS16550, NS16550_FIFO_CONTROL, ucDataByte); ucDataByte = SP_FIFO_ENABLE | SP_FIFO_RXRST | SP_FIFO_TXRST; (*setReg)(pNS16550, NS16550_FIFO_CONTROL, ucDataByte); ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR); /* Set data terminal ready. */ /* And open interrupt tristate line */ (*setReg)(pNS16550, NS16550_MODEM_CONTROL,ctx->modem_control); (*getReg)(pNS16550, NS16550_LINE_STATUS ); (*getReg)(pNS16550, NS16550_RECEIVE_BUFFER ); return true; } static size_t ns16550_write_to_fifo( const ns16550_context *ctx, const char *buf, size_t len ) { uintptr_t port = ctx->port; ns16550_set_reg set = ctx->set_reg; size_t out = len > SP_FIFO_SIZE ? SP_FIFO_SIZE : len; size_t i; for (i = 0; i < out; ++i) { (*set)(port, NS16550_TRANSMIT_BUFFER, buf[i]); } return out; } /** * @brief Process interrupt. */ static void ns16550_isr(void *arg) { rtems_termios_tty *tty = arg; ns16550_context *ctx = rtems_termios_get_device_context(tty); uintptr_t port = ctx->port; ns16550_get_reg get = ctx->get_reg; int i = 0; char buf [SP_FIFO_SIZE]; /* Iterate until no more interrupts are pending */ do { /* Fetch received characters */ for (i = 0; i < SP_FIFO_SIZE; ++i) { if ((get( port, NS16550_LINE_STATUS) & SP_LSR_RDY) != 0) { buf [i] = (char) get(port, NS16550_RECEIVE_BUFFER); } else { break; } } /* Enqueue fetched characters */ rtems_termios_enqueue_raw_characters(tty, buf, i); /* Do transmit */ if (ctx->out_total > 0 && (get(port, NS16550_LINE_STATUS) & SP_LSR_THOLD) != 0) { size_t current = ctx->out_current; ctx->out_buf += current; ctx->out_remaining -= current; if (ctx->out_remaining > 0) { ctx->out_current = ns16550_write_to_fifo(ctx, ctx->out_buf, ctx->out_remaining); } else { rtems_termios_dequeue_characters(tty, ctx->out_total); } } } while ((get( port, NS16550_INTERRUPT_ID) & SP_IID_0) == 0); } static void ns16550_isr_task(void *arg) { rtems_termios_tty *tty = arg; ns16550_context *ctx = rtems_termios_get_device_context(tty); uint8_t status = (*ctx->get_reg)(ctx->port, NS16550_LINE_STATUS); if ((status & SP_LSR_RDY) != 0) { ns16550_clear_and_set_interrupts(ctx, SP_INT_RX_ENABLE, 0); rtems_termios_rxirq_occured(tty); } if (ctx->out_total > 0 && (status & SP_LSR_THOLD) != 0) { size_t current = ctx->out_current; ctx->out_buf += current; ctx->out_remaining -= current; if (ctx->out_remaining > 0) { ctx->out_current = ns16550_write_to_fifo(ctx, ctx->out_buf, ctx->out_remaining); } else { size_t done = ctx->out_total; ctx->out_total = 0; ns16550_clear_and_set_interrupts(ctx, SP_INT_TX_ENABLE, 0); rtems_termios_dequeue_characters(tty, done); } } } static int ns16550_read_task(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; uintptr_t port = ctx->port; ns16550_get_reg get = ctx->get_reg; char buf[SP_FIFO_SIZE]; int i; for (i = 0; i < SP_FIFO_SIZE; ++i) { if ((get(port, NS16550_LINE_STATUS) & SP_LSR_RDY) != 0) { buf[i] = (char) get(port, NS16550_RECEIVE_BUFFER); } else { break; } } rtems_termios_enqueue_raw_characters(ctx->tty, buf, i); ns16550_clear_and_set_interrupts(ctx, 0, SP_INT_RX_ENABLE); return -1; } /* * ns16550_initialize_interrupts * * This routine initializes the port to operate in interrupt driver mode. */ static void ns16550_initialize_interrupts( struct rtems_termios_tty *tty, ns16550_context *ctx, void (*isr)(void *) ) { #ifdef BSP_FEATURE_IRQ_EXTENSION { rtems_status_code sc = RTEMS_SUCCESSFUL; sc = rtems_interrupt_handler_install( ctx->irq, "NS16550", RTEMS_INTERRUPT_SHARED, isr, tty ); if (sc != RTEMS_SUCCESSFUL) { /* FIXME */ printk( "%s: Error: Install interrupt handler\n", __func__); rtems_fatal_error_occurred( 0xdeadbeef); } } #elif defined(BSP_FEATURE_IRQ_LEGACY) { int rv = 0; #ifdef BSP_FEATURE_IRQ_LEGACY_SHARED_HANDLER_SUPPORT rtems_irq_connect_data cd = { ctx->irq, isr, tty, NULL, NULL, NULL, NULL }; rv = BSP_install_rtems_shared_irq_handler( &cd); #else rtems_irq_connect_data cd = { ctx->irq, isr, tty, NULL, NULL, NULL }; rv = BSP_install_rtems_irq_handler( &cd); #endif if (rv == 0) { /* FIXME */ printk( "%s: Error: Install interrupt handler\n", __func__); rtems_fatal_error_occurred( 0xdeadbeef); } } #endif } /* * ns16550_open */ static bool ns16550_open( struct rtems_termios_tty *tty, rtems_termios_device_context *base, struct termios *term, rtems_libio_open_close_args_t *args ) { ns16550_context *ctx = (ns16550_context *) base; ctx->tty = tty; /* Set initial baud */ rtems_termios_set_initial_baud(tty, ctx->initial_baud); if (tty->handler.mode == TERMIOS_IRQ_DRIVEN) { ns16550_initialize_interrupts(tty, ctx, ns16550_isr); ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR_EXCEPT_TX); } else if (tty->handler.mode == TERMIOS_TASK_DRIVEN) { ns16550_initialize_interrupts(tty, ctx, ns16550_isr_task); ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR_EXCEPT_TX); } return true; } static void ns16550_cleanup_interrupts( struct rtems_termios_tty *tty, ns16550_context *ctx, void (*isr)(void *) ) { #if defined(BSP_FEATURE_IRQ_EXTENSION) rtems_status_code sc = RTEMS_SUCCESSFUL; sc = rtems_interrupt_handler_remove( ctx->irq, isr, tty ); if (sc != RTEMS_SUCCESSFUL) { /* FIXME */ printk("%s: Error: Remove interrupt handler\n", __func__); rtems_fatal_error_occurred(0xdeadbeef); } #elif defined(BSP_FEATURE_IRQ_LEGACY) int rv = 0; rtems_irq_connect_data cd = { .name = ctx->irq, .hdl = isr, .handle = tty }; rv = BSP_remove_rtems_irq_handler(&cd); if (rv == 0) { /* FIXME */ printk("%s: Error: Remove interrupt handler\n", __func__); rtems_fatal_error_occurred(0xdeadbeef); } #endif } /* * ns16550_close */ static void ns16550_close( struct rtems_termios_tty *tty, rtems_termios_device_context *base, rtems_libio_open_close_args_t *args ) { ns16550_context *ctx = (ns16550_context *) base; ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR); if (tty->handler.mode == TERMIOS_IRQ_DRIVEN) { ns16550_cleanup_interrupts(tty, ctx, ns16550_isr); } else if (tty->handler.mode == TERMIOS_TASK_DRIVEN) { ns16550_cleanup_interrupts(tty, ctx, ns16550_isr_task); } } /** * @brief Polled write for NS16550. */ void ns16550_polled_putchar(rtems_termios_device_context *base, char out) { ns16550_context *ctx = (ns16550_context *) base; uintptr_t port = ctx->port; ns16550_get_reg get = ctx->get_reg; ns16550_set_reg set = ctx->set_reg; uint32_t status = 0; rtems_interrupt_lock_context lock_context; /* Save port interrupt mask */ uint32_t interrupt_mask = get( port, NS16550_INTERRUPT_ENABLE); /* Disable port interrupts */ ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR); while (true) { /* Try to transmit the character in a critical section */ rtems_termios_device_lock_acquire(&ctx->base, &lock_context); /* Read the transmitter holding register and check it */ status = get( port, NS16550_LINE_STATUS); if ((status & SP_LSR_THOLD) != 0) { /* Transmit character */ set( port, NS16550_TRANSMIT_BUFFER, out); /* Finished */ rtems_termios_device_lock_release(&ctx->base, &lock_context); break; } else { rtems_termios_device_lock_release(&ctx->base, &lock_context); } /* Wait for transmitter holding register to be empty */ do { status = get( port, NS16550_LINE_STATUS); } while ((status & SP_LSR_THOLD) == 0); } /* Restore port interrupt mask */ set( port, NS16550_INTERRUPT_ENABLE, interrupt_mask); } /* * These routines provide control of the RTS and DTR lines */ /* * ns16550_assert_RTS */ static void ns16550_assert_RTS(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; rtems_interrupt_lock_context lock_context; /* * Assert RTS */ rtems_termios_device_lock_acquire(base, &lock_context); ctx->modem_control |= SP_MODEM_RTS; (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL, ctx->modem_control); rtems_termios_device_lock_release(base, &lock_context); } /* * ns16550_negate_RTS */ static void ns16550_negate_RTS(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; rtems_interrupt_lock_context lock_context; /* * Negate RTS */ rtems_termios_device_lock_acquire(base, &lock_context); ctx->modem_control &= ~SP_MODEM_RTS; (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL, ctx->modem_control); rtems_termios_device_lock_release(base, &lock_context); } /* * These flow control routines utilise a connection from the local DTR * line to the remote CTS line */ /* * ns16550_assert_DTR */ static void ns16550_assert_DTR(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; rtems_interrupt_lock_context lock_context; /* * Assert DTR */ rtems_termios_device_lock_acquire(base, &lock_context); ctx->modem_control |= SP_MODEM_DTR; (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL, ctx->modem_control); rtems_termios_device_lock_release(base, &lock_context); } /* * ns16550_negate_DTR */ static void ns16550_negate_DTR(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; rtems_interrupt_lock_context lock_context; /* * Negate DTR */ rtems_termios_device_lock_acquire(base, &lock_context); ctx->modem_control &=~SP_MODEM_DTR; (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL,ctx->modem_control); rtems_termios_device_lock_release(base, &lock_context); } /* * ns16550_set_attributes * * This function sets the channel to reflect the requested termios * port settings. */ static bool ns16550_set_attributes( rtems_termios_device_context *base, const struct termios *t ) { ns16550_context *ctx = (ns16550_context *) base; uint32_t pNS16550; uint32_t ulBaudDivisor; uint8_t ucLineControl; uint32_t baud_requested; ns16550_set_reg setReg; pNS16550 = ctx->port; setReg = ctx->set_reg; /* * Calculate the baud rate divisor * * Assert ensures there is no division by 0. */ baud_requested = rtems_termios_baud_to_number(t->c_ospeed); _Assert( baud_requested != 0 ); ulBaudDivisor = NS16550_GetBaudDivisor(ctx, baud_requested); ucLineControl = 0; /* * Parity */ if (t->c_cflag & PARENB) { ucLineControl |= SP_LINE_PAR; if (!(t->c_cflag & PARODD)) ucLineControl |= SP_LINE_ODD; } /* * Character Size */ if (t->c_cflag & CSIZE) { switch (t->c_cflag & CSIZE) { case CS5: ucLineControl |= FIVE_BITS; break; case CS6: ucLineControl |= SIX_BITS; break; case CS7: ucLineControl |= SEVEN_BITS; break; case CS8: ucLineControl |= EIGHT_BITS; break; } } else { ucLineControl |= EIGHT_BITS; /* default to 9600,8,N,1 */ } /* * Stop Bits */ if (t->c_cflag & CSTOPB) { ucLineControl |= SP_LINE_STOP; /* 2 stop bits */ } else { ; /* 1 stop bit */ } /* * Now actually set the chip */ if (ulBaudDivisor != ctx->baud_divisor || ucLineControl != ctx->line_control) { rtems_interrupt_lock_context lock_context; ctx->baud_divisor = ulBaudDivisor; ctx->line_control = ucLineControl; rtems_termios_device_lock_acquire(base, &lock_context); /* * Set the baud rate * * NOTE: When the Divisor Latch Access Bit (DLAB) is set to 1, * the transmit buffer and interrupt enable registers * turn into the LSB and MSB divisor latch registers. */ (*setReg)(pNS16550, NS16550_LINE_CONTROL, SP_LINE_DLAB); (*setReg)(pNS16550, NS16550_DIVISOR_LATCH_L, ulBaudDivisor&0xff); (*setReg)(pNS16550, NS16550_DIVISOR_LATCH_M, (ulBaudDivisor>>8)&0xff); /* * Now write the line control */ if (ctx->has_precision_clock_synthesizer) { (*setReg)(pNS16550, NS16550_SCRATCH_PAD, (uint8_t)(ulBaudDivisor >> 24)); (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucLineControl ); (*setReg)(pNS16550, NS16550_SCRATCH_PAD, (uint8_t)(ulBaudDivisor >> 16)); } else { (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucLineControl ); } rtems_termios_device_lock_release(base, &lock_context); } return true; } /** * @brief Transmits up to @a len characters from @a buf. * * This routine is invoked either from task context with disabled interrupts to * start a new transmission process with exactly one character in case of an * idle output state or from the interrupt handler to refill the transmitter. * * Returns always zero. */ static void ns16550_write_support_int( rtems_termios_device_context *base, const char *buf, size_t len ) { ns16550_context *ctx = (ns16550_context *) base; ctx->out_total = len; if (len > 0) { ctx->out_remaining = len; ctx->out_buf = buf; ctx->out_current = ns16550_write_to_fifo(ctx, buf, len); ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR); } else { ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR_EXCEPT_TX); } } static void ns16550_write_support_task( rtems_termios_device_context *base, const char *buf, size_t len ) { ns16550_context *ctx = (ns16550_context *) base; ctx->out_total = len; if (len > 0) { ctx->out_remaining = len; ctx->out_buf = buf; ctx->out_current = ns16550_write_to_fifo(ctx, buf, len); ns16550_clear_and_set_interrupts(ctx, 0, SP_INT_TX_ENABLE); } } /* * ns16550_write_support_polled * * Console Termios output entry point. * */ static void ns16550_write_support_polled( rtems_termios_device_context *base, const char *buf, size_t len ) { size_t nwrite = 0; /* * poll each byte in the string out of the port. */ while (nwrite < len) { /* * transmit character */ ns16550_polled_putchar(base, *buf++); nwrite++; } } /* * Debug gets() support */ int ns16550_polled_getchar(rtems_termios_device_context *base) { ns16550_context *ctx = (ns16550_context *) base; uint32_t pNS16550; unsigned char ucLineStatus; uint8_t cChar; ns16550_get_reg getReg; pNS16550 = ctx->port; getReg = ctx->get_reg; ucLineStatus = (*getReg)(pNS16550, NS16550_LINE_STATUS); if (ucLineStatus & SP_LSR_RDY) { cChar = (*getReg)(pNS16550, NS16550_RECEIVE_BUFFER); return (int)cChar; } return -1; } /* * Flow control is only supported when using interrupts */ const rtems_termios_device_flow ns16550_flow_rtscts = { .stop_remote_tx = ns16550_negate_RTS, .start_remote_tx = ns16550_assert_RTS }; const rtems_termios_device_flow ns16550_flow_dtrcts = { .stop_remote_tx = ns16550_negate_DTR, .start_remote_tx = ns16550_assert_DTR }; const rtems_termios_device_handler ns16550_handler_interrupt = { .first_open = ns16550_open, .last_close = ns16550_close, .poll_read = NULL, .write = ns16550_write_support_int, .set_attributes = ns16550_set_attributes, .mode = TERMIOS_IRQ_DRIVEN }; const rtems_termios_device_handler ns16550_handler_polled = { .first_open = ns16550_open, .last_close = ns16550_close, .poll_read = ns16550_polled_getchar, .write = ns16550_write_support_polled, .set_attributes = ns16550_set_attributes, .mode = TERMIOS_POLLED }; const rtems_termios_device_handler ns16550_handler_task = { .first_open = ns16550_open, .last_close = ns16550_close, .poll_read = ns16550_read_task, .write = ns16550_write_support_task, .set_attributes = ns16550_set_attributes, .mode = TERMIOS_TASK_DRIVEN };