minix/drivers/tty/arch/earm/rs232.c
Kees Jongenburger b1e0ada171 arm:tty enable FIFO
Change-Id: Iaaa7c0bcb40c2cce2423c1defc14809ed6c70e9c
2013-06-05 19:19:38 +02:00

828 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 */
int irq_hook_kernel_id; /* id as returned from sys_irqsetpolicy */
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;
#ifdef DM37XX
/* 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 }
};
#endif
#ifdef AM335X
static uart_port_t omap3[] = {
{ 0x44E09000 , 72 }, /* UART0 */
{ 0, 0 },
{ 0, 0 },
{ 0, 0 }
};
#endif
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 */
rs->fcr = 0;
rs->fcr &= ~OMAP_UART_FCR_RX_FIFO_TRIG_MASK;
rs->fcr |= (0x1 << OMAP_UART_FCR_RX_FIFO_TRIG_SHIFT);
rs->fcr |= UART_FCR_ENABLE_FIFO;
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_kernel_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_kernel_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;
/* callback with irq line number + 1 because using line number 0
fails eslewhere */
rs->irq_hook_kernel_id = rs->irq_hook_id = line + 1;
/* sys_irqsetpolicy modifies irq_hook_kernel_id. this modified id
* needs to be used in sys_irqenable and similar calls.
*/
if (sys_irqsetpolicy(rs->irq, 0, &rs->irq_hook_kernel_id) != OK) {
printf("RS232: Couldn't obtain hook for irq %d\n", rs->irq);
} else {
if (sys_irqenable(&rs->irq_hook_kernel_id) != OK) {
printf("RS232: Couldn't enable irq %d (hooked)\n",
rs->irq);
}
}
/* When we get called back we get called back using the original
* hook_id bit set. e.g. if we register with hook_id 5 the callback
* calls us with the 5 th bit set */
rs_irq_set |= (1 << (rs->irq_hook_id ));
/* 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)) {
rs232_handler(rs);
if (sys_irqenable(&rs->irq_hook_kernel_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;
/* check the line status to know if there are more chars */
while (serial_in(rs, OMAP3_LSR) & UART_LSR_DR) {
assert( (serial_in(rs,OMAP3_LSR) & OMAP3_LSR_RXOE) == 0);
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 */