V4L/DVB: s2255drv: return if vdev not found
[safe/jmp/linux-2.6] / drivers / serial / samsung.c
index ba2e868..a9d6c56 100644 (file)
@@ -2,7 +2,7 @@
  *
  * Driver core for Samsung SoC onboard UARTs.
  *
- * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics
+ * Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics
  *     http://armlinux.simtec.co.uk/
  *
  * This program is free software; you can redistribute it and/or modify
 #define S3C24XX_SERIAL_MAJOR   204
 #define S3C24XX_SERIAL_MINOR   64
 
-/* we can support 3 uarts, but not always use them */
-
-#if defined(CONFIG_CPU_S3C2400) || defined(CONFIG_CPU_S3C24A0)
-#define NR_PORTS (2)
-#else
-#define NR_PORTS (3)
-#endif
-
 /* macros to change one thing to another */
 
 #define tx_enabled(port) ((port)->unused[0])
@@ -135,7 +127,7 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port)
        struct s3c24xx_uart_port *ourport = to_ourport(port);
 
        if (tx_enabled(port)) {
-               disable_irq(ourport->tx_irq);
+               disable_irq_nosync(ourport->tx_irq);
                tx_enabled(port) = 0;
                if (port->flags & UPF_CONS_FLOW)
                        s3c24xx_serial_rx_enable(port);
@@ -162,7 +154,7 @@ static void s3c24xx_serial_stop_rx(struct uart_port *port)
 
        if (rx_enabled(port)) {
                dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
-               disable_irq(ourport->rx_irq);
+               disable_irq_nosync(ourport->rx_irq);
                rx_enabled(port) = 0;
        }
 }
