source: rtems/c/src/libchip/serial/ns16550-context.c @ 6ec438e

4.115
Last change on this file since 6ec438e was 6ec438e, checked in by Sebastian Huber <sebastian.huber@…>, on 10/07/14 at 06:29:16

libchip/serial: Add alternative NS16550 driver

Use the Termios device API.

  • Property mode set to 100644
File size: 17.0 KB
Line 
1/**
2 *  @file
3 * 
4 *  This file contains the TTY driver for the National Semiconductor NS16550.
5 *
6 *  This part is widely cloned and second sourced.  It is found in a number
7 *  of "Super IO" controllers.
8 *
9 *  This driver uses the termios pseudo driver.
10 */
11
12/*
13 *  COPYRIGHT (c) 1998 by Radstone Technology
14 *
15 *  THIS FILE IS PROVIDED TO YOU, THE USER, "AS IS", WITHOUT WARRANTY OF ANY
16 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
17 *  IMPLIED WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
18 *  AS TO THE QUALITY AND PERFORMANCE OF ALL CODE IN THIS FILE IS WITH YOU.
19 *
20 *  You are hereby granted permission to use, copy, modify, and distribute
21 *  this file, provided that this notice, plus the above copyright notice
22 *  and disclaimer, appears in all copies. Radstone Technology will provide
23 *  no support for this code.
24 *
25 *  COPYRIGHT (c) 1989-2012.
26 *  On-Line Applications Research Corporation (OAR).
27 *
28 *  The license and distribution terms for this file may be
29 *  found in the file LICENSE in this distribution or at
30 *  http://www.rtems.org/license/LICENSE.
31 */
32
33#include <stdlib.h>
34
35#include <rtems/bspIo.h>
36
37#include <bsp.h>
38
39#include "ns16550.h"
40#include "ns16550_p.h"
41
42#if defined(BSP_FEATURE_IRQ_EXTENSION)
43  #include <bsp/irq.h>
44#elif defined(BSP_FEATURE_IRQ_LEGACY)
45  #include <bsp/irq.h>
46#elif defined(__PPC__) || defined(__i386__)
47  #include <bsp/irq.h>
48  #define BSP_FEATURE_IRQ_LEGACY
49  #ifdef BSP_SHARED_HANDLER_SUPPORT
50    #define BSP_FEATURE_IRQ_LEGACY_SHARED_HANDLER_SUPPORT
51  #endif
52#endif
53
54static uint32_t NS16550_GetBaudDivisor(ns16550_context *ctx, uint32_t baud)
55{
56  uint32_t clock = ctx->clock;
57  uint32_t baudDivisor = (clock != 0 ? clock : 115200) / (baud * 16);
58
59  if (ctx->has_fractional_divider_register) {
60    uint32_t fractionalDivider = 0x10;
61    uint32_t err = baud;
62    uint32_t mulVal;
63    uint32_t divAddVal;
64
65    clock /= 16 * baudDivisor;
66    for (mulVal = 1; mulVal < 16; ++mulVal) {
67      for (divAddVal = 0; divAddVal < mulVal; ++divAddVal) {
68        uint32_t actual = (mulVal * clock) / (mulVal + divAddVal);
69        uint32_t newErr = actual > baud ? actual - baud : baud - actual;
70
71        if (newErr < err) {
72          err = newErr;
73          fractionalDivider = (mulVal << 4) | divAddVal;
74        }
75      }
76    }
77
78    (*ctx->set_reg)(
79      ctx->port,
80      NS16550_FRACTIONAL_DIVIDER,
81      fractionalDivider
82    );
83  }
84
85  return baudDivisor;
86}
87
88/*
89 *  ns16550_enable_interrupts
90 *
91 *  This routine initializes the port to have the specified interrupts masked.
92 */
93static void ns16550_enable_interrupts(
94  ns16550_context *ctx,
95  int              mask
96)
97{
98  (*ctx->set_reg)(ctx->port, NS16550_INTERRUPT_ENABLE, mask);
99}
100
101/*
102 *  ns16550_probe
103 */
104
105bool ns16550_probe(rtems_termios_device_context *base)
106{
107  ns16550_context        *ctx = (ns16550_context *) base;
108  uintptr_t               pNS16550;
109  uint8_t                 ucDataByte;
110  uint32_t                ulBaudDivisor;
111  ns16550_set_reg         setReg;
112  ns16550_get_reg         getReg;
113
114  ctx->modem_control = SP_MODEM_IRQ;
115
116  pNS16550 = ctx->port;   
117  setReg   = ctx->set_reg;
118  getReg   = ctx->get_reg;
119
120  /* Clear the divisor latch, clear all interrupt enables,
121   * and reset and
122   * disable the FIFO's.
123   */
124
125  (*setReg)(pNS16550, NS16550_LINE_CONTROL, 0x0);
126  ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR );
127
128  /* Set the divisor latch and set the baud rate. */
129
130  ulBaudDivisor = NS16550_GetBaudDivisor(ctx, ctx->initial_baud);
131  ucDataByte = SP_LINE_DLAB;
132  (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucDataByte);
133
134  /* XXX */
135  (*setReg)(pNS16550,NS16550_TRANSMIT_BUFFER,(uint8_t)(ulBaudDivisor & 0xffU));
136  (*setReg)(
137    pNS16550,NS16550_INTERRUPT_ENABLE,
138    (uint8_t)(( ulBaudDivisor >> 8 ) & 0xffU )
139  );
140
141  /* Clear the divisor latch and set the character size to eight bits */
142  /* with one stop bit and no parity checking. */
143  ucDataByte = EIGHT_BITS;
144  (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucDataByte);
145
146  /* Enable and reset transmit and receive FIFOs. TJA     */
147  ucDataByte = SP_FIFO_ENABLE;
148  (*setReg)(pNS16550, NS16550_FIFO_CONTROL, ucDataByte);
149
150  ucDataByte = SP_FIFO_ENABLE | SP_FIFO_RXRST | SP_FIFO_TXRST;
151  (*setReg)(pNS16550, NS16550_FIFO_CONTROL, ucDataByte);
152
153  ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR);
154
155  /* Set data terminal ready. */
156  /* And open interrupt tristate line */
157  (*setReg)(pNS16550, NS16550_MODEM_CONTROL,ctx->modem_control);
158
159  (*getReg)(pNS16550, NS16550_LINE_STATUS );
160  (*getReg)(pNS16550, NS16550_RECEIVE_BUFFER );
161
162  return true;
163}
164
165#if defined(BSP_FEATURE_IRQ_EXTENSION) || defined(BSP_FEATURE_IRQ_LEGACY)
166
167/**
168 * @brief Process interrupt.
169 */
170static void ns16550_isr(void *arg)
171{
172  rtems_termios_tty *tty = arg;
173  ns16550_context *ctx = rtems_termios_get_device_context(tty);
174  uint32_t port = ctx->port;
175  ns16550_get_reg get = ctx->get_reg;
176  int i = 0;
177  char buf [SP_FIFO_SIZE];
178
179  /* Iterate until no more interrupts are pending */
180  do {
181    /* Fetch received characters */
182    for (i = 0; i < SP_FIFO_SIZE; ++i) {
183      if ((get( port, NS16550_LINE_STATUS) & SP_LSR_RDY) != 0) {
184        buf [i] = (char) get(port, NS16550_RECEIVE_BUFFER);
185      } else {
186        break;
187      }
188    }
189
190    /* Enqueue fetched characters */
191    rtems_termios_enqueue_raw_characters(tty, buf, i);
192
193    /* Check if we can dequeue transmitted characters */
194    if (ctx->transmit_fifo_chars > 0
195        && (get( port, NS16550_LINE_STATUS) & SP_LSR_THOLD) != 0) {
196
197      /* Dequeue transmitted characters */
198      rtems_termios_dequeue_characters(
199        tty,
200        ctx->transmit_fifo_chars
201      );
202    }
203  } while ((get( port, NS16550_INTERRUPT_ID) & SP_IID_0) == 0);
204}
205#endif
206
207/*
208 *  ns16550_initialize_interrupts
209 *
210 *  This routine initializes the port to operate in interrupt driver mode.
211 */
212static void ns16550_initialize_interrupts(
213  struct rtems_termios_tty *tty,
214  ns16550_context          *ctx
215)
216{
217  #ifdef BSP_FEATURE_IRQ_EXTENSION
218    {
219      rtems_status_code sc = RTEMS_SUCCESSFUL;
220      sc = rtems_interrupt_handler_install(
221        ctx->irq,
222        "NS16550",
223        RTEMS_INTERRUPT_SHARED,
224        ns16550_isr,
225        tty
226      );
227      if (sc != RTEMS_SUCCESSFUL) {
228        /* FIXME */
229        printk( "%s: Error: Install interrupt handler\n", __func__);
230        rtems_fatal_error_occurred( 0xdeadbeef);
231      }
232    }
233  #elif defined(BSP_FEATURE_IRQ_LEGACY)
234    {
235      int rv = 0;
236      #ifdef BSP_FEATURE_IRQ_LEGACY_SHARED_HANDLER_SUPPORT
237        rtems_irq_connect_data cd = {
238          ctx->irq,
239          ns16550_isr,
240          tty,
241          NULL,
242          NULL,
243          NULL,
244          NULL
245        };
246        rv = BSP_install_rtems_shared_irq_handler( &cd);
247      #else
248        rtems_irq_connect_data cd = {
249          ctx->irq,
250          ns16550_isr,
251          tty,
252          NULL,
253          NULL,
254          NULL
255        };
256        rv = BSP_install_rtems_irq_handler( &cd);
257      #endif
258      if (rv == 0) {
259        /* FIXME */
260        printk( "%s: Error: Install interrupt handler\n", __func__);
261        rtems_fatal_error_occurred( 0xdeadbeef);
262      }
263    }
264  #endif
265}
266
267/*
268 *  ns16550_open
269 */
270
271static bool ns16550_open(
272  struct rtems_termios_tty      *tty,
273  rtems_termios_device_context  *base,
274  struct termios                *term,
275  rtems_libio_open_close_args_t *args
276)
277{
278  ns16550_context *ctx = (ns16550_context *) base;
279
280  /* Set initial baud */
281  rtems_termios_set_initial_baud(tty, ctx->initial_baud);
282
283  if (tty->handler.mode == TERMIOS_IRQ_DRIVEN) {
284    ns16550_initialize_interrupts(tty, ctx);
285    ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR_EXCEPT_TX);
286  }
287
288  return true;
289}
290
291static void ns16550_cleanup_interrupts(
292  struct rtems_termios_tty *tty,
293  ns16550_context          *ctx
294)
295{
296  #if defined(BSP_FEATURE_IRQ_EXTENSION)
297    rtems_status_code sc = RTEMS_SUCCESSFUL;
298    sc = rtems_interrupt_handler_remove(
299      ctx->irq,
300      ns16550_isr,
301      tty
302    );
303    if (sc != RTEMS_SUCCESSFUL) {
304      /* FIXME */
305      printk("%s: Error: Remove interrupt handler\n", __func__);
306      rtems_fatal_error_occurred(0xdeadbeef);
307    }
308  #elif defined(BSP_FEATURE_IRQ_LEGACY)
309    int rv = 0;
310    rtems_irq_connect_data cd = {
311      .name = ctx->irq,
312      .hdl = ns16550_isr,
313      .handle = tty
314    };
315    rv = BSP_remove_rtems_irq_handler(&cd);
316    if (rv == 0) {
317      /* FIXME */
318      printk("%s: Error: Remove interrupt handler\n", __func__);
319      rtems_fatal_error_occurred(0xdeadbeef);
320    }
321  #endif
322}
323
324/*
325 *  ns16550_close
326 */
327
328static void ns16550_close(
329  struct rtems_termios_tty      *tty,
330  rtems_termios_device_context  *base,
331  rtems_libio_open_close_args_t *args
332)
333{
334  ns16550_context *ctx = (ns16550_context *) base;
335
336  ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR);
337
338  if (tty->handler.mode == TERMIOS_IRQ_DRIVEN) {
339    ns16550_cleanup_interrupts(tty, ctx);
340  }
341}
342
343/**
344 * @brief Polled write for NS16550.
345 */
346void ns16550_polled_putchar(rtems_termios_device_context *base, char out)
347{
348  ns16550_context *ctx = (ns16550_context *) base;
349  uintptr_t port = ctx->port;
350  ns16550_get_reg get = ctx->get_reg;
351  ns16550_set_reg set = ctx->set_reg;
352  uint32_t status = 0;
353  rtems_interrupt_lock_context lock_context;
354
355  /* Save port interrupt mask */
356  uint32_t interrupt_mask = get( port, NS16550_INTERRUPT_ENABLE);
357
358  /* Disable port interrupts */
359  ns16550_enable_interrupts(ctx, NS16550_DISABLE_ALL_INTR);
360
361  while (true) {
362    /* Try to transmit the character in a critical section */
363    rtems_termios_device_lock_acquire(&ctx->base, &lock_context);
364
365    /* Read the transmitter holding register and check it */
366    status = get( port, NS16550_LINE_STATUS);
367    if ((status & SP_LSR_THOLD) != 0) {
368      /* Transmit character */
369      set( port, NS16550_TRANSMIT_BUFFER, out);
370
371      /* Finished */
372      rtems_termios_device_lock_release(&ctx->base, &lock_context);
373      break;
374    } else {
375      rtems_termios_device_lock_release(&ctx->base, &lock_context);
376    }
377
378    /* Wait for transmitter holding register to be empty */
379    do {
380      status = get( port, NS16550_LINE_STATUS);
381    } while ((status & SP_LSR_THOLD) == 0);
382  }
383
384  /* Restore port interrupt mask */
385  set( port, NS16550_INTERRUPT_ENABLE, interrupt_mask);
386}
387
388/*
389 * These routines provide control of the RTS and DTR lines
390 */
391
392/*
393 *  ns16550_assert_RTS
394 */
395
396static void ns16550_assert_RTS(rtems_termios_device_context *base)
397{
398  ns16550_context             *ctx = (ns16550_context *) base;
399  rtems_interrupt_lock_context lock_context;
400
401  /*
402   * Assert RTS
403   */
404  rtems_termios_device_lock_acquire(base, &lock_context);
405  ctx->modem_control |= SP_MODEM_RTS;
406  (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL, ctx->modem_control);
407  rtems_termios_device_lock_release(base, &lock_context);
408}
409
410/*
411 *  ns16550_negate_RTS
412 */
413
414static void ns16550_negate_RTS(rtems_termios_device_context *base)
415{
416  ns16550_context             *ctx = (ns16550_context *) base;
417  rtems_interrupt_lock_context lock_context;
418
419  /*
420   * Negate RTS
421   */
422  rtems_termios_device_lock_acquire(base, &lock_context);
423  ctx->modem_control &= ~SP_MODEM_RTS;
424  (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL, ctx->modem_control);
425  rtems_termios_device_lock_release(base, &lock_context);
426}
427
428/*
429 * These flow control routines utilise a connection from the local DTR
430 * line to the remote CTS line
431 */
432
433/*
434 *  ns16550_assert_DTR
435 */
436
437static void ns16550_assert_DTR(rtems_termios_device_context *base)
438{
439  ns16550_context             *ctx = (ns16550_context *) base;
440  rtems_interrupt_lock_context lock_context;
441
442  /*
443   * Assert DTR
444   */
445  rtems_termios_device_lock_acquire(base, &lock_context);
446  ctx->modem_control |= SP_MODEM_DTR;
447  (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL, ctx->modem_control);
448  rtems_termios_device_lock_release(base, &lock_context);
449}
450
451/*
452 *  ns16550_negate_DTR
453 */
454
455static void ns16550_negate_DTR(rtems_termios_device_context *base)
456{
457  ns16550_context             *ctx = (ns16550_context *) base;
458  rtems_interrupt_lock_context lock_context;
459
460  /*
461   * Negate DTR
462   */
463  rtems_termios_device_lock_acquire(base, &lock_context);
464  ctx->modem_control &=~SP_MODEM_DTR;
465  (*ctx->set_reg)(ctx->port, NS16550_MODEM_CONTROL,ctx->modem_control);
466  rtems_termios_device_lock_release(base, &lock_context);
467}
468
469/*
470 *  ns16550_set_attributes
471 *
472 *  This function sets the channel to reflect the requested termios
473 *  port settings.
474 */
475
476static bool ns16550_set_attributes(
477  rtems_termios_device_context *base,
478  const struct termios         *t
479)
480{
481  ns16550_context        *ctx = (ns16550_context *) base;
482  uint32_t                pNS16550;
483  uint32_t                ulBaudDivisor;
484  uint8_t                 ucLineControl;
485  uint32_t                baud_requested;
486  ns16550_set_reg         setReg;
487  rtems_interrupt_lock_context lock_context;
488
489  pNS16550 = ctx->port;
490  setReg   = ctx->set_reg;
491
492  /*
493   *  Calculate the baud rate divisor
494   */
495
496  baud_requested = rtems_termios_baud_to_number(t->c_cflag);
497  ulBaudDivisor = NS16550_GetBaudDivisor(ctx, baud_requested);
498
499  ucLineControl = 0;
500
501  /*
502   *  Parity
503   */
504
505  if (t->c_cflag & PARENB) {
506    ucLineControl |= SP_LINE_PAR;
507    if (!(t->c_cflag & PARODD))
508      ucLineControl |= SP_LINE_ODD;
509  }
510
511  /*
512   *  Character Size
513   */
514
515  if (t->c_cflag & CSIZE) {
516    switch (t->c_cflag & CSIZE) {
517      case CS5:  ucLineControl |= FIVE_BITS;  break;
518      case CS6:  ucLineControl |= SIX_BITS;   break;
519      case CS7:  ucLineControl |= SEVEN_BITS; break;
520      case CS8:  ucLineControl |= EIGHT_BITS; break;
521    }
522  } else {
523    ucLineControl |= EIGHT_BITS;               /* default to 9600,8,N,1 */
524  }
525
526  /*
527   *  Stop Bits
528   */
529
530  if (t->c_cflag & CSTOPB) {
531    ucLineControl |= SP_LINE_STOP;              /* 2 stop bits */
532  } else {
533    ;                                           /* 1 stop bit */
534  }
535
536  /*
537   *  Now actually set the chip
538   */
539
540  rtems_termios_device_lock_acquire(base, &lock_context);
541
542    /*
543     *  Set the baud rate
544     *
545     *  NOTE: When the Divisor Latch Access Bit (DLAB) is set to 1,
546     *        the transmit buffer and interrupt enable registers
547     *        turn into the LSB and MSB divisor latch registers.
548     */
549
550    (*setReg)(pNS16550, NS16550_LINE_CONTROL, SP_LINE_DLAB);
551    (*setReg)(pNS16550, NS16550_TRANSMIT_BUFFER, ulBaudDivisor&0xff);
552    (*setReg)(pNS16550, NS16550_INTERRUPT_ENABLE, (ulBaudDivisor>>8)&0xff);
553
554    /*
555     *  Now write the line control
556     */
557    (*setReg)(pNS16550, NS16550_LINE_CONTROL, ucLineControl );
558
559  rtems_termios_device_lock_release(base, &lock_context);
560
561  return true;
562}
563
564/**
565 * @brief Transmits up to @a len characters from @a buf.
566 *
567 * This routine is invoked either from task context with disabled interrupts to
568 * start a new transmission process with exactly one character in case of an
569 * idle output state or from the interrupt handler to refill the transmitter.
570 *
571 * Returns always zero.
572 */
573static void ns16550_write_support_int(
574  rtems_termios_device_context *base,
575  const char                   *buf,
576  size_t                        len
577)
578{
579  ns16550_context *ctx = (ns16550_context *) base;
580  uint32_t port = ctx->port;
581  ns16550_set_reg set = ctx->set_reg;
582  int i = 0;
583  int out = len > SP_FIFO_SIZE ? SP_FIFO_SIZE : len;
584
585  for (i = 0; i < out; ++i) {
586    set( port, NS16550_TRANSMIT_BUFFER, buf [i]);
587  }
588
589  ctx->transmit_fifo_chars = out;
590
591  if (out > 0) {
592    ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR);
593  } else {
594    ns16550_enable_interrupts(ctx, NS16550_ENABLE_ALL_INTR_EXCEPT_TX);
595  }
596}
597
598/*
599 *  ns16550_write_support_polled
600 *
601 *  Console Termios output entry point.
602 *
603 */
604
605static void ns16550_write_support_polled(
606  rtems_termios_device_context *base,
607  const char                   *buf,
608  size_t                        len
609)
610{
611  size_t nwrite = 0;
612
613  /*
614   * poll each byte in the string out of the port.
615   */
616  while (nwrite < len) {
617    /*
618     * transmit character
619     */
620    ns16550_polled_putchar(base, *buf++);
621    nwrite++;
622  }
623}
624
625/*
626 *  Debug gets() support
627 */
628int ns16550_polled_getchar(rtems_termios_device_context *base)
629{
630  ns16550_context     *ctx = (ns16550_context *) base;
631  uint32_t             pNS16550;
632  unsigned char        ucLineStatus;
633  uint8_t              cChar;
634  ns16550_get_reg      getReg;
635
636  pNS16550 = ctx->port;
637  getReg   = ctx->get_reg;
638
639  ucLineStatus = (*getReg)(pNS16550, NS16550_LINE_STATUS);
640  if (ucLineStatus & SP_LSR_RDY) {
641    cChar = (*getReg)(pNS16550, NS16550_RECEIVE_BUFFER);
642    return (int)cChar;
643  }
644  return -1;
645}
646
647/*
648 * Flow control is only supported when using interrupts
649 */
650
651const rtems_termios_device_flow ns16550_flow_rtscts = {
652  .stop_remote_tx = ns16550_negate_RTS,
653  .start_remote_tx = ns16550_assert_RTS
654};
655
656const rtems_termios_device_flow ns16550_flow_dtrcts = {
657  .stop_remote_tx = ns16550_negate_DTR,
658  .start_remote_tx = ns16550_assert_DTR
659};
660
661const rtems_termios_device_handler ns16550_handler_interrupt = {
662  .first_open = ns16550_open,
663  .last_close = ns16550_close,
664  .poll_read = NULL,
665  .write = ns16550_write_support_int,
666  .set_attributes = ns16550_set_attributes,
667  .mode = TERMIOS_IRQ_DRIVEN
668};
669
670const rtems_termios_device_handler ns16550_handler_polled = {
671  .first_open = ns16550_open,
672  .last_close = ns16550_close,
673  .poll_read = ns16550_polled_getchar,
674  .write = ns16550_write_support_polled,
675  .set_attributes = ns16550_set_attributes,
676  .mode = TERMIOS_POLLED
677};
Note: See TracBrowser for help on using the repository browser.