minix/drivers/tty/arch/arm/rs232.c
Thomas Veerman b01e9ebfdb TTY: seperate hardware dependent parts + add new serial driver
.Split TTY in order to support both x86 and ARM.
.Add support for the TI 16750 UARTs on OMAP35x.
.Various other improvements:
  .Kernel messages are printed using generic terminal write
   functions. That is, they are no longer directly displayed
   on the console.
  .The console can now be displayed on any terminal. This
   is configured by the "console={tty00,tty01,ttyc2,ttyc3,ttyc4}"
   boot variable -- basically any valid /dev/tty* terminal.
  .Cutify kernel messages with colors. Configured by
   "kernelclr={1,2,3,4,5,6,7}" boot variable.
2012-10-30 11:33:29 +00:00

798 lines
22 KiB
C

#include <minix/config.h>
#include <minix/drivers.h>
#include <minix/vm.h>
#include <sys/mman.h>
#include <assert.h>
#include <signal.h>
#include <termios.h>
#include "omap_serial.h"
#include "tty.h"
#if NR_RS_LINES > 0
#define UART_FREQ 48000000L /* timer frequency */
#if 0
#define DFLT_BAUD TSPEED_DEF /* default baud rate */
#else
#define DFLT_BAUD B115200 /* default baud rate */
#endif
#define RS_IBUFSIZE 1024 /* RS232 input buffer size */
#define RS_OBUFSIZE 1024 /* RS232 output buffer size */
/* Input buffer watermarks.
* The external device is asked to stop sending when the buffer
* exactly reaches high water, or when TTY requests it. Sending restarts
* when the input buffer empties below the low watermark.
*/
#define RS_ILOWWATER (1 * RS_IBUFSIZE / 4)
#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4)
/* Output buffer low watermark.
* TTY is notified when the output buffer empties below the low watermark, so
* it may continue filling the buffer if doing a large write.
*/
#define RS_OLOWWATER (1 * RS_OBUFSIZE / 4)
/* Macros to handle flow control.
* Interrupts must be off when they are used.
* Time is critical - already the function call for outb() is annoying.
* If outb() can be done in-line, tests to avoid it can be dropped.
* istart() tells external device we are ready by raising RTS.
* istop() tells external device we are not ready by dropping RTS.
* DTR is kept high all the time (it probably should be raised by open and
* dropped by close of the device).
* OUT2 is also kept high all the time.
*/
#define istart(rs) \
(serial_out((rs), OMAP3_MCR, UART_MCR_OUT2|UART_MCR_RTS|UART_MCR_DTR),\
(rs)->idevready = TRUE)
#define istop(rs) \
(serial_out((rs), OMAP3_MCR, UART_MCR_OUT2|UART_MCR_DTR), \
(rs)->idevready = FALSE)
/* Macro to tell if device is ready. The rs->cts field is set to UART_MSR_CTS
* if CLOCAL is in effect for a line without a CTS wire.
*/
#define devready(rs) ((serial_in(rs, OMAP3_MSR) | rs->cts) & UART_MSR_CTS)
/* Macro to tell if transmitter is ready. */
#define txready(rs) (serial_in(rs, OMAP3_LSR) & UART_LSR_THRE)
/* RS232 device structure, one per device. */
typedef struct rs232 {
tty_t *tty; /* associated TTY structure */
int icount; /* number of bytes in the input buffer */
char *ihead; /* next free spot in input buffer */
char *itail; /* first byte to give to TTY */
char idevready; /* nonzero if we are ready to receive (RTS) */
char cts; /* normally 0, but MS_CTS if CLOCAL is set */
unsigned char ostate; /* combination of flags: */
#define ODONE 1 /* output completed (< output enable bits) */
#define ORAW 2 /* raw mode for xoff disable (< enab. bits) */
#define OWAKEUP 4 /* tty_wakeup() pending (asm code only) */
#define ODEVREADY UART_MSR_CTS /* external device hardware ready (CTS) */
#define OQUEUED 0x20 /* output buffer not empty */
#define OSWREADY 0x40 /* external device software ready (no xoff) */
#define ODEVHUP UART_MSR_DCD /* external device has dropped carrier */
#define OSOFTBITS (ODONE | ORAW | OWAKEUP | OQUEUED | OSWREADY)
/* user-defined bits */
#if (OSOFTBITS | ODEVREADY | ODEVHUP) == OSOFTBITS
/* a weak sanity check */
#error /* bits are not unique */
#endif
unsigned char oxoff; /* char to stop output */
char inhibited; /* output inhibited? (follows tty_inhibited) */
char drain; /* if set drain output and reconfigure line */
int ocount; /* number of bytes in the output buffer */
char *ohead; /* next free spot in output buffer */
char *otail; /* next char to output */
phys_bytes phys_base; /* UART physical base address (I/O map) */
unsigned int reg_offset; /* UART register offset */
unsigned int ier; /* copy of ier register */
unsigned int scr; /* copy of scr register */
unsigned int fcr; /* copy of fcr register */
unsigned int dll; /* copy of dll register */
unsigned int dlh; /* copy of dlh register */
unsigned int uartclk; /* UART clock rate */
unsigned char lstatus; /* last line status */
unsigned framing_errors; /* error counts (no reporting yet) */
unsigned overrun_errors;
unsigned parity_errors;
unsigned break_interrupts;
int irq; /* irq for this line */
int irq_hook_id; /* interrupt hook */
char ibuf[RS_IBUFSIZE]; /* input buffer */
char obuf[RS_OBUFSIZE]; /* output buffer */
} rs232_t;
static rs232_t rs_lines[NR_RS_LINES];
typedef struct uart_port {
phys_bytes base_addr;
int irq;
} uart_port_t;
/* OMAP3 UART base addresses. */
static uart_port_t omap3[] = {
{ OMAP3_UART1_BASE, 72}, /* UART1 */
{ OMAP3_UART2_BASE, 73}, /* UART2 */
{ OMAP3_UART3_BASE, 74}, /* UART3 */
{ 0, 0 }
};
static int rs_write(tty_t *tp, int try);
static void rs_echo(tty_t *tp, int c);
static int rs_ioctl(tty_t *tp, int try);
static void rs_config(rs232_t *rs);
static int rs_read(tty_t *tp, int try);
static int rs_icancel(tty_t *tp, int try);
static int rs_ocancel(tty_t *tp, int try);
static void rs_ostart(rs232_t *rs);
static int rs_break(tty_t *tp, int try);
static int rs_close(tty_t *tp, int try);
static int rs_open(tty_t *tp, int try);
static void rs232_handler(rs232_t *rs);
static void rs_reset(rs232_t *rs);
static unsigned int check_modem_status(rs232_t *rs);
static int termios_baud_rate(struct termios *term);
static inline unsigned int readw(vir_bytes addr);
static inline unsigned int serial_in(rs232_t *rs, int offset);
static inline void serial_out(rs232_t *rs, int offset, int val);
static inline void writew(vir_bytes addr, int val);
static void write_chars(rs232_t *rs);
static void read_chars(rs232_t *rs, unsigned int status);
static inline unsigned int
readw(vir_bytes addr)
{
return *((volatile unsigned int *) addr);
}
static inline void
writew(vir_bytes addr, int val)
{
*((volatile unsigned int *) addr) = val;
}
static inline unsigned int
serial_in(rs232_t *rs, int offset)
{
offset <<= rs->reg_offset;
return readw(rs->phys_base + offset);
}
static inline void
serial_out(rs232_t *rs, int offset, int val)
{
offset <<= rs->reg_offset;
writew(rs->phys_base + offset, val);
}
static void
rs_reset(rs232_t *rs)
{
u32_t syss;
serial_out(rs, OMAP3_SYSC, UART_SYSC_SOFTRESET);
/* Poll until done */
do {
syss = serial_in(rs, OMAP3_SYSS);
} while (!(syss & UART_SYSS_RESETDONE));
}
static int
rs_write(register tty_t *tp, int try)
{
/* (*devwrite)() routine for RS232. */
rs232_t *rs = tp->tty_priv;
int r, count, ocount;
if (rs->inhibited != tp->tty_inhibited) {
/* Inhibition state has changed. */
rs->ostate |= OSWREADY;
if (tp->tty_inhibited) rs->ostate &= ~OSWREADY;
rs->inhibited = tp->tty_inhibited;
}
if (rs->drain) {
/* Wait for the line to drain then reconfigure and continue
* output. */
if (rs->ocount > 0) return 0;
rs->drain = FALSE;
rs_config(rs);
}
/* While there is something to do. */
for (;;) {
ocount = buflen(rs->obuf) - rs->ocount;
count = bufend(rs->obuf) - rs->ohead;
if (count > ocount) count = ocount;
if (count > tp->tty_outleft) count = tp->tty_outleft;
if (count == 0 || tp->tty_inhibited) {
if (try) return 0;
break;
}
if (try) return 1;
/* Copy from user space to the RS232 output buffer. */
if (tp->tty_outcaller == KERNEL) {
/* We're trying to print on kernel's behalf */
memcpy(rs->ohead,
(void *) tp->tty_outgrant + tp->tty_outoffset,
count);
} else {
if ((r = sys_safecopyfrom(tp->tty_outcaller,
tp->tty_outgrant, tp->tty_outoffset,
(vir_bytes) rs->ohead, count)) != OK) {
return 0;
}
}
/* Perform output processing on the output buffer. */
out_process(tp, rs->obuf, rs->ohead, bufend(rs->obuf), &count,
&ocount);
if (count == 0) {
break;
}
/* Assume echoing messed up by output. */
tp->tty_reprint = TRUE;
/* Bookkeeping. */
rs->ocount += ocount;
rs_ostart(rs);
if ((rs->ohead += ocount) >= bufend(rs->obuf))
rs->ohead -= buflen(rs->obuf);
tp->tty_outoffset += count;
tp->tty_outcum += count;
if ((tp->tty_outleft -= count) == 0) {
/* Output is finished, reply to the writer. */
if(tp->tty_outrepcode == TTY_REVIVE) {
notify(tp->tty_outcaller);
tp->tty_outrevived = 1;
} else {
tty_reply(tp->tty_outrepcode, tp->tty_outcaller,
tp->tty_outproc, tp->tty_outcum);
tp->tty_outcum = 0;
}
}
}
if (tp->tty_outleft > 0 && tp->tty_termios.c_ospeed == B0) {
/* Oops, the line has hung up. */
if(tp->tty_outrepcode == TTY_REVIVE) {
notify(tp->tty_outcaller);
tp->tty_outrevived = 1;
} else {
tty_reply(tp->tty_outrepcode, tp->tty_outcaller,
tp->tty_outproc, EIO);
tp->tty_outleft = tp->tty_outcum = 0;
}
}
return 1;
}
static void
rs_echo(tty_t *tp, int character)
{
/* Echo one character. (Like rs_write, but only one character, optionally.) */
rs232_t *rs = tp->tty_priv;
int count, ocount;
ocount = buflen(rs->obuf) - rs->ocount;
if (ocount == 0) return; /* output buffer full */
count = 1;
*rs->ohead = character; /* add one character */
out_process(tp, rs->obuf, rs->ohead, bufend(rs->obuf), &count, &ocount);
if (count == 0) return;
rs->ocount += ocount;
rs_ostart(rs);
if ((rs->ohead += ocount) >= bufend(rs->obuf))
rs->ohead -= buflen(rs->obuf);
}
static int
rs_ioctl(tty_t *tp, int UNUSED(dummy))
{
/* Reconfigure the line as soon as the output has drained. */
rs232_t *rs = tp->tty_priv;
rs->drain = TRUE;
return 0; /* dummy */
}
static unsigned int
omap_get_divisor(rs232_t *rs, unsigned int baud)
{
/* Calculate divisor value. The 16750 has two oversampling modes to reach
* high baud rates with little error rate (see table 17-1 in OMAP TRM).
* Baud rates 460800, 921600, 1843200, and 3686400 use 13x oversampling,
* the other rates 16x. The baud rate is calculated as follows:
* baud rate = (functional clock / oversampling) / divisor.
*/
unsigned int oversampling;
assert(baud != 0);
switch(baud) {
case B460800: /* Fall through */
case B921600: /* Fall through */
case B1843200: /* Fall through */
case B3686400: oversampling = 13; break;
default: oversampling = 16;
}
return (rs->uartclk / oversampling) / baud;
}
static int
termios_baud_rate(struct termios *term)
{
int baud;
switch(term->c_ospeed) {
case B0: term->c_ospeed = DFLT_BAUD; baud = termios_baud_rate(term);
case B300: baud = 300; break;
case B600: baud = 600; break;
case B1200: baud = 1200; break;
case B2400: baud = 2400; break;
case B4800: baud = 4800; break;
case B9600: baud = 9600; break;
case B38400: baud = 38400; break;
case B57600: baud = 57600; break;
case B115200: baud = 115200; break;
default: term->c_ospeed = DFLT_BAUD; baud = termios_baud_rate(term);
}
return baud;
}
static void rs_config(rs232_t *rs)
{
/* Set various line control parameters for RS232 I/O. */
tty_t *tp = rs->tty;
unsigned int divisor, efr, lcr, mcr, baud;
/* Fifo and DMA settings */
/* See OMAP35x TRM 17.5.1.1.2 */
lcr = serial_in(rs, OMAP3_LCR); /* 1a */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_B); /* 1b */
efr = serial_in(rs, OMAP3_EFR); /* 2a */
serial_out(rs, OMAP3_EFR, efr | UART_EFR_ECB); /* 2b */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_A); /* 3 */
mcr = serial_in(rs, OMAP3_MCR); /* 4a */
serial_out(rs, OMAP3_MCR, mcr | UART_MCR_TCRTLR); /* 4b */
/* Set up FIFO for 1 byte */
rs->fcr = 0;
rs->fcr &= ~OMAP_UART_FCR_RX_FIFO_TRIG_MASK;
rs->fcr |= (0x1 << OMAP_UART_FCR_RX_FIFO_TRIG_SHIFT);
serial_out(rs, OMAP3_FCR, rs->fcr); /* 5 */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_B); /* 6 */
/* DMA triggers, not supported by this driver */ /* 7 */
rs->scr = OMAP_UART_SCR_RX_TRIG_GRANU1_MASK;
serial_out(rs, OMAP3_SCR, rs->scr); /* 8 */
serial_out(rs, OMAP3_EFR, efr); /* 9 */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_A); /* 10 */
serial_out(rs, OMAP3_MCR, mcr); /* 11 */
serial_out(rs, OMAP3_LCR, lcr); /* 12 */
/* RS232 needs to know the xoff character, and if CTS works. */
rs->oxoff = tp->tty_termios.c_cc[VSTOP];
rs->cts = (tp->tty_termios.c_cflag & CLOCAL) ? UART_MSR_CTS : 0;
baud = termios_baud_rate(&tp->tty_termios);
/* Look up the 16750 rate divisor from the output speed. */
divisor = omap_get_divisor(rs, baud);
rs->dll = divisor & 0xFF;
rs->dlh = divisor >> 8;
/* Compute line control flag bits. */
lcr = 0;
if (tp->tty_termios.c_cflag & PARENB) {
lcr |= UART_LCR_PARITY;
if (!(tp->tty_termios.c_cflag & PARODD)) lcr |= UART_LCR_EPAR;
}
if (tp->tty_termios.c_cflag & CSTOPB) lcr |= UART_LCR_STOP;
switch(tp->tty_termios.c_cflag & CSIZE) {
case CS5:
lcr |= UART_LCR_WLEN5;
break;
case CS6:
lcr |= UART_LCR_WLEN6;
break;
case CS7:
lcr |= UART_LCR_WLEN7;
break;
default:
case CS8:
lcr |= UART_LCR_WLEN8;
break;
}
/* Lock out interrupts while setting the speed. The receiver register
* is going to be hidden by the div_low register, but the input
* interrupt handler relies on reading it to clear the interrupt and
* avoid looping forever.
*/
if (sys_irqdisable(&rs->irq_hook_id) != OK)
panic("unable to disable interrupts");
/* Select the baud rate divisor registers and change the rate. */
/* See OMAP35x TRM 17.5.1.1.3 */
serial_out(rs, OMAP3_MDR1, OMAP_MDR1_DISABLE); /* 1 */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_B); /* 2 */
efr = serial_in(rs, OMAP3_EFR); /* 3a */
serial_out(rs, OMAP3_EFR, efr | UART_EFR_ECB); /* 3b */
serial_out(rs, OMAP3_LCR, 0); /* 4 */
serial_out(rs, OMAP3_IER, 0); /* 5 */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_B); /* 6 */
serial_out(rs, OMAP3_DLL, rs->dll); /* 7 */
serial_out(rs, OMAP3_DLH, rs->dlh); /* 7 */
serial_out(rs, OMAP3_LCR, 0); /* 8 */
serial_out(rs, OMAP3_IER, rs->ier); /* 9 */
serial_out(rs, OMAP3_LCR, UART_LCR_CONF_MODE_B); /* 10 */
serial_out(rs, OMAP3_EFR, efr); /* 11 */
serial_out(rs, OMAP3_LCR, lcr); /* 12 */
if (baud > 230400 && baud != 3000000)
serial_out(rs, OMAP3_MDR1, OMAP_MDR1_MODE13X); /* 13 */
else
serial_out(rs, OMAP3_MDR1, OMAP_MDR1_MODE16X);
rs->ostate = devready(rs) | ORAW | OSWREADY; /* reads MSR */
if ((tp->tty_termios.c_lflag & IXON) && rs->oxoff != _POSIX_VDISABLE)
rs->ostate &= ~ORAW;
(void) serial_in(rs, OMAP3_IIR);
if (sys_irqenable(&rs->irq_hook_id) != OK)
panic("unable to enable interrupts");
}
void
rs_init(tty_t *tp)
{
/* Initialize RS232 for one line. */
register rs232_t *rs;
int line;
uart_port_t this_omap3;
char l[10];
struct minix_mem_range mr;
/* Associate RS232 and TTY structures. */
line = tp - &tty_table[NR_CONS];
/* See if kernel debugging is enabled; if so, don't initialize this
* serial line, making tty not look at the irq and returning ENXIO
* for all requests on it from userland. (The kernel will use it.)
*/
if(env_get_param(SERVARNAME, l, sizeof(l)-1) == OK && atoi(l) == line){
printf("TTY: rs232 line %d not initialized (used by kernel)\n",
line);
return;
}
rs = tp->tty_priv = &rs_lines[line];
rs->tty = tp;
/* Set up input queue. */
rs->ihead = rs->itail = rs->ibuf;
this_omap3 = omap3[line];
if (this_omap3.base_addr == 0) return;
/* Configure memory access */
mr.mr_base = rs->phys_base;
mr.mr_limit = rs->phys_base + 0x100;
if (sys_privctl(SELF, SYS_PRIV_ADD_MEM, &mr) != OK) {
panic("Unable to request access to UART memory");
}
rs->phys_base = (vir_bytes) vm_map_phys(SELF,
(void *) this_omap3.base_addr, 0x100);
if (rs->phys_base == (vir_bytes) MAP_FAILED) {
panic("Unable to request access to UART memory");
}
rs->reg_offset = 2;
rs->uartclk = UART_FREQ;
rs->ohead = rs->otail = rs->obuf;
/* Override system default baud rate. We do this because u-boot
* configures the UART for a baud rate of 115200 b/s and the kernel
* directly sends data over serial out upon boot up. If we then
* suddenly change the settings, the output will be garbled during
* booting.
*/
tp->tty_termios.c_ospeed = DFLT_BAUD;
/* Configure IRQ */
rs->irq = this_omap3.irq;
rs->irq_hook_id = 1 << line; /* call back with irq line number */
if (sys_irqsetpolicy(rs->irq, 0, &rs->irq_hook_id) != OK) {
printf("RS232: Couldn't obtain hook for irq %d\n", rs->irq);
} else {
if (sys_irqenable(&rs->irq_hook_id) != OK) {
printf("RS232: Couldn't enable irq %d (hooked)\n",
rs->irq);
}
}
rs_irq_set |= (1 << (rs->irq_hook_id + 1));
/* Enable interrupts */
rs_reset(rs);
rs->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_MSI;
rs_config(rs);
/* Fill in TTY function hooks. */
tp->tty_devread = rs_read;
tp->tty_devwrite = rs_write;
tp->tty_echo = rs_echo;
tp->tty_icancel = rs_icancel;
tp->tty_ocancel = rs_ocancel;
tp->tty_ioctl = rs_ioctl;
tp->tty_break = rs_break;
tp->tty_open = rs_open;
tp->tty_close = rs_close;
/* Tell external device we are ready. */
istart(rs);
}
void
rs_interrupt(message *m)
{
unsigned long irq_set;
int line;
rs232_t *rs;
irq_set = m->NOTIFY_ARG;
for (line = 0, rs = rs_lines; line < NR_RS_LINES; line++, rs++) {
if (irq_set & (1 << (rs->irq_hook_id+1))) {
rs232_handler(rs);
if (sys_irqenable(&rs->irq_hook_id) != OK)
panic("unable to enable interrupts");
}
}
}
static int
rs_icancel(tty_t *tp, int UNUSED(dummy))
{
/* Cancel waiting input. */
rs232_t *rs = tp->tty_priv;
rs->icount = 0;
rs->itail = rs->ihead;
istart(rs);
return 0; /* dummy */
}
static int
rs_ocancel(tty_t *tp, int UNUSED(dummy))
{
/* Cancel pending output. */
rs232_t *rs = tp->tty_priv;
rs->ostate &= ~(ODONE | OQUEUED);
rs->ocount = 0;
rs->otail = rs->ohead;
return 0; /* dummy */
}
static int
rs_read(tty_t *tp, int try)
{
/* Process characters from the circular input buffer. */
rs232_t *rs = tp->tty_priv;
int icount, count, ostate;
if (!(tp->tty_termios.c_cflag & CLOCAL)) {
if (try) return 1;
/* Send a SIGHUP if hangup detected. */
ostate = rs->ostate;
rs->ostate &= ~ODEVHUP; /* save ostate, clear DEVHUP */
if (ostate & ODEVHUP) {
sigchar(tp, SIGHUP, 1);
tp->tty_termios.c_ospeed = B0;/* Disable further I/O.*/
return 0;
}
}
if (try) {
return(rs->icount > 0);
}
while ((count = rs->icount) > 0) {
icount = bufend(rs->ibuf) - rs->itail;
if (count > icount) count = icount;
/* Perform input processing on (part of) the input buffer. */
if ((count = in_process(tp, rs->itail, count, -1)) == 0) break;
rs->icount -= count;
if (!rs->idevready && rs->icount < RS_ILOWWATER) istart(rs);
if ((rs->itail += count) == bufend(rs->ibuf))
rs->itail = rs->ibuf;
}
return 0;
}
static void
rs_ostart(rs232_t *rs)
{
/* Tell RS232 there is something waiting in the output buffer. */
rs->ostate |= OQUEUED;
if (txready(rs)) write_chars(rs);
}
static int
rs_break(tty_t *tp, int UNUSED(dummy))
{
/* Generate a break condition by setting the BREAK bit for 0.4 sec. */
rs232_t *rs = tp->tty_priv;
unsigned int lsr;
lsr = serial_in(rs, OMAP3_LSR);
serial_out(rs, OMAP3_LSR, lsr | UART_LSR_BI);
/* XXX */
/* milli_delay(400); */ /* ouch */
serial_out(rs, OMAP3_LSR, lsr);
return 0; /* dummy */
}
static int
rs_open(tty_t *tp, int UNUSED(dummy))
{
/* Set the speed to 115200 by default */
tp->tty_termios.c_ospeed = DFLT_BAUD;
return 0;
}
static int
rs_close(tty_t *tp, int UNUSED(dummy))
{
/* The line is closed; optionally hang up. */
rs232_t *rs = tp->tty_priv;
if (tp->tty_termios.c_cflag & HUPCL) {
serial_out(rs, OMAP3_MCR, UART_MCR_OUT2|UART_MCR_RTS);
if (rs->ier & UART_IER_THRI) {
rs->ier &= ~UART_IER_THRI;
serial_out(rs, OMAP3_IER, rs->ier);
}
}
return 0; /* dummy */
}
/* Low level (interrupt) routines. */
static void
rs232_handler(struct rs232 *rs)
{
/* Handle interrupt of a UART port */
unsigned int iir, lsr;
iir = serial_in(rs, OMAP3_IIR);
if (iir & UART_IIR_NO_INT) /* No interrupt */
return;
lsr = serial_in(rs, OMAP3_LSR);
if (iir & UART_IIR_RDI) { /* Data ready interrupt */
if (lsr & UART_LSR_DR) {
read_chars(rs, lsr);
}
}
check_modem_status(rs);
if (iir & UART_IIR_THRI) {
if (lsr & UART_LSR_THRE) {
/* Ready to send and space available */
write_chars(rs);
}
}
}
static void
read_chars(rs232_t *rs, unsigned int status)
{
unsigned char c;
unsigned int lsr;
lsr = status;
if (lsr & UART_LSR_DR) {
c = serial_in(rs, OMAP3_RHR);
if (!(rs->ostate & ORAW)) {
if (c == rs->oxoff) {
rs->ostate &= ~OSWREADY;
} else if (!(rs->ostate & OSWREADY)) {
rs->ostate = OSWREADY;
}
}
if (rs->icount == buflen(rs->ibuf)) {
printf("%s:%d buffer full, discarding byte\n",
__FUNCTION__, __LINE__);
return;
}
if (++rs->icount == RS_IHIGHWATER && rs->idevready) istop(rs);
*rs->ihead = c;
if (++rs->ihead == bufend(rs->ibuf)) rs->ihead = rs->ibuf;
if (rs->icount == 1) {
rs->tty->tty_events = 1;
}
}
}
static void
write_chars(rs232_t *rs)
{
/* If there is output to do and everything is ready, do it (local device is
* known ready).
* Notify TTY when the buffer goes empty.
*/
if (rs->ostate >= (ODEVREADY | OQUEUED | OSWREADY)) {
/* Bit test allows ORAW and requires the others. */
serial_out(rs, OMAP3_THR, *rs->otail);
if (++rs->otail == bufend(rs->obuf))
rs->otail = rs->obuf;
if (--rs->ocount == 0) {
/* Turn on ODONE flag, turn off OQUEUED */
rs->ostate ^= (ODONE | OQUEUED);
rs->tty->tty_events = 1;
if (rs->ier & UART_IER_THRI) {
rs->ier &= ~UART_IER_THRI;
serial_out(rs, OMAP3_IER, rs->ier);
}
} else {
if (rs->icount == RS_OLOWWATER)
rs->tty->tty_events = 1;
if (!(rs->ier & UART_IER_THRI)) {
rs->ier |= UART_IER_THRI;
serial_out(rs, OMAP3_IER, rs->ier);
}
}
}
}
static unsigned int
check_modem_status(rs232_t *rs)
{
/* Check modem status */
unsigned int msr;
msr = serial_in(rs, OMAP3_MSR); /* Resets modem interrupt */
if ((msr & (UART_MSR_DCD|UART_MSR_DDCD)) == UART_MSR_DDCD) {
rs->ostate |= ODEVHUP;
rs->tty->tty_events = 1;
}
if (!devready(rs))
rs->ostate &= ~ODEVREADY;
else
rs->ostate |= ODEVREADY;
return msr;
}
#endif /* NR_RS_LINES > 0 */