@@ -204,7 +196,7 @@ s3c24xx_serial_rx_chars(int irq, void *dev_id)
 {
        struct s3c24xx_uart_port *ourport = dev_id;
        struct uart_port *port = &ourport->port;
-       struct tty_struct *tty = port->info->port.tty;
+       struct tty_struct *tty = port->state->port.tty;
        unsigned int ufcon, ch, flag, ufstat, uerstat;
        int max_count = 64;
 
@@ -289,7 +281,7 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
 {
        struct s3c24xx_uart_port *ourport = id;
        struct uart_port *port = &ourport->port;
-       struct circ_buf *xmit = &port->info->xmit;
+       struct circ_buf *xmit = &port->state->xmit;
        int count = 256;
 
        if (port->x_char) {
@@ -516,6 +508,7 @@ s3c24xx_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c)
 struct baud_calc {
        struct s3c24xx_uart_clksrc      *clksrc;
        unsigned int                     calc;
+       unsigned int                     divslot;
        unsigned int                     quot;
        struct clk                      *src;
 };
@@ -525,6 +518,7 @@ static int s3c24xx_serial_calcbaud(struct baud_calc *calc,
                                   struct s3c24xx_uart_clksrc *clksrc,
                                   unsigned int baud)
 {
+       struct s3c24xx_uart_port *ourport = to_ourport(port);
        unsigned long rate;
 
        calc->src = clk_get(port->dev, clksrc->name);
@@ -535,8 +529,24 @@ static int s3c24xx_serial_calcbaud(struct baud_calc *calc,
        rate /= clksrc->divisor;
 
        calc->clksrc = clksrc;
-       calc->quot = (rate + (8 * baud)) / (16 * baud);
-       calc->calc = (rate / (calc->quot * 16));
+
+       if (ourport->info->has_divslot) {
+               unsigned long div = rate / baud;
+
+               /* The UDIVSLOT register on the newer UARTs allows us to
+                * get a divisor adjustment of 1/16th on the baud clock.
+                *
+                * We don't keep the UDIVSLOT value (the 16ths we calculated
+                * by not multiplying the baud by 16) as it is easy enough
+                * to recalculate.
+                */
+
+               calc->quot = div / 16;
+               calc->calc = rate / div;
+       } else {
+               calc->quot = (rate + (8 * baud)) / (16 * baud);
+               calc->calc = (rate / (calc->quot * 16));
+       }
 
        calc->quot--;
        return 1;
@@ -619,6 +629,30 @@ static unsigned int s3c24xx_serial_getclk(struct uart_port *port,
        return best->quot;
 }
 
+/* udivslot_table[]
+ *
+ * This table takes the fractional value of the baud divisor and gives
+ * the recommended setting for the UDIVSLOT register.
+ */
+static u16 udivslot_table[16] = {
+       [0] = 0x0000,
+       [1] = 0x0080,
+       [2] = 0x0808,
+       [3] = 0x0888,
+       [4] = 0x2222,
+       [5] = 0x4924,
+       [6] = 0x4A52,
+       [7] = 0x54AA,
+       [8] = 0x5555,
+       [9] = 0xD555,
+       [10] = 0xD5D5,
+       [11] = 0xDDD5,
+       [12] = 0xDDDD,
+       [13] = 0xDFDD,
+       [14] = 0xDFDF,
+       [15] = 0xFFDF,
+};
+
 static void s3c24xx_serial_set_termios(struct uart_port *port,
                                       struct ktermios *termios,
                                       struct ktermios *old)
@@ -631,6 +665,7 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
        unsigned int baud, quot;
        unsigned int ulcon;
        unsigned int umcon;
+       unsigned int udivslot = 0;
 
        /*
         * We don't support modem control lines.
@@ -652,6 +687,7 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
        /* check to see if we need  to change clock source */
 
        if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
+               dbg("selecting clock %p\n", clk);
                s3c24xx_serial_setsource(port, clksrc);
 
                if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) {
@@ -666,6 +702,13 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
                ourport->baudclk_rate = clk ? clk_get_rate(clk) : 0;
        }
 
+       if (ourport->info->has_divslot) {
+               unsigned int div = ourport->baudclk_rate / baud;
+
+               udivslot = udivslot_table[div & 15];
+               dbg("udivslot = %04x (div %d)\n", udivslot, div & 15);
+       }
+
        switch (termios->c_cflag & CSIZE) {
        case CS5:
                dbg("config: 5bits/char\n");
@@ -705,12 +748,16 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
 
        spin_lock_irqsave(&port->lock, flags);
 
-       dbg("setting ulcon to %08x, brddiv to %d\n", ulcon, quot);
+       dbg("setting ulcon to %08x, brddiv to %d, udivslot %08x\n",
+           ulcon, quot, udivslot);
 
        wr_regl(port, S3C2410_ULCON, ulcon);
        wr_regl(port, S3C2410_UBRDIV, quot);
        wr_regl(port, S3C2410_UMCON, umcon);
 
+       if (ourport->info->has_divslot)
+               wr_regl(port, S3C2443_DIVSLOT, udivslot);
+
        dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
            rd_regl(port, S3C2410_ULCON),
            rd_regl(port, S3C2410_UCON),
@@ -832,14 +879,14 @@ static struct uart_ops s3c24xx_serial_ops = {
 static struct uart_driver s3c24xx_uart_drv = {
        .owner          = THIS_MODULE,
        .dev_name       = "s3c2410_serial",
-       .nr             = 3,
+       .nr             = CONFIG_SERIAL_SAMSUNG_UARTS,
        .cons           = S3C24XX_SERIAL_CONSOLE,
        .driver_name    = S3C24XX_SERIAL_NAME,
        .major          = S3C24XX_SERIAL_MAJOR,
        .minor          = S3C24XX_SERIAL_MINOR,
 };
 
-static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
+static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
        [0] = {
                .port = {
                        .lock           = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
@@ -864,7 +911,7 @@ static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
                        .line           = 1,
                }
        },
-#if NR_PORTS > 2
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
 
        [2] = {
                .port = {
@@ -877,6 +924,20 @@ static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
                        .flags          = UPF_BOOT_AUTOCONF,
                        .line           = 2,
                }
+       },
+#endif
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
+       [3] = {
+               .port = {
+                       .lock           = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
+                       .iotype         = UPIO_MEM,
+                       .irq            = IRQ_S3CUART_RX3,
+                       .uartclk        = 0,
+                       .fifosize       = 16,
+                       .ops            = &s3c24xx_serial_ops,
+                       .flags          = UPF_BOOT_AUTOCONF,
+                       .line           = 3,
+               }
        }
 #endif
 };
@@ -931,17 +992,13 @@ static int s3c24xx_serial_cpufreq_transition(struct notifier_block *nb,
                struct ktermios *termios;
                struct tty_struct *tty;
 
-               if (uport->info == NULL) {
-                       printk(KERN_WARNING "%s: info NULL\n", __func__);
+               if (uport->state == NULL)
                        goto exit;
-               }
 
-               tty = uport->info->port.tty;
+               tty = uport->state->port.tty;
 
-               if (tty == NULL) {
-                       printk(KERN_WARNING "%s: tty is NULL\n", __func__);
+               if (tty == NULL)
                        goto exit;
-               }
 
                termios = tty->termios;
 
@@ -1006,8 +1063,11 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
        if (port->mapbase != 0)
                return 0;
 
-       if (cfg->hwport > 3)
-               return -EINVAL;
+       if (cfg->hwport > CONFIG_SERIAL_SAMSUNG_UARTS) {
+               printk(KERN_ERR "%s: port %d bigger than %d\n", __func__,
+                      cfg->hwport, CONFIG_SERIAL_SAMSUNG_UARTS);
+               return -ERANGE;
+       }
 
        /* setup info for port */
        port->dev       = &platdev->dev;
@@ -1114,7 +1174,7 @@ int s3c24xx_serial_probe(struct platform_device *dev,
 
 EXPORT_SYMBOL_GPL(s3c24xx_serial_probe);
 
-int s3c24xx_serial_remove(struct platform_device *dev)
+int __devexit s3c24xx_serial_remove(struct platform_device *dev)
 {
        struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
 
@@ -1211,7 +1271,7 @@ s3c24xx_serial_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... */
+               /* fifo mode - check amount of data in fifo registers... */
 
                ufstat = rd_regl(port, S3C2410_UFSTAT);
                return (ufstat & info->tx_fifofull) ? 0 : 1;
@@ -1314,7 +1374,7 @@ s3c24xx_serial_get_options(struct uart_port *port, int *baud,
  * data.
 */
 
-static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info *info)
+static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info **info)
 {
        struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports;
        struct platform_device **platdev_ptr;
@@ -1324,8 +1384,8 @@ static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info *info)
 
        platdev_ptr = s3c24xx_uart_devs;
 
-       for (i = 0; i < NR_PORTS; i++, ptr++, platdev_ptr++) {
-               s3c24xx_serial_init_port(ptr, info, *platdev_ptr);
+       for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++, platdev_ptr++) {
+               s3c24xx_serial_init_port(ptr, info[i], *platdev_ptr);
        }
 
        return 0;
@@ -1345,7 +1405,7 @@ s3c24xx_serial_console_setup(struct console *co, char *options)
 
        /* is this a valid port */
 
-       if (co->index == -1 || co->index >= NR_PORTS)
+       if (co->index == -1 || co->index >= CONFIG_SERIAL_SAMSUNG_UARTS)
                co->index = 0;
 
        port = &s3c24xx_serial_ports[co->index].port;
@@ -1391,7 +1451,7 @@ static struct console s3c24xx_serial_console = {
 };
 
 int s3c24xx_serial_initconsole(struct platform_driver *drv,
-                              struct s3c24xx_uart_info *info)
+                              struct s3c24xx_uart_info **info)
 
 {
        struct platform_device *dev = s3c24xx_uart_devs[0];