summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/serial/Kconfig31
-rw-r--r--drivers/serial/Makefile2
-rw-r--r--drivers/serial/bast_sio.c63
-rw-r--r--drivers/serial/s3c2410.c860
-rw-r--r--include/linux/serial_core.h3
5 files changed, 959 insertions, 0 deletions
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 2cc74988e54a..3306478df6d7 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -224,6 +224,37 @@ config SERIAL_CLPS711X_CONSOLE
your boot loader (lilo or loadlin) about how to pass options to the
kernel at boot time.)
+config SERIAL_S3C2410
+ tristate "Samsung S3C2410 Serial port support"
+ depends on ARM && ARCH_S3C2410
+ select SERIAL_CORE
+ help
+ Support for the on-chip UARTs on the Samsung S3C2410X CPU,
+ providing /dev/ttySAC0, 1 and 2 (note, some machines may not
+ provide all of these ports, depending on how the serial port
+ pins are configured.
+
+config SERIAL_S3C2410_CONSOLE
+ bool "Support for console on S3C2410 serial port"
+ depends on SERIAL_S3C2410=y
+ help
+ Allow selection of the S3C2410 on-board serial ports for use as
+ an virtual console.
+
+ Even if you say Y here, the currently visible virtual console
+ (/dev/tty0) will still be used as the system console by default, but
+ you can alter that using a kernel command line option such as
+ "console=ttySACx". (Try "man bootparam" or see the documentation of
+ your boot loader about how to pass options to the kernel at
+ boot time.)
+
+config SERIAL_BAST_SIO
+ bool "Support for BAST SuperIO serial ports"
+ depends on ARCH_BAST && SERIAL_8250=y
+ help
+ Support for registerin the SuperIO chip on BAST board with
+ the 8250/16550 uart code.
+
config SERIAL_DZ
bool "DECstation DZ serial driver"
depends on DECSTATION
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 1b0271e9f132..ad1a21d93e36 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_AMBA) += amba.o
obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
obj-$(CONFIG_SERIAL_PXA) += pxa.o
obj-$(CONFIG_SERIAL_SA1100) += sa1100.o
+obj-$(CONFIG_SERIAL_S3C2410) += s3c2410.o
obj-$(CONFIG_SERIAL_UART00) += uart00.o
obj-$(CONFIG_SERIAL_SUNCORE) += suncore.o
obj-$(CONFIG_SERIAL_SUNZILOG) += sunzilog.o
@@ -36,3 +37,4 @@ obj-$(CONFIG_SERIAL_PMACZILOG) += pmac_zilog.o
obj-$(CONFIG_SERIAL_AU1X00) += au1x00_uart.o
obj-$(CONFIG_SERIAL_DZ) += dz.o
obj-$(CONFIG_SERIAL_SH_SCI) += sh-sci.o
+obj-$(CONFIG_SERIAL_BAST_SIO) += bast_sio.o
diff --git a/drivers/serial/bast_sio.c b/drivers/serial/bast_sio.c
new file mode 100644
index 000000000000..ea9fd71b5351
--- /dev/null
+++ b/drivers/serial/bast_sio.c
@@ -0,0 +1,63 @@
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/types.h>
+
+#include <asm/io.h>
+#include <asm/serial.h>
+#include <asm/mach-types.h>
+
+#include <asm/arch/map.h>
+#include <asm/arch/irqs.h>
+#include <asm/arch/bast-map.h>
+#include <asm/arch/bast-irq.h>
+
+static int __init serial_bast_register(unsigned long port, unsigned int irq)
+{
+ struct serial_struct serial_req;
+
+#if 0
+ printk("BAST: SuperIO serial (%08lx,%d)\n", port, irq);
+#endif
+
+ serial_req.flags = UPF_AUTOPROBE | UPF_RESOURCES | UPF_SHARE_IRQ;
+ serial_req.baud_base = BASE_BAUD;
+ serial_req.irq = irq;
+ serial_req.io_type = UPIO_MEM;
+ serial_req.iomap_base = port;
+ serial_req.iomem_base = ioremap(port, 0x10);
+ serial_req.iomem_reg_shift = 0;
+
+ return register_serial(&serial_req);
+}
+
+#define SERIAL_BASE (S3C2410_CS2 + BAST_PA_SUPERIO)
+
+static int __init serial_bast_init(void)
+{
+ if (machine_is_bast()) {
+ serial_bast_register(SERIAL_BASE + 0x2f8, IRQ_PCSERIAL1);
+ serial_bast_register(SERIAL_BASE + 0x3f8, IRQ_PCSERIAL2);
+ }
+
+ return 0;
+}
+
+static void __exit serial_bast_exit(void)
+{
+ /* todo -> remove both our ports */
+}
+
+
+module_init(serial_bast_init);
+module_exit(serial_bast_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ben Dooks, ben@simtec.co.uk");
+MODULE_DESCRIPTION("BAST Onboard Serial setup");
+
+
diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c
new file mode 100644
index 000000000000..84b4a8e0afbe
--- /dev/null
+++ b/drivers/serial/s3c2410.c
@@ -0,0 +1,860 @@
+/*
+ * linux/drivers/char/s3c2410.c
+ *
+ * Driver for onboard UARTs on the Samsung S3C2410
+ *
+ * Based on drivers/char/serial.c and drivers/char/21285.c
+ *
+ * Ben Dooks, (c) 2003 Simtec Electronics
+ *
+ * Changelog:
+ *
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <asm/hardware.h>
+#include <asm/arch/regs-serial.h>
+
+#include <asm/mach-types.h>
+
+#if 0
+#include <asm/debug-ll.h>
+#define dbg(x...) llprintk(x)
+#else
+#define dbg(x...)
+#endif
+
+#define SERIAL_S3C2410_NAME "ttySAC"
+#define SERIAL_S3C2410_MAJOR 204
+#define SERIAL_S3C2410_MINOR 4
+
+/* we can support 3 uarts, but not always use them */
+
+#define NR_PORTS (3)
+
+static const char serial_s3c2410_name[] = "Samsung S3C2410 UART";
+
+/* port irq numbers */
+
+#define TX_IRQ(port) ((port)->irq + 1)
+#define RX_IRQ(port) ((port)->irq)
+
+#define tx_enabled(port) ((port)->unused[0])
+#define rx_enabled(port) ((port)->unused[1])
+
+/* flag to ignore all characters comming in */
+#define RXSTAT_DUMMY_READ (0x10000000)
+
+/* access functions */
+
+#define portaddr(port, reg) ((void *)((port)->membase + (reg)))
+
+#define rd_regb(port, reg) (__raw_readb(portaddr(port, reg)))
+#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg)))
+
+#define wr_regb(port, reg, val) \
+ do { __raw_writeb(val, portaddr(port, reg)); } while(0)
+
+#define wr_regl(port, reg, val) \
+ do { __raw_writel(val, portaddr(port, reg)); } while(0)
+
+
+
+
+/* code */
+
+static void
+serial_s3c2410_stop_tx(struct uart_port *port, unsigned int tty_stop)
+{
+ if (tx_enabled(port)) {
+ disable_irq(TX_IRQ(port));
+ tx_enabled(port) = 0;
+ }
+}
+
+static void
+serial_s3c2410_start_tx(struct uart_port *port, unsigned int tty_start)
+{
+ if (!tx_enabled(port)) {
+ enable_irq(TX_IRQ(port));
+ tx_enabled(port) = 1;
+ }
+}
+
+static void serial_s3c2410_stop_rx(struct uart_port *port)
+{
+ if (rx_enabled(port)) {
+ dbg("serial_s3c2410_stop_rx: port=%p\n", port);
+ disable_irq(RX_IRQ(port));
+ rx_enabled(port) = 0;
+ }
+}
+
+static void serial_s3c2410_enable_ms(struct uart_port *port)
+{
+}
+
+/* ? - where has parity gone?? */
+#define S3C2410_UERSTAT_PARITY (0x1000)
+
+static irqreturn_t
+serial_s3c2410_rx_chars(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct uart_port *port = dev_id;
+ struct tty_struct *tty = port->info->tty;
+ unsigned int ufcon, ch, rxs, ufstat;
+ int max_count = 256;
+
+ while (max_count-- > 0) {
+ ufcon = rd_regl(port, S3C2410_UFCON);
+ ufstat = rd_regl(port, S3C2410_UFSTAT);
+
+ if (S3C2410_UFCON_RXC(ufstat) == 0)
+ break;
+
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ tty->flip.work.func((void *)tty);
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ printk(KERN_WARNING "TTY_DONT_FLIP set\n");
+ goto out;
+ }
+ }
+
+ ch = rd_regb(port, S3C2410_URXH);
+
+ *tty->flip.char_buf_ptr = ch;
+ *tty->flip.flag_buf_ptr = TTY_NORMAL;
+ port->icount.rx++;
+
+ rxs = rd_regb(port, S3C2410_UERSTAT) | RXSTAT_DUMMY_READ;
+
+ if (rxs & S3C2410_UERSTAT_ANY) {
+ if (rxs & S3C2410_UERSTAT_FRAME)
+ port->icount.frame++;
+ if (rxs & S3C2410_UERSTAT_OVERRUN)
+ port->icount.overrun++;
+
+ rxs &= port->read_status_mask;
+
+ if (rxs & S3C2410_UERSTAT_PARITY)
+ *tty->flip.flag_buf_ptr = TTY_PARITY;
+ else if (rxs & ( S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN))
+ *tty->flip.flag_buf_ptr = TTY_FRAME;
+ }
+
+ if ((rxs & port->ignore_status_mask) == 0) {
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ }
+
+ if ((rxs & S3C2410_UERSTAT_OVERRUN) &&
+ tty->flip.count < TTY_FLIPBUF_SIZE) {
+ /*
+ * Overrun is special, since it's reported
+ * immediately, and doesn't affect the current
+ * character.
+ */
+ *tty->flip.char_buf_ptr++ = 0;
+ *tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+ tty->flip.count++;
+ }
+ }
+ tty_flip_buffer_push(tty);
+
+ out:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t
+serial_s3c2410_tx_chars(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct uart_port *port = (struct uart_port *)dev_id;
+ struct circ_buf *xmit = &port->info->xmit;
+ int count = 256;
+
+ if (port->x_char) {
+ wr_regb(port, S3C2410_UTXH, port->x_char);
+ port->icount.tx++;
+ port->x_char = 0;
+ goto out;
+ }
+
+ /* if there isnt anything more to transmit, or the uart is now
+ * stopped, disable the uart and exit
+ */
+
+ if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
+ serial_s3c2410_stop_tx(port, 0);
+ goto out;
+ }
+
+ /* try and drain the buffer... */
+
+ while (!uart_circ_empty(xmit) && count-- > 0) {
+ if (rd_regl(port, S3C2410_UFSTAT) & S3C2410_UFSTAT_TXFULL)
+ break;
+
+ wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ port->icount.tx++;
+ }
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+
+ if (uart_circ_empty(xmit))
+ serial_s3c2410_stop_tx(port, 0);
+
+ out:
+ return IRQ_HANDLED;
+}
+
+static unsigned int
+serial_s3c2410_tx_empty(struct uart_port *port)
+{
+ unsigned int ufcon = rd_regl(port, S3C2410_UFCON);
+ return (S3C2410_UFCON_TXC(ufcon) != 0) ? 0 : TIOCSER_TEMT;
+}
+
+/* no modem control lines */
+static unsigned int
+serial_s3c2410_get_mctrl(struct uart_port *port)
+{
+ unsigned int umstat = rd_regb(port,S3C2410_UMSTAT);
+
+ if (umstat & S3C2410_UMSTAT_CTS)
+ return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
+ else
+ return TIOCM_CAR | TIOCM_DSR;
+}
+
+static void
+serial_s3c2410_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ /* todo - possibly remove AFC and do manual CTS */
+}
+
+static void serial_s3c2410_break_ctl(struct uart_port *port, int break_state)
+{
+ unsigned long flags;
+ unsigned int ucon;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ ucon = rd_regl(port, S3C2410_UCON);
+
+ if (break_state)
+ ucon |= S3C2410_UCON_SBREAK;
+ else
+ ucon &= ~S3C2410_UCON_SBREAK;
+
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static int serial_s3c2410_startup(struct uart_port *port)
+{
+ int ret;
+
+ tx_enabled(port) = 1;
+ rx_enabled(port) = 1;
+
+ dbg("serial_s3c2410_startup: port=%p (%p)\n",
+ port, port->mapbase);
+
+ ret = request_irq(RX_IRQ(port), serial_s3c2410_rx_chars, 0,
+ serial_s3c2410_name, port);
+
+ if (ret != 0)
+ return ret;
+
+ ret = request_irq(TX_IRQ(port), serial_s3c2410_tx_chars, 0,
+ serial_s3c2410_name, port);
+
+ if (ret) {
+ free_irq(RX_IRQ(port), port);
+ return ret;
+ }
+
+ /* the port reset code should have done the correct
+ * register setup for the port controls */
+
+ return ret;
+}
+
+static void serial_s3c2410_shutdown(struct uart_port *port)
+{
+ free_irq(TX_IRQ(port), port);
+ free_irq(RX_IRQ(port), port);
+}
+
+static void
+serial_s3c2410_set_termios(struct uart_port *port, struct termios *termios,
+ struct termios *old)
+{
+ unsigned long flags;
+ unsigned int baud, quot;
+ unsigned int ulcon;
+
+ /*
+ * We don't support modem control lines.
+ */
+ termios->c_cflag &= ~(HUPCL | CRTSCTS | CMSPAR);
+ termios->c_cflag |= CLOCAL;
+
+ /*
+ * We don't support BREAK character recognition.
+ */
+ termios->c_iflag &= ~(IGNBRK | BRKINT);
+
+ /*
+ * Ask the core to calculate the divisor for us.
+ */
+ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+ quot = uart_get_divisor(port, baud);
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ dbg("config: 5bits/char\n");
+ ulcon = S3C2410_LCON_CS5;
+ break;
+ case CS6:
+ dbg("config: 6bits/char\n");
+ ulcon = S3C2410_LCON_CS6;
+ break;
+ case CS7:
+ dbg("config: 7bits/char\n");
+ ulcon = S3C2410_LCON_CS7;
+ break;
+ case CS8:
+ default:
+ dbg("config: 8bits/char\n");
+ ulcon = S3C2410_LCON_CS8;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ ulcon |= S3C2410_LCON_STOPB;
+
+ if (termios->c_cflag & PARENB) {
+ if (!(termios->c_cflag & PARODD))
+ ulcon |= S3C2410_LCON_PODD;
+ else
+ ulcon |= S3C2410_LCON_PEVEN;
+ } else {
+ ulcon |= S3C2410_LCON_PNONE;
+ }
+
+ /*
+ if (port->fifosize)
+ enable_fifo()
+ */
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ dbg("setting ulcon to %08x\n", ulcon);
+ //dbg("<flushing output from serial>\n");
+
+ /* set the ulcon register */
+ wr_regl(port, S3C2410_ULCON, ulcon);
+
+ dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
+ rd_regl(port, S3C2410_ULCON),
+ rd_regl(port, S3C2410_UCON),
+ rd_regl(port, S3C2410_UFCON));
+
+ /*
+ * Update the per-port timeout.
+ */
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ /*
+ * Which character status flags are we interested in?
+ */
+ port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;
+
+ /*
+ * Which character status flags should we ignore?
+ */
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
+ if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
+
+ /*
+ * Ignore all characters if CREAD is not set.
+ */
+ if ((termios->c_cflag & CREAD) == 0)
+ port->ignore_status_mask |= RXSTAT_DUMMY_READ;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static const char *serial_s3c2410_type(struct uart_port *port)
+{
+ return port->type == PORT_S3C2410 ? "S3C2410" : NULL;
+}
+
+#define MAP_SIZE (0x100)
+
+static void
+serial_s3c2410_release_port(struct uart_port *port)
+{
+ release_mem_region(port->mapbase, MAP_SIZE);
+}
+
+static int
+serial_s3c2410_request_port(struct uart_port *port)
+{
+ return request_mem_region(port->mapbase, MAP_SIZE, serial_s3c2410_name)
+ != NULL ? 0 : -EBUSY;
+}
+
+static void
+serial_s3c2410_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE &&
+ serial_s3c2410_request_port(port) == 0)
+ port->type = PORT_S3C2410;
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int
+serial_s3c2410_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+ int ret = 0;
+
+ if (ser->type != PORT_UNKNOWN && ser->type != PORT_S3C2410)
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static struct uart_ops serial_s3c2410_ops = {
+ .tx_empty = serial_s3c2410_tx_empty,
+ .get_mctrl = serial_s3c2410_get_mctrl,
+ .set_mctrl = serial_s3c2410_set_mctrl,
+ .stop_tx = serial_s3c2410_stop_tx,
+ .start_tx = serial_s3c2410_start_tx,
+ .stop_rx = serial_s3c2410_stop_rx,
+ .enable_ms = serial_s3c2410_enable_ms,
+ .break_ctl = serial_s3c2410_break_ctl,
+ .startup = serial_s3c2410_startup,
+ .shutdown = serial_s3c2410_shutdown,
+ .set_termios = serial_s3c2410_set_termios,
+ .type = serial_s3c2410_type,
+ .release_port = serial_s3c2410_release_port,
+ .request_port = serial_s3c2410_request_port,
+ .config_port = serial_s3c2410_config_port,
+ .verify_port = serial_s3c2410_verify_port,
+};
+
+static struct uart_port serial_s3c2410_ports[NR_PORTS] = {
+ {
+ .membase = 0,
+ .mapbase = 0,
+ .iotype = UPIO_MEM,
+ .irq = IRQ_S3CUART_RX0,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &serial_s3c2410_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 0,
+ },
+ {
+ .membase = 0,
+ .mapbase = 0,
+ .iotype = UPIO_MEM,
+ .irq = IRQ_S3CUART_RX1,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &serial_s3c2410_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 1,
+ }
+#if NR_PORTS > 2
+ ,
+ {
+ .membase = 0,
+ .mapbase = 0,
+ .iotype = UPIO_MEM,
+ .irq = IRQ_S3CUART_RX2,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &serial_s3c2410_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 2,
+ }
+#endif
+};
+
+static int
+serial_s3c2410_resetport(struct uart_port *port,
+ struct s3c2410_uartcfg *cfg)
+{
+ /* ensure registers are setup */
+
+ dbg("serial_s3c2410_resetport: port=%p (%08x), cfg=%p\n",
+ port, port->mapbase, cfg);
+
+ wr_regl(port, S3C2410_UCON, cfg->ucon);
+ wr_regl(port, S3C2410_ULCON, cfg->ulcon);
+
+ /* reset both fifos */
+
+ wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
+ wr_regl(port, S3C2410_UFCON, cfg->ufcon);
+
+ return 0;
+}
+
+/* serial_s3c2410_init_ports
+ *
+ * initialise the serial ports from the machine provided initialisation
+ * data.
+*/
+
+static int serial_s3c2410_init_ports(void)
+{
+ struct uart_port *ptr = serial_s3c2410_ports;
+ struct s3c2410_uartcfg *cfg = s3c2410_uartcfgs;
+ static int inited = 0;
+ int i;
+
+ if (inited)
+ return 0;
+ inited = 1;
+
+ dbg("serial_s3c2410_init_ports: initialising ports...\n");
+
+ for (i = 0; i < NR_PORTS; i++, ptr++, cfg++) {
+
+ if (cfg->hwport > 3)
+ continue;
+
+ dbg("serial_s3c2410_init_ports: port %d (hw %d)...\n",
+ i, cfg->hwport);
+
+ if (cfg->clock != NULL)
+ ptr->uartclk = *cfg->clock;
+
+ switch (cfg->hwport) {
+ case 0:
+ ptr->mapbase = S3C2410_PA_UART0;
+ ptr->membase = (char *)S3C2410_VA_UART0;
+ ptr->irq = IRQ_S3CUART_RX0;
+ break;
+
+ case 1:
+ ptr->mapbase = S3C2410_PA_UART1;
+ ptr->membase = (char *)S3C2410_VA_UART1;
+ ptr->irq = IRQ_S3CUART_RX1;
+ break;
+
+ case 2:
+ ptr->mapbase = S3C2410_PA_UART2;
+ ptr->membase = (char *)S3C2410_VA_UART2;
+ ptr->irq = IRQ_S3CUART_RX2;
+ break;
+ }
+
+ if (ptr->mapbase == 0)
+ continue;
+
+ /* reset the fifos (and setup the uart */
+ serial_s3c2410_resetport(ptr, cfg);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_SERIAL_S3C2410_CONSOLE
+
+static struct uart_port *cons_uart;
+
+static int
+serial_s3c2410_console_txrdy(struct uart_port *port, unsigned int ufcon)
+{
+ unsigned long ufstat, utrstat;
+
+ if (ufcon & S3C2410_UFCON_FIFOMODE) {
+ /* fifo mode - check ammount of data in fifo registers... */
+
+ ufstat = rd_regl(port, S3C2410_UFSTAT);
+
+ return S3C2410_UFCON_TXC(ufstat) < 12;
+ }
+
+ /* in non-fifo mode, we go and use the tx buffer empty */
+
+ utrstat = rd_regl(port, S3C2410_UTRSTAT);
+
+ return (utrstat & S3C2410_UTRSTAT_TXFE) ? 1 : 0;
+}
+
+static void
+serial_s3c2410_console_write(struct console *co, const char *s,
+ unsigned int count)
+{
+ int i;
+ unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON);
+
+ for (i = 0; i < count; i++) {
+ while (!serial_s3c2410_console_txrdy(cons_uart, ufcon))
+ barrier();
+
+ wr_regb(cons_uart, S3C2410_UTXH, s[i]);
+
+ if (s[i] == '\n') {
+ while (!serial_s3c2410_console_txrdy(cons_uart, ufcon))
+ barrier();
+
+ wr_regb(cons_uart, S3C2410_UTXH, '\r');
+ }
+ }
+}
+
+static void __init
+serial_s3c2410_get_options(struct uart_port *port, int *baud,
+ int *parity, int *bits)
+{
+
+ unsigned int ulcon, ucon, ubrdiv;
+
+ ulcon = rd_regl(port, S3C2410_ULCON);
+ ucon = rd_regl(port, S3C2410_UCON);
+ ubrdiv = rd_regl(port, S3C2410_UBRDIV);
+
+ dbg("serial_s3c2410_get_options: port=%p\n"
+ "registers: ulcon=%08x, ucon=%08x, ubdriv=%08x\n",
+ port, ulcon, ucon, ubrdiv);
+
+ if ((ucon & 0xf) != 0) {
+ /* consider the serial port configured if the tx/rx mode set */
+
+ switch (ulcon & S3C2410_LCON_CSMASK) {
+ case S3C2410_LCON_CS5:
+ *bits = 5;
+ break;
+ case S3C2410_LCON_CS6:
+ *bits = 6;
+ break;
+ case S3C2410_LCON_CS7:
+ *bits = 7;
+ break;
+ default:
+ case S3C2410_LCON_CS8:
+ *bits = 8;
+ break;
+ }
+
+ switch (ulcon & S3C2410_LCON_PMASK) {
+ case S3C2410_LCON_PEVEN:
+ *parity = 'e';
+ break;
+
+ case S3C2410_LCON_PODD:
+ *parity = 'o';
+ break;
+
+ default:
+ case S3C2410_LCON_PNONE:
+ /* nothing */
+ }
+
+ /* now calculate the baud rate */
+
+ *baud = port->uartclk / ( 16 * (ubrdiv + 1));
+ dbg("calculated baud %d\n", *baud);
+ }
+
+}
+
+static int __init
+serial_s3c2410_console_setup(struct console *co, char *options)
+{
+ struct uart_port *port;
+ int baud = 9600;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ /* is this a valid port */
+
+ if (co->index == -1 || co->index >= NR_PORTS)
+ co->index = 0;
+
+ port = &serial_s3c2410_ports[co->index];
+
+ /* is the port configured? */
+
+ if (port->mapbase == 0x0) {
+ co->index = 0;
+ port = &serial_s3c2410_ports[co->index];
+ }
+
+ cons_uart = port;
+
+ dbg("serial_s3c2410_console_setup: port=%p (%d)\n", port, co->index);
+
+ /*
+ * Check whether an invalid uart number has been specified, and
+ * if so, search for the first available port that does have
+ * console support.
+ */
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+ else
+ serial_s3c2410_get_options(port, &baud, &parity, &bits);
+
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver s3c2410_uart_drv;
+
+static struct console serial_s3c2410_console =
+{
+ .name = SERIAL_S3C2410_NAME,
+ .write = serial_s3c2410_console_write,
+ .device = uart_console_device,
+ .setup = serial_s3c2410_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &s3c2410_uart_drv,
+};
+
+static int __init s3c2410_console_init(void)
+{
+ dbg("s3c2410_console_init:\n");
+
+ serial_s3c2410_init_ports();
+ register_console(&serial_s3c2410_console);
+ return 0;
+}
+console_initcall(s3c2410_console_init);
+
+#define SERIAL_S3C2410_CONSOLE &serial_s3c2410_console
+#else
+#define SERIAL_S3C2410_CONSOLE NULL
+#endif
+
+static struct uart_driver s3c2410_uart_drv = {
+ .owner = THIS_MODULE,
+ .driver_name = SERIAL_S3C2410_NAME,
+ .dev_name = SERIAL_S3C2410_NAME,
+ .major = SERIAL_S3C2410_MAJOR,
+ .minor = SERIAL_S3C2410_MINOR,
+ .nr = 3,
+ .cons = SERIAL_S3C2410_CONSOLE,
+};
+
+/* device driver */
+
+static int s3c2410_serial_probe(struct device *_dev);
+static int s3c2410_serial_remove(struct device *_dev);
+
+static struct device_driver s3c2410_serial_drv = {
+ .name = "s3c2410-uart",
+ .bus = &platform_bus_type,
+ .probe = s3c2410_serial_probe,
+ .remove = s3c2410_serial_remove,
+ .suspend = NULL,
+ .resume = NULL,
+};
+
+#define s3c2410_dev_to_port(__dev) (struct uart_port *)dev_get_drvdata(__dev)
+
+static int s3c2410_serial_probe(struct device *_dev)
+{
+ struct platform_device *dev = to_platform_device(_dev);
+ struct resource *res = dev->resource;
+ int i;
+
+ dbg("s3c2410_serial_probe: dev=%p, _dev=%p, res=%p\n", _dev, dev, res);
+
+ for (i = 0; i < dev->num_resources; i++, res++)
+ if (res->flags & IORESOURCE_MEM)
+ break;
+
+ if (i < dev->num_resources) {
+ struct uart_port *ptr = serial_s3c2410_ports;
+
+ for (i = 0; i < NR_PORTS; i++, ptr++) {
+ dbg("s3c2410_serial_probe: ptr=%p (%08x, %08x)\n",
+ ptr, ptr->mapbase, ptr->membase);
+
+ if (ptr->mapbase != res->start)
+ continue;
+
+ dbg("s3c2410_serial_probe: got device %p: port=%p\n",
+ _dev, ptr);
+
+ uart_add_one_port(&s3c2410_uart_drv, ptr);
+ dev_set_drvdata(_dev, ptr);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int s3c2410_serial_remove(struct device *_dev)
+{
+ struct uart_port *port = s3c2410_dev_to_port(_dev);
+
+ if (port)
+ uart_remove_one_port(&s3c2410_uart_drv, port);
+
+ return 0;
+}
+
+
+
+static int __init serial_s3c2410_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "S3C2410X Serial, (c) 2003 Simtec Electronics\n");
+
+ ret = uart_register_driver(&s3c2410_uart_drv);
+ if (ret != 0)
+ return ret;
+
+ ret = driver_register(&s3c2410_serial_drv);
+ if (ret) {
+ uart_unregister_driver(&s3c2410_uart_drv);
+ }
+
+ return ret;
+}
+
+static void __exit serial_s3c2410_exit(void)
+{
+ driver_unregister(&s3c2410_serial_drv);
+ uart_unregister_driver(&s3c2410_uart_drv);
+}
+
+module_init(serial_s3c2410_init);
+module_exit(serial_s3c2410_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("Samsung S3C2410X (S3C2410) Serial driver");
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index a78ee1c8e09d..409844e06a6d 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -83,6 +83,9 @@
#define PORT_SCIF 53
#define PORT_IRDA 54
+/* Samsung S3C2410 SoC and derivatives thereof */
+#define PORT_S3C2410 55
+
#ifdef __KERNEL__
#include <linux/config.h>