forked from Minki/linux
c5f4644e6c
This patch changes the way serial ports are locked when getting modem status. This change is necessary because we will need to atomically read the modem status and take action depending on the CTS status. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
1310 lines
31 KiB
C
1310 lines
31 KiB
C
/*
|
|
* Driver for 8250/16550-type serial ports
|
|
*
|
|
* Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o.
|
|
*
|
|
* Copyright (C) 2001 Russell King.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* A note about mapbase / membase
|
|
*
|
|
* mapbase is the physical address of the IO port. Currently, we don't
|
|
* support this very well, and it may well be dropped from this driver
|
|
* in future. As such, mapbase should be NULL.
|
|
*
|
|
* membase is an 'ioremapped' cookie. This is compatible with the old
|
|
* serial.c driver, and is currently the preferred form.
|
|
*/
|
|
#include <linux/config.h>
|
|
#include <linux/module.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/init.h>
|
|
#include <linux/console.h>
|
|
#include <linux/sysrq.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/serialP.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <asm/serial.h>
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/mach-au1x00/au1000.h>
|
|
|
|
#if defined(CONFIG_SERIAL_AU1X00_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
|
|
#define SUPPORT_SYSRQ
|
|
#endif
|
|
|
|
#include <linux/serial_core.h>
|
|
#include "8250.h"
|
|
|
|
/*
|
|
* Debugging.
|
|
*/
|
|
#if 0
|
|
#define DEBUG_AUTOCONF(fmt...) printk(fmt)
|
|
#else
|
|
#define DEBUG_AUTOCONF(fmt...) do { } while (0)
|
|
#endif
|
|
|
|
#if 0
|
|
#define DEBUG_INTR(fmt...) printk(fmt)
|
|
#else
|
|
#define DEBUG_INTR(fmt...) do { } while (0)
|
|
#endif
|
|
|
|
#define PASS_LIMIT 256
|
|
|
|
/*
|
|
* We default to IRQ0 for the "no irq" hack. Some
|
|
* machine types want others as well - they're free
|
|
* to redefine this in their header file.
|
|
*/
|
|
#define is_real_interrupt(irq) ((irq) != 0)
|
|
|
|
static struct old_serial_port old_serial_port[] = {
|
|
{ .baud_base = 0,
|
|
.iomem_base = (u8 *)UART0_ADDR,
|
|
.irq = AU1000_UART0_INT,
|
|
.flags = STD_COM_FLAGS,
|
|
.iomem_reg_shift = 2,
|
|
}, {
|
|
.baud_base = 0,
|
|
.iomem_base = (u8 *)UART1_ADDR,
|
|
.irq = AU1000_UART1_INT,
|
|
.flags = STD_COM_FLAGS,
|
|
.iomem_reg_shift = 2
|
|
}, {
|
|
.baud_base = 0,
|
|
.iomem_base = (u8 *)UART2_ADDR,
|
|
.irq = AU1000_UART2_INT,
|
|
.flags = STD_COM_FLAGS,
|
|
.iomem_reg_shift = 2
|
|
}, {
|
|
.baud_base = 0,
|
|
.iomem_base = (u8 *)UART3_ADDR,
|
|
.irq = AU1000_UART3_INT,
|
|
.flags = STD_COM_FLAGS,
|
|
.iomem_reg_shift = 2
|
|
}
|
|
};
|
|
|
|
#define UART_NR ARRAY_SIZE(old_serial_port)
|
|
|
|
struct uart_8250_port {
|
|
struct uart_port port;
|
|
struct timer_list timer; /* "no irq" timer */
|
|
struct list_head list; /* ports on this IRQ */
|
|
unsigned short rev;
|
|
unsigned char acr;
|
|
unsigned char ier;
|
|
unsigned char lcr;
|
|
unsigned char mcr_mask; /* mask of user bits */
|
|
unsigned char mcr_force; /* mask of forced bits */
|
|
unsigned char lsr_break_flag;
|
|
|
|
/*
|
|
* We provide a per-port pm hook.
|
|
*/
|
|
void (*pm)(struct uart_port *port,
|
|
unsigned int state, unsigned int old);
|
|
};
|
|
|
|
struct irq_info {
|
|
spinlock_t lock;
|
|
struct list_head *head;
|
|
};
|
|
|
|
static struct irq_info irq_lists[NR_IRQS];
|
|
|
|
/*
|
|
* Here we define the default xmit fifo size used for each type of UART.
|
|
*/
|
|
static const struct serial_uart_config uart_config[PORT_MAX_8250+1] = {
|
|
{ "unknown", 1, 0 },
|
|
{ "8250", 1, 0 },
|
|
{ "16450", 1, 0 },
|
|
{ "16550", 1, 0 },
|
|
/* PORT_16550A */
|
|
{ "AU1X00_UART",16, UART_CLEAR_FIFO | UART_USE_FIFO },
|
|
};
|
|
|
|
static _INLINE_ unsigned int serial_in(struct uart_8250_port *up, int offset)
|
|
{
|
|
return au_readl((unsigned long)up->port.membase + offset);
|
|
}
|
|
|
|
static _INLINE_ void
|
|
serial_out(struct uart_8250_port *up, int offset, int value)
|
|
{
|
|
au_writel(value, (unsigned long)up->port.membase + offset);
|
|
}
|
|
|
|
#define serial_inp(up, offset) serial_in(up, offset)
|
|
#define serial_outp(up, offset, value) serial_out(up, offset, value)
|
|
|
|
/*
|
|
* This routine is called by rs_init() to initialize a specific serial
|
|
* port. It determines what type of UART chip this serial port is
|
|
* using: 8250, 16450, 16550, 16550A. The important question is
|
|
* whether or not this UART is a 16550A or not, since this will
|
|
* determine whether or not we can use its FIFO features or not.
|
|
*/
|
|
static void autoconfig(struct uart_8250_port *up, unsigned int probeflags)
|
|
{
|
|
unsigned char save_lcr, save_mcr;
|
|
unsigned long flags;
|
|
|
|
if (!up->port.iobase && !up->port.mapbase && !up->port.membase)
|
|
return;
|
|
|
|
DEBUG_AUTOCONF("ttyS%d: autoconf (0x%04x, 0x%08lx): ",
|
|
up->port.line, up->port.iobase, up->port.membase);
|
|
|
|
/*
|
|
* We really do need global IRQs disabled here - we're going to
|
|
* be frobbing the chips IRQ enable register to see if it exists.
|
|
*/
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
// save_flags(flags); cli();
|
|
|
|
save_mcr = serial_in(up, UART_MCR);
|
|
save_lcr = serial_in(up, UART_LCR);
|
|
|
|
up->port.type = PORT_16550A;
|
|
serial_outp(up, UART_LCR, save_lcr);
|
|
|
|
up->port.fifosize = uart_config[up->port.type].dfl_xmit_fifo_size;
|
|
|
|
if (up->port.type == PORT_UNKNOWN)
|
|
goto out;
|
|
|
|
/*
|
|
* Reset the UART.
|
|
*/
|
|
serial_outp(up, UART_MCR, save_mcr);
|
|
serial_outp(up, UART_FCR, (UART_FCR_ENABLE_FIFO |
|
|
UART_FCR_CLEAR_RCVR |
|
|
UART_FCR_CLEAR_XMIT));
|
|
serial_outp(up, UART_FCR, 0);
|
|
(void)serial_in(up, UART_RX);
|
|
serial_outp(up, UART_IER, 0);
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
// restore_flags(flags);
|
|
DEBUG_AUTOCONF("type=%s\n", uart_config[up->port.type].name);
|
|
}
|
|
|
|
static void serial8250_stop_tx(struct uart_port *port, unsigned int tty_stop)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
|
|
if (up->ier & UART_IER_THRI) {
|
|
up->ier &= ~UART_IER_THRI;
|
|
serial_out(up, UART_IER, up->ier);
|
|
}
|
|
}
|
|
|
|
static void serial8250_start_tx(struct uart_port *port, unsigned int tty_start)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
|
|
if (!(up->ier & UART_IER_THRI)) {
|
|
up->ier |= UART_IER_THRI;
|
|
serial_out(up, UART_IER, up->ier);
|
|
}
|
|
}
|
|
|
|
static void serial8250_stop_rx(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
|
|
up->ier &= ~UART_IER_RLSI;
|
|
up->port.read_status_mask &= ~UART_LSR_DR;
|
|
serial_out(up, UART_IER, up->ier);
|
|
}
|
|
|
|
static void serial8250_enable_ms(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
|
|
up->ier |= UART_IER_MSI;
|
|
serial_out(up, UART_IER, up->ier);
|
|
}
|
|
|
|
static _INLINE_ void
|
|
receive_chars(struct uart_8250_port *up, int *status, struct pt_regs *regs)
|
|
{
|
|
struct tty_struct *tty = up->port.info->tty;
|
|
unsigned char ch;
|
|
int max_count = 256;
|
|
|
|
do {
|
|
if (unlikely(tty->flip.count >= TTY_FLIPBUF_SIZE)) {
|
|
tty->flip.work.func((void *)tty);
|
|
if (tty->flip.count >= TTY_FLIPBUF_SIZE)
|
|
return; // if TTY_DONT_FLIP is set
|
|
}
|
|
ch = serial_inp(up, UART_RX);
|
|
*tty->flip.char_buf_ptr = ch;
|
|
*tty->flip.flag_buf_ptr = TTY_NORMAL;
|
|
up->port.icount.rx++;
|
|
|
|
if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE |
|
|
UART_LSR_FE | UART_LSR_OE))) {
|
|
/*
|
|
* For statistics only
|
|
*/
|
|
if (*status & UART_LSR_BI) {
|
|
*status &= ~(UART_LSR_FE | UART_LSR_PE);
|
|
up->port.icount.brk++;
|
|
/*
|
|
* We do the SysRQ and SAK checking
|
|
* here because otherwise the break
|
|
* may get masked by ignore_status_mask
|
|
* or read_status_mask.
|
|
*/
|
|
if (uart_handle_break(&up->port))
|
|
goto ignore_char;
|
|
} else if (*status & UART_LSR_PE)
|
|
up->port.icount.parity++;
|
|
else if (*status & UART_LSR_FE)
|
|
up->port.icount.frame++;
|
|
if (*status & UART_LSR_OE)
|
|
up->port.icount.overrun++;
|
|
|
|
/*
|
|
* Mask off conditions which should be ingored.
|
|
*/
|
|
*status &= up->port.read_status_mask;
|
|
|
|
#ifdef CONFIG_SERIAL_AU1X00_CONSOLE
|
|
if (up->port.line == up->port.cons->index) {
|
|
/* Recover the break flag from console xmit */
|
|
*status |= up->lsr_break_flag;
|
|
up->lsr_break_flag = 0;
|
|
}
|
|
#endif
|
|
if (*status & UART_LSR_BI) {
|
|
DEBUG_INTR("handling break....");
|
|
*tty->flip.flag_buf_ptr = TTY_BREAK;
|
|
} else if (*status & UART_LSR_PE)
|
|
*tty->flip.flag_buf_ptr = TTY_PARITY;
|
|
else if (*status & UART_LSR_FE)
|
|
*tty->flip.flag_buf_ptr = TTY_FRAME;
|
|
}
|
|
if (uart_handle_sysrq_char(&up->port, ch, regs))
|
|
goto ignore_char;
|
|
if ((*status & up->port.ignore_status_mask) == 0) {
|
|
tty->flip.flag_buf_ptr++;
|
|
tty->flip.char_buf_ptr++;
|
|
tty->flip.count++;
|
|
}
|
|
if ((*status & UART_LSR_OE) &&
|
|
tty->flip.count < TTY_FLIPBUF_SIZE) {
|
|
/*
|
|
* Overrun is special, since it's reported
|
|
* immediately, and doesn't affect the current
|
|
* character.
|
|
*/
|
|
*tty->flip.flag_buf_ptr = TTY_OVERRUN;
|
|
tty->flip.flag_buf_ptr++;
|
|
tty->flip.char_buf_ptr++;
|
|
tty->flip.count++;
|
|
}
|
|
ignore_char:
|
|
*status = serial_inp(up, UART_LSR);
|
|
} while ((*status & UART_LSR_DR) && (max_count-- > 0));
|
|
spin_unlock(&up->port.lock);
|
|
tty_flip_buffer_push(tty);
|
|
spin_lock(&up->port.lock);
|
|
}
|
|
|
|
static _INLINE_ void transmit_chars(struct uart_8250_port *up)
|
|
{
|
|
struct circ_buf *xmit = &up->port.info->xmit;
|
|
int count;
|
|
|
|
if (up->port.x_char) {
|
|
serial_outp(up, UART_TX, up->port.x_char);
|
|
up->port.icount.tx++;
|
|
up->port.x_char = 0;
|
|
return;
|
|
}
|
|
if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {
|
|
serial8250_stop_tx(&up->port, 0);
|
|
return;
|
|
}
|
|
|
|
count = up->port.fifosize;
|
|
do {
|
|
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
|
|
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
|
up->port.icount.tx++;
|
|
if (uart_circ_empty(xmit))
|
|
break;
|
|
} while (--count > 0);
|
|
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
uart_write_wakeup(&up->port);
|
|
|
|
DEBUG_INTR("THRE...");
|
|
|
|
if (uart_circ_empty(xmit))
|
|
serial8250_stop_tx(&up->port, 0);
|
|
}
|
|
|
|
static _INLINE_ void check_modem_status(struct uart_8250_port *up)
|
|
{
|
|
int status;
|
|
|
|
status = serial_in(up, UART_MSR);
|
|
|
|
if ((status & UART_MSR_ANY_DELTA) == 0)
|
|
return;
|
|
|
|
if (status & UART_MSR_TERI)
|
|
up->port.icount.rng++;
|
|
if (status & UART_MSR_DDSR)
|
|
up->port.icount.dsr++;
|
|
if (status & UART_MSR_DDCD)
|
|
uart_handle_dcd_change(&up->port, status & UART_MSR_DCD);
|
|
if (status & UART_MSR_DCTS)
|
|
uart_handle_cts_change(&up->port, status & UART_MSR_CTS);
|
|
|
|
wake_up_interruptible(&up->port.info->delta_msr_wait);
|
|
}
|
|
|
|
/*
|
|
* This handles the interrupt from one port.
|
|
*/
|
|
static inline void
|
|
serial8250_handle_port(struct uart_8250_port *up, struct pt_regs *regs)
|
|
{
|
|
unsigned int status = serial_inp(up, UART_LSR);
|
|
|
|
DEBUG_INTR("status = %x...", status);
|
|
|
|
if (status & UART_LSR_DR)
|
|
receive_chars(up, &status, regs);
|
|
check_modem_status(up);
|
|
if (status & UART_LSR_THRE)
|
|
transmit_chars(up);
|
|
}
|
|
|
|
/*
|
|
* This is the serial driver's interrupt routine.
|
|
*
|
|
* Arjan thinks the old way was overly complex, so it got simplified.
|
|
* Alan disagrees, saying that need the complexity to handle the weird
|
|
* nature of ISA shared interrupts. (This is a special exception.)
|
|
*
|
|
* In order to handle ISA shared interrupts properly, we need to check
|
|
* that all ports have been serviced, and therefore the ISA interrupt
|
|
* line has been de-asserted.
|
|
*
|
|
* This means we need to loop through all ports. checking that they
|
|
* don't have an interrupt pending.
|
|
*/
|
|
static irqreturn_t serial8250_interrupt(int irq, void *dev_id, struct pt_regs *regs)
|
|
{
|
|
struct irq_info *i = dev_id;
|
|
struct list_head *l, *end = NULL;
|
|
int pass_counter = 0;
|
|
|
|
DEBUG_INTR("serial8250_interrupt(%d)...", irq);
|
|
|
|
spin_lock(&i->lock);
|
|
|
|
l = i->head;
|
|
do {
|
|
struct uart_8250_port *up;
|
|
unsigned int iir;
|
|
|
|
up = list_entry(l, struct uart_8250_port, list);
|
|
|
|
iir = serial_in(up, UART_IIR);
|
|
if (!(iir & UART_IIR_NO_INT)) {
|
|
spin_lock(&up->port.lock);
|
|
serial8250_handle_port(up, regs);
|
|
spin_unlock(&up->port.lock);
|
|
|
|
end = NULL;
|
|
} else if (end == NULL)
|
|
end = l;
|
|
|
|
l = l->next;
|
|
|
|
if (l == i->head && pass_counter++ > PASS_LIMIT) {
|
|
/* If we hit this, we're dead. */
|
|
printk(KERN_ERR "serial8250: too much work for "
|
|
"irq%d\n", irq);
|
|
break;
|
|
}
|
|
} while (l != end);
|
|
|
|
spin_unlock(&i->lock);
|
|
|
|
DEBUG_INTR("end.\n");
|
|
/* FIXME! Was it really ours? */
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* To support ISA shared interrupts, we need to have one interrupt
|
|
* handler that ensures that the IRQ line has been deasserted
|
|
* before returning. Failing to do this will result in the IRQ
|
|
* line being stuck active, and, since ISA irqs are edge triggered,
|
|
* no more IRQs will be seen.
|
|
*/
|
|
static void serial_do_unlink(struct irq_info *i, struct uart_8250_port *up)
|
|
{
|
|
spin_lock_irq(&i->lock);
|
|
|
|
if (!list_empty(i->head)) {
|
|
if (i->head == &up->list)
|
|
i->head = i->head->next;
|
|
list_del(&up->list);
|
|
} else {
|
|
BUG_ON(i->head != &up->list);
|
|
i->head = NULL;
|
|
}
|
|
|
|
spin_unlock_irq(&i->lock);
|
|
}
|
|
|
|
static int serial_link_irq_chain(struct uart_8250_port *up)
|
|
{
|
|
struct irq_info *i = irq_lists + up->port.irq;
|
|
int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? SA_SHIRQ : 0;
|
|
|
|
spin_lock_irq(&i->lock);
|
|
|
|
if (i->head) {
|
|
list_add(&up->list, i->head);
|
|
spin_unlock_irq(&i->lock);
|
|
|
|
ret = 0;
|
|
} else {
|
|
INIT_LIST_HEAD(&up->list);
|
|
i->head = &up->list;
|
|
spin_unlock_irq(&i->lock);
|
|
|
|
ret = request_irq(up->port.irq, serial8250_interrupt,
|
|
irq_flags, "serial", i);
|
|
if (ret < 0)
|
|
serial_do_unlink(i, up);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void serial_unlink_irq_chain(struct uart_8250_port *up)
|
|
{
|
|
struct irq_info *i = irq_lists + up->port.irq;
|
|
|
|
BUG_ON(i->head == NULL);
|
|
|
|
if (list_empty(i->head))
|
|
free_irq(up->port.irq, i);
|
|
|
|
serial_do_unlink(i, up);
|
|
}
|
|
|
|
/*
|
|
* This function is used to handle ports that do not have an
|
|
* interrupt. This doesn't work very well for 16450's, but gives
|
|
* barely passable results for a 16550A. (Although at the expense
|
|
* of much CPU overhead).
|
|
*/
|
|
static void serial8250_timeout(unsigned long data)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)data;
|
|
unsigned int timeout;
|
|
unsigned int iir;
|
|
|
|
iir = serial_in(up, UART_IIR);
|
|
if (!(iir & UART_IIR_NO_INT)) {
|
|
spin_lock(&up->port.lock);
|
|
serial8250_handle_port(up, NULL);
|
|
spin_unlock(&up->port.lock);
|
|
}
|
|
|
|
timeout = up->port.timeout;
|
|
timeout = timeout > 6 ? (timeout / 2 - 2) : 1;
|
|
mod_timer(&up->timer, jiffies + timeout);
|
|
}
|
|
|
|
static unsigned int serial8250_tx_empty(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned long flags;
|
|
unsigned int ret;
|
|
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
ret = serial_in(up, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int serial8250_get_mctrl(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned char status;
|
|
unsigned int ret;
|
|
|
|
status = serial_in(up, UART_MSR);
|
|
|
|
ret = 0;
|
|
if (status & UART_MSR_DCD)
|
|
ret |= TIOCM_CAR;
|
|
if (status & UART_MSR_RI)
|
|
ret |= TIOCM_RNG;
|
|
if (status & UART_MSR_DSR)
|
|
ret |= TIOCM_DSR;
|
|
if (status & UART_MSR_CTS)
|
|
ret |= TIOCM_CTS;
|
|
return ret;
|
|
}
|
|
|
|
static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned char mcr = 0;
|
|
|
|
if (mctrl & TIOCM_RTS)
|
|
mcr |= UART_MCR_RTS;
|
|
if (mctrl & TIOCM_DTR)
|
|
mcr |= UART_MCR_DTR;
|
|
if (mctrl & TIOCM_OUT1)
|
|
mcr |= UART_MCR_OUT1;
|
|
if (mctrl & TIOCM_OUT2)
|
|
mcr |= UART_MCR_OUT2;
|
|
if (mctrl & TIOCM_LOOP)
|
|
mcr |= UART_MCR_LOOP;
|
|
|
|
mcr = (mcr & up->mcr_mask) | up->mcr_force;
|
|
|
|
serial_out(up, UART_MCR, mcr);
|
|
}
|
|
|
|
static void serial8250_break_ctl(struct uart_port *port, int break_state)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
if (break_state == -1)
|
|
up->lcr |= UART_LCR_SBC;
|
|
else
|
|
up->lcr &= ~UART_LCR_SBC;
|
|
serial_out(up, UART_LCR, up->lcr);
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
}
|
|
|
|
static int serial8250_startup(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned long flags;
|
|
int retval;
|
|
|
|
/*
|
|
* Clear the FIFO buffers and disable them.
|
|
* (they will be reeanbled in set_termios())
|
|
*/
|
|
if (uart_config[up->port.type].flags & UART_CLEAR_FIFO) {
|
|
serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO);
|
|
serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO |
|
|
UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
|
|
serial_outp(up, UART_FCR, 0);
|
|
}
|
|
|
|
/*
|
|
* Clear the interrupt registers.
|
|
*/
|
|
(void) serial_inp(up, UART_LSR);
|
|
(void) serial_inp(up, UART_RX);
|
|
(void) serial_inp(up, UART_IIR);
|
|
(void) serial_inp(up, UART_MSR);
|
|
|
|
/*
|
|
* At this point, there's no way the LSR could still be 0xff;
|
|
* if it is, then bail out, because there's likely no UART
|
|
* here.
|
|
*/
|
|
if (!(up->port.flags & UPF_BUGGY_UART) &&
|
|
(serial_inp(up, UART_LSR) == 0xff)) {
|
|
printk("ttyS%d: LSR safety check engaged!\n", up->port.line);
|
|
return -ENODEV;
|
|
}
|
|
|
|
retval = serial_link_irq_chain(up);
|
|
if (retval)
|
|
return retval;
|
|
|
|
/*
|
|
* Now, initialize the UART
|
|
*/
|
|
serial_outp(up, UART_LCR, UART_LCR_WLEN8);
|
|
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
if (up->port.flags & UPF_FOURPORT) {
|
|
if (!is_real_interrupt(up->port.irq))
|
|
up->port.mctrl |= TIOCM_OUT1;
|
|
} else
|
|
/*
|
|
* Most PC uarts need OUT2 raised to enable interrupts.
|
|
*/
|
|
if (is_real_interrupt(up->port.irq))
|
|
up->port.mctrl |= TIOCM_OUT2;
|
|
|
|
serial8250_set_mctrl(&up->port, up->port.mctrl);
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
|
|
/*
|
|
* Finally, enable interrupts. Note: Modem status interrupts
|
|
* are set via set_termios(), which will be occurring imminently
|
|
* anyway, so we don't enable them here.
|
|
*/
|
|
up->ier = UART_IER_RLSI | UART_IER_RDI;
|
|
serial_outp(up, UART_IER, up->ier);
|
|
|
|
if (up->port.flags & UPF_FOURPORT) {
|
|
unsigned int icp;
|
|
/*
|
|
* Enable interrupts on the AST Fourport board
|
|
*/
|
|
icp = (up->port.iobase & 0xfe0) | 0x01f;
|
|
outb_p(0x80, icp);
|
|
(void) inb_p(icp);
|
|
}
|
|
|
|
/*
|
|
* And clear the interrupt registers again for luck.
|
|
*/
|
|
(void) serial_inp(up, UART_LSR);
|
|
(void) serial_inp(up, UART_RX);
|
|
(void) serial_inp(up, UART_IIR);
|
|
(void) serial_inp(up, UART_MSR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void serial8250_shutdown(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* Disable interrupts from this port
|
|
*/
|
|
up->ier = 0;
|
|
serial_outp(up, UART_IER, 0);
|
|
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
if (up->port.flags & UPF_FOURPORT) {
|
|
/* reset interrupts on the AST Fourport board */
|
|
inb((up->port.iobase & 0xfe0) | 0x1f);
|
|
up->port.mctrl |= TIOCM_OUT1;
|
|
} else
|
|
up->port.mctrl &= ~TIOCM_OUT2;
|
|
|
|
serial8250_set_mctrl(&up->port, up->port.mctrl);
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
|
|
/*
|
|
* Disable break condition and FIFOs
|
|
*/
|
|
serial_out(up, UART_LCR, serial_inp(up, UART_LCR) & ~UART_LCR_SBC);
|
|
serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO |
|
|
UART_FCR_CLEAR_RCVR |
|
|
UART_FCR_CLEAR_XMIT);
|
|
serial_outp(up, UART_FCR, 0);
|
|
|
|
/*
|
|
* Read data port to reset things, and then unlink from
|
|
* the IRQ chain.
|
|
*/
|
|
(void) serial_in(up, UART_RX);
|
|
|
|
if (!is_real_interrupt(up->port.irq))
|
|
del_timer_sync(&up->timer);
|
|
else
|
|
serial_unlink_irq_chain(up);
|
|
}
|
|
|
|
static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud)
|
|
{
|
|
unsigned int quot;
|
|
|
|
/*
|
|
* Handle magic divisors for baud rates above baud_base on
|
|
* SMSC SuperIO chips.
|
|
*/
|
|
if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
|
|
baud == (port->uartclk/4))
|
|
quot = 0x8001;
|
|
else if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
|
|
baud == (port->uartclk/8))
|
|
quot = 0x8002;
|
|
else
|
|
quot = uart_get_divisor(port, baud);
|
|
|
|
return quot;
|
|
}
|
|
|
|
static void
|
|
serial8250_set_termios(struct uart_port *port, struct termios *termios,
|
|
struct termios *old)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned char cval, fcr = 0;
|
|
unsigned long flags;
|
|
unsigned int baud, quot;
|
|
|
|
switch (termios->c_cflag & CSIZE) {
|
|
case CS5:
|
|
cval = UART_LCR_WLEN5;
|
|
break;
|
|
case CS6:
|
|
cval = UART_LCR_WLEN6;
|
|
break;
|
|
case CS7:
|
|
cval = UART_LCR_WLEN7;
|
|
break;
|
|
default:
|
|
case CS8:
|
|
cval = UART_LCR_WLEN8;
|
|
break;
|
|
}
|
|
|
|
if (termios->c_cflag & CSTOPB)
|
|
cval |= UART_LCR_STOP;
|
|
if (termios->c_cflag & PARENB)
|
|
cval |= UART_LCR_PARITY;
|
|
if (!(termios->c_cflag & PARODD))
|
|
cval |= UART_LCR_EPAR;
|
|
#ifdef CMSPAR
|
|
if (termios->c_cflag & CMSPAR)
|
|
cval |= UART_LCR_SPAR;
|
|
#endif
|
|
|
|
/*
|
|
* Ask the core to calculate the divisor for us.
|
|
*/
|
|
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
|
|
quot = serial8250_get_divisor(port, baud);
|
|
quot = 0x35; /* FIXME */
|
|
|
|
/*
|
|
* Work around a bug in the Oxford Semiconductor 952 rev B
|
|
* chip which causes it to seriously miscalculate baud rates
|
|
* when DLL is 0.
|
|
*/
|
|
if ((quot & 0xff) == 0 && up->port.type == PORT_16C950 &&
|
|
up->rev == 0x5201)
|
|
quot ++;
|
|
|
|
if (uart_config[up->port.type].flags & UART_USE_FIFO) {
|
|
if (baud < 2400)
|
|
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIGGER_1;
|
|
else
|
|
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIGGER_8;
|
|
}
|
|
|
|
/*
|
|
* Ok, we're now changing the port state. Do it with
|
|
* interrupts disabled.
|
|
*/
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
|
|
/*
|
|
* Update the per-port timeout.
|
|
*/
|
|
uart_update_timeout(port, termios->c_cflag, baud);
|
|
|
|
up->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
|
|
if (termios->c_iflag & INPCK)
|
|
up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
|
|
if (termios->c_iflag & (BRKINT | PARMRK))
|
|
up->port.read_status_mask |= UART_LSR_BI;
|
|
|
|
/*
|
|
* Characteres to ignore
|
|
*/
|
|
up->port.ignore_status_mask = 0;
|
|
if (termios->c_iflag & IGNPAR)
|
|
up->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
|
|
if (termios->c_iflag & IGNBRK) {
|
|
up->port.ignore_status_mask |= UART_LSR_BI;
|
|
/*
|
|
* If we're ignoring parity and break indicators,
|
|
* ignore overruns too (for real raw support).
|
|
*/
|
|
if (termios->c_iflag & IGNPAR)
|
|
up->port.ignore_status_mask |= UART_LSR_OE;
|
|
}
|
|
|
|
/*
|
|
* ignore all characters if CREAD is not set
|
|
*/
|
|
if ((termios->c_cflag & CREAD) == 0)
|
|
up->port.ignore_status_mask |= UART_LSR_DR;
|
|
|
|
/*
|
|
* CTS flow control flag and modem status interrupts
|
|
*/
|
|
up->ier &= ~UART_IER_MSI;
|
|
if (UART_ENABLE_MS(&up->port, termios->c_cflag))
|
|
up->ier |= UART_IER_MSI;
|
|
|
|
serial_out(up, UART_IER, up->ier);
|
|
serial_outp(up, 0x28, quot & 0xffff);
|
|
up->lcr = cval; /* Save LCR */
|
|
if (up->port.type != PORT_16750) {
|
|
if (fcr & UART_FCR_ENABLE_FIFO) {
|
|
/* emulated UARTs (Lucent Venus 167x) need two steps */
|
|
serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO);
|
|
}
|
|
serial_outp(up, UART_FCR, fcr); /* set fcr */
|
|
}
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
}
|
|
|
|
static void
|
|
serial8250_pm(struct uart_port *port, unsigned int state,
|
|
unsigned int oldstate)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
if (state) {
|
|
/* sleep */
|
|
if (up->pm)
|
|
up->pm(port, state, oldstate);
|
|
} else {
|
|
/* wake */
|
|
if (up->pm)
|
|
up->pm(port, state, oldstate);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Resource handling. This is complicated by the fact that resources
|
|
* depend on the port type. Maybe we should be claiming the standard
|
|
* 8250 ports, and then trying to get other resources as necessary?
|
|
*/
|
|
static int
|
|
serial8250_request_std_resource(struct uart_8250_port *up, struct resource **res)
|
|
{
|
|
unsigned int size = 8 << up->port.regshift;
|
|
int ret = 0;
|
|
|
|
switch (up->port.iotype) {
|
|
case SERIAL_IO_MEM:
|
|
if (up->port.mapbase) {
|
|
*res = request_mem_region(up->port.mapbase, size, "serial");
|
|
if (!*res)
|
|
ret = -EBUSY;
|
|
}
|
|
break;
|
|
|
|
case SERIAL_IO_HUB6:
|
|
case SERIAL_IO_PORT:
|
|
*res = request_region(up->port.iobase, size, "serial");
|
|
if (!*res)
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void serial8250_release_port(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
unsigned long start, offset = 0, size = 0;
|
|
|
|
size <<= up->port.regshift;
|
|
|
|
switch (up->port.iotype) {
|
|
case SERIAL_IO_MEM:
|
|
if (up->port.mapbase) {
|
|
/*
|
|
* Unmap the area.
|
|
*/
|
|
iounmap(up->port.membase);
|
|
up->port.membase = NULL;
|
|
|
|
start = up->port.mapbase;
|
|
|
|
if (size)
|
|
release_mem_region(start + offset, size);
|
|
release_mem_region(start, 8 << up->port.regshift);
|
|
}
|
|
break;
|
|
|
|
case SERIAL_IO_HUB6:
|
|
case SERIAL_IO_PORT:
|
|
start = up->port.iobase;
|
|
|
|
if (size)
|
|
release_region(start + offset, size);
|
|
release_region(start + offset, 8 << up->port.regshift);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int serial8250_request_port(struct uart_port *port)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
struct resource *res = NULL, *res_rsa = NULL;
|
|
int ret = 0;
|
|
|
|
ret = serial8250_request_std_resource(up, &res);
|
|
|
|
/*
|
|
* If we have a mapbase, then request that as well.
|
|
*/
|
|
if (ret == 0 && up->port.flags & UPF_IOREMAP) {
|
|
int size = res->end - res->start + 1;
|
|
|
|
up->port.membase = ioremap(up->port.mapbase, size);
|
|
if (!up->port.membase)
|
|
ret = -ENOMEM;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
if (res_rsa)
|
|
release_resource(res_rsa);
|
|
if (res)
|
|
release_resource(res);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void serial8250_config_port(struct uart_port *port, int flags)
|
|
{
|
|
struct uart_8250_port *up = (struct uart_8250_port *)port;
|
|
struct resource *res_std = NULL, *res_rsa = NULL;
|
|
int probeflags = PROBE_ANY;
|
|
|
|
probeflags &= ~PROBE_RSA;
|
|
|
|
if (flags & UART_CONFIG_TYPE)
|
|
autoconfig(up, probeflags);
|
|
|
|
/*
|
|
* If the port wasn't an RSA port, release the resource.
|
|
*/
|
|
if (up->port.type != PORT_RSA && res_rsa)
|
|
release_resource(res_rsa);
|
|
|
|
if (up->port.type == PORT_UNKNOWN && res_std)
|
|
release_resource(res_std);
|
|
}
|
|
|
|
static int
|
|
serial8250_verify_port(struct uart_port *port, struct serial_struct *ser)
|
|
{
|
|
if (ser->irq >= NR_IRQS || ser->irq < 0 ||
|
|
ser->baud_base < 9600 || ser->type < PORT_UNKNOWN ||
|
|
ser->type > PORT_MAX_8250 || ser->type == PORT_CIRRUS ||
|
|
ser->type == PORT_STARTECH)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
static const char *
|
|
serial8250_type(struct uart_port *port)
|
|
{
|
|
int type = port->type;
|
|
|
|
if (type >= ARRAY_SIZE(uart_config))
|
|
type = 0;
|
|
return uart_config[type].name;
|
|
}
|
|
|
|
static struct uart_ops serial8250_pops = {
|
|
.tx_empty = serial8250_tx_empty,
|
|
.set_mctrl = serial8250_set_mctrl,
|
|
.get_mctrl = serial8250_get_mctrl,
|
|
.stop_tx = serial8250_stop_tx,
|
|
.start_tx = serial8250_start_tx,
|
|
.stop_rx = serial8250_stop_rx,
|
|
.enable_ms = serial8250_enable_ms,
|
|
.break_ctl = serial8250_break_ctl,
|
|
.startup = serial8250_startup,
|
|
.shutdown = serial8250_shutdown,
|
|
.set_termios = serial8250_set_termios,
|
|
.pm = serial8250_pm,
|
|
.type = serial8250_type,
|
|
.release_port = serial8250_release_port,
|
|
.request_port = serial8250_request_port,
|
|
.config_port = serial8250_config_port,
|
|
.verify_port = serial8250_verify_port,
|
|
};
|
|
|
|
static struct uart_8250_port serial8250_ports[UART_NR];
|
|
|
|
static void __init serial8250_isa_init_ports(void)
|
|
{
|
|
struct uart_8250_port *up;
|
|
static int first = 1;
|
|
int i;
|
|
|
|
if (!first)
|
|
return;
|
|
first = 0;
|
|
|
|
for (i = 0, up = serial8250_ports; i < ARRAY_SIZE(old_serial_port);
|
|
i++, up++) {
|
|
up->port.iobase = old_serial_port[i].port;
|
|
up->port.irq = old_serial_port[i].irq;
|
|
up->port.uartclk = get_au1x00_uart_baud_base();
|
|
up->port.flags = old_serial_port[i].flags;
|
|
up->port.hub6 = old_serial_port[i].hub6;
|
|
up->port.membase = old_serial_port[i].iomem_base;
|
|
up->port.iotype = old_serial_port[i].io_type;
|
|
up->port.regshift = old_serial_port[i].iomem_reg_shift;
|
|
up->port.ops = &serial8250_pops;
|
|
}
|
|
}
|
|
|
|
static void __init serial8250_register_ports(struct uart_driver *drv)
|
|
{
|
|
int i;
|
|
|
|
serial8250_isa_init_ports();
|
|
|
|
for (i = 0; i < UART_NR; i++) {
|
|
struct uart_8250_port *up = &serial8250_ports[i];
|
|
|
|
up->port.line = i;
|
|
up->port.ops = &serial8250_pops;
|
|
init_timer(&up->timer);
|
|
up->timer.function = serial8250_timeout;
|
|
|
|
/*
|
|
* ALPHA_KLUDGE_MCR needs to be killed.
|
|
*/
|
|
up->mcr_mask = ~ALPHA_KLUDGE_MCR;
|
|
up->mcr_force = ALPHA_KLUDGE_MCR;
|
|
|
|
uart_add_one_port(drv, &up->port);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_SERIAL_AU1X00_CONSOLE
|
|
|
|
#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
|
|
|
|
/*
|
|
* Wait for transmitter & holding register to empty
|
|
*/
|
|
static inline void wait_for_xmitr(struct uart_8250_port *up)
|
|
{
|
|
unsigned int status, tmout = 10000;
|
|
|
|
/* Wait up to 10ms for the character(s) to be sent. */
|
|
do {
|
|
status = serial_in(up, UART_LSR);
|
|
|
|
if (status & UART_LSR_BI)
|
|
up->lsr_break_flag = UART_LSR_BI;
|
|
|
|
if (--tmout == 0)
|
|
break;
|
|
udelay(1);
|
|
} while ((status & BOTH_EMPTY) != BOTH_EMPTY);
|
|
|
|
/* Wait up to 1s for flow control if necessary */
|
|
if (up->port.flags & UPF_CONS_FLOW) {
|
|
tmout = 1000000;
|
|
while (--tmout &&
|
|
((serial_in(up, UART_MSR) & UART_MSR_CTS) == 0))
|
|
udelay(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print a string to the serial port trying not to disturb
|
|
* any possible real use of the port...
|
|
*
|
|
* The console_lock must be held when we get here.
|
|
*/
|
|
static void
|
|
serial8250_console_write(struct console *co, const char *s, unsigned int count)
|
|
{
|
|
struct uart_8250_port *up = &serial8250_ports[co->index];
|
|
unsigned int ier;
|
|
int i;
|
|
|
|
/*
|
|
* First save the UER then disable the interrupts
|
|
*/
|
|
ier = serial_in(up, UART_IER);
|
|
serial_out(up, UART_IER, 0);
|
|
|
|
/*
|
|
* Now, do each character
|
|
*/
|
|
for (i = 0; i < count; i++, s++) {
|
|
wait_for_xmitr(up);
|
|
|
|
/*
|
|
* Send the character out.
|
|
* If a LF, also do CR...
|
|
*/
|
|
serial_out(up, UART_TX, *s);
|
|
if (*s == 10) {
|
|
wait_for_xmitr(up);
|
|
serial_out(up, UART_TX, 13);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Finally, wait for transmitter to become empty
|
|
* and restore the IER
|
|
*/
|
|
wait_for_xmitr(up);
|
|
serial_out(up, UART_IER, ier);
|
|
}
|
|
|
|
static int __init serial8250_console_setup(struct console *co, char *options)
|
|
{
|
|
struct uart_port *port;
|
|
int baud = 9600;
|
|
int bits = 8;
|
|
int parity = 'n';
|
|
int flow = 'n';
|
|
|
|
/*
|
|
* Check whether an invalid uart number has been specified, and
|
|
* if so, search for the first available port that does have
|
|
* console support.
|
|
*/
|
|
if (co->index >= UART_NR)
|
|
co->index = 0;
|
|
port = &serial8250_ports[co->index].port;
|
|
|
|
/*
|
|
* Temporary fix.
|
|
*/
|
|
spin_lock_init(&port->lock);
|
|
|
|
if (options)
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
|
|
return uart_set_options(port, co, baud, parity, bits, flow);
|
|
}
|
|
|
|
extern struct uart_driver serial8250_reg;
|
|
static struct console serial8250_console = {
|
|
.name = "ttyS",
|
|
.write = serial8250_console_write,
|
|
.device = uart_console_device,
|
|
.setup = serial8250_console_setup,
|
|
.flags = CON_PRINTBUFFER,
|
|
.index = -1,
|
|
.data = &serial8250_reg,
|
|
};
|
|
|
|
static int __init serial8250_console_init(void)
|
|
{
|
|
serial8250_isa_init_ports();
|
|
register_console(&serial8250_console);
|
|
return 0;
|
|
}
|
|
console_initcall(serial8250_console_init);
|
|
|
|
#define SERIAL8250_CONSOLE &serial8250_console
|
|
#else
|
|
#define SERIAL8250_CONSOLE NULL
|
|
#endif
|
|
|
|
static struct uart_driver serial8250_reg = {
|
|
.owner = THIS_MODULE,
|
|
.driver_name = "serial",
|
|
.devfs_name = "tts/",
|
|
.dev_name = "ttyS",
|
|
.major = TTY_MAJOR,
|
|
.minor = 64,
|
|
.nr = UART_NR,
|
|
.cons = SERIAL8250_CONSOLE,
|
|
};
|
|
|
|
int __init early_serial_setup(struct uart_port *port)
|
|
{
|
|
serial8250_isa_init_ports();
|
|
serial8250_ports[port->line].port = *port;
|
|
serial8250_ports[port->line].port.ops = &serial8250_pops;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* serial8250_suspend_port - suspend one serial port
|
|
* @line: serial line number
|
|
* @level: the level of port suspension, as per uart_suspend_port
|
|
*
|
|
* Suspend one serial port.
|
|
*/
|
|
void serial8250_suspend_port(int line)
|
|
{
|
|
uart_suspend_port(&serial8250_reg, &serial8250_ports[line].port);
|
|
}
|
|
|
|
/**
|
|
* serial8250_resume_port - resume one serial port
|
|
* @line: serial line number
|
|
* @level: the level of port resumption, as per uart_resume_port
|
|
*
|
|
* Resume one serial port.
|
|
*/
|
|
void serial8250_resume_port(int line)
|
|
{
|
|
uart_resume_port(&serial8250_reg, &serial8250_ports[line].port);
|
|
}
|
|
|
|
static int __init serial8250_init(void)
|
|
{
|
|
int ret, i;
|
|
|
|
printk(KERN_INFO "Serial: Au1x00 driver\n");
|
|
|
|
for (i = 0; i < NR_IRQS; i++)
|
|
spin_lock_init(&irq_lists[i].lock);
|
|
|
|
ret = uart_register_driver(&serial8250_reg);
|
|
if (ret >= 0)
|
|
serial8250_register_ports(&serial8250_reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit serial8250_exit(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < UART_NR; i++)
|
|
uart_remove_one_port(&serial8250_reg, &serial8250_ports[i].port);
|
|
|
|
uart_unregister_driver(&serial8250_reg);
|
|
}
|
|
|
|
module_init(serial8250_init);
|
|
module_exit(serial8250_exit);
|
|
|
|
EXPORT_SYMBOL(serial8250_suspend_port);
|
|
EXPORT_SYMBOL(serial8250_resume_port);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Au1x00 serial driver\n");
|