V4L/DVB: s2255drv: return if vdev not found
[safe/jmp/linux-2.6] / drivers / serial / samsung.c
index 5a88b3f..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
 #include <linux/serial.h>
 #include <linux/delay.h>
 #include <linux/clk.h>
+#include <linux/cpufreq.h>
 
 #include <asm/irq.h>
 
 #include <mach/hardware.h>
+#include <mach/map.h>
 
-#include <asm/plat-s3c/regs-serial.h>
-#include <mach/regs-gpio.h>
+#include <plat/regs-serial.h>
 
 #include "samsung.h"
 
 #define S3C24XX_SERIAL_MAJOR   204
 #define S3C24XX_SERIAL_MINOR   64
 
-/* we can support 3 uarts, but not always use them */
-
-#ifdef CONFIG_CPU_S3C2400
-#define NR_PORTS (2)
-#else
-#define NR_PORTS (3)
-#endif
-
-/* port irq numbers */
-
-#define TX_IRQ(port) ((port)->irq + 1)
-#define RX_IRQ(port) ((port)->irq)
-
 /* macros to change one thing to another */
 
 #define tx_enabled(port) ((port)->unused[0])
@@ -136,8 +124,10 @@ static void s3c24xx_serial_rx_disable(struct uart_port *port)
 
 static void s3c24xx_serial_stop_tx(struct uart_port *port)
 {
+       struct s3c24xx_uart_port *ourport = to_ourport(port);
+
        if (tx_enabled(port)) {
-               disable_irq(TX_IRQ(port));
+               disable_irq_nosync(ourport->tx_irq);
                tx_enabled(port) = 0;
                if (port->flags & UPF_CONS_FLOW)
                        s3c24xx_serial_rx_enable(port);
@@ -146,11 +136,13 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port)
 
 static void s3c24xx_serial_start_tx(struct uart_port *port)
 {
+       struct s3c24xx_uart_port *ourport = to_ourport(port);
+
        if (!tx_enabled(port)) {
                if (port->flags & UPF_CONS_FLOW)
                        s3c24xx_serial_rx_disable(port);
 
-               enable_irq(TX_IRQ(port));
+               enable_irq(ourport->tx_irq);
                tx_enabled(port) = 1;
        }
 }
@@ -158,9 +150,11 @@ static void s3c24xx_serial_start_tx(struct uart_port *port)
 
 static void s3c24xx_serial_stop_rx(struct uart_port *port)
 {
+       struct s3c24xx_uart_port *ourport = to_ourport(port);
+
        if (rx_enabled(port)) {
                dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
-               disable_irq(RX_IRQ(port));
+               disable_irq_nosync(ourport->rx_irq);
                rx_enabled(port) = 0;
        }
 }
@@ -202,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;
 
@@ -287,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) {
@@ -384,13 +378,13 @@ static void s3c24xx_serial_shutdown(struct uart_port *port)
        struct s3c24xx_uart_port *ourport = to_ourport(port);
 
        if (ourport->tx_claimed) {
-               free_irq(TX_IRQ(port), ourport);
+               free_irq(ourport->tx_irq, ourport);
                tx_enabled(port) = 0;
                ourport->tx_claimed = 0;
        }
 
        if (ourport->rx_claimed) {
-               free_irq(RX_IRQ(port), ourport);
+               free_irq(ourport->rx_irq, ourport);
                ourport->rx_claimed = 0;
                rx_enabled(port) = 0;
        }
@@ -407,12 +401,11 @@ static int s3c24xx_serial_startup(struct uart_port *port)
 
        rx_enabled(port) = 1;
 
-       ret = request_irq(RX_IRQ(port),
-                         s3c24xx_serial_rx_chars, 0,
+       ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
                          s3c24xx_serial_portname(port), ourport);
 
        if (ret != 0) {
-               printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port));
+               printk(KERN_ERR "cannot get irq %d\n", ourport->rx_irq);
                return ret;
        }
 
@@ -422,12 +415,11 @@ static int s3c24xx_serial_startup(struct uart_port *port)
 
        tx_enabled(port) = 1;
 
-       ret = request_irq(TX_IRQ(port),
-                         s3c24xx_serial_tx_chars, 0,
+       ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
                          s3c24xx_serial_portname(port), ourport);
 
        if (ret) {
-               printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port));
+               printk(KERN_ERR "cannot get irq %d\n", ourport->tx_irq);
                goto err;
        }
 
@@ -452,6 +444,8 @@ static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
 {
        struct s3c24xx_uart_port *ourport = to_ourport(port);
 
+       ourport->pm_level = level;
+
        switch (level) {
        case 3:
                if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL)
@@ -514,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;
 };
@@ -523,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);
@@ -533,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;
@@ -617,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)
@@ -629,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.
@@ -650,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)) {
@@ -661,6 +699,14 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
 
                ourport->clksrc = clksrc;
                ourport->baudclk = clk;
+               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) {
@@ -702,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),
@@ -752,6 +802,8 @@ static const char *s3c24xx_serial_type(struct uart_port *port)
                return "S3C2440";
        case PORT_S3C2412:
                return "S3C2412";
+       case PORT_S3C6400:
+               return "S3C6400/10";
        default:
                return NULL;
        }
@@ -827,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),
@@ -859,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 = {
@@ -872,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
 };
@@ -890,6 +956,89 @@ static inline int s3c24xx_serial_resetport(struct uart_port *port,
        return (info->reset_port)(port, cfg);
 }
 
+
+#ifdef CONFIG_CPU_FREQ
+
+static int s3c24xx_serial_cpufreq_transition(struct notifier_block *nb,
+                                            unsigned long val, void *data)
+{
+       struct s3c24xx_uart_port *port;
+       struct uart_port *uport;
+
+       port = container_of(nb, struct s3c24xx_uart_port, freq_transition);
+       uport = &port->port;
+
+       /* check to see if port is enabled */
+
+       if (port->pm_level != 0)
+               return 0;
+
+       /* try and work out if the baudrate is changing, we can detect
+        * a change in rate, but we do not have support for detecting
+        * a disturbance in the clock-rate over the change.
+        */
+
+       if (IS_ERR(port->clk))
+               goto exit;
+
+       if (port->baudclk_rate == clk_get_rate(port->clk))
+               goto exit;
+
+       if (val == CPUFREQ_PRECHANGE) {
+               /* we should really shut the port down whilst the
+                * frequency change is in progress. */
+
+       } else if (val == CPUFREQ_POSTCHANGE) {
+               struct ktermios *termios;
+               struct tty_struct *tty;
+
+               if (uport->state == NULL)
+                       goto exit;
+
+               tty = uport->state->port.tty;
+
+               if (tty == NULL)
+                       goto exit;
+
+               termios = tty->termios;
+
+               if (termios == NULL) {
+                       printk(KERN_WARNING "%s: no termios?\n", __func__);
+                       goto exit;
+               }
+
+               s3c24xx_serial_set_termios(uport, termios, NULL);
+       }
+
+ exit:
+       return 0;
+}
+
+static inline int s3c24xx_serial_cpufreq_register(struct s3c24xx_uart_port *port)
+{
+       port->freq_transition.notifier_call = s3c24xx_serial_cpufreq_transition;
+
+       return cpufreq_register_notifier(&port->freq_transition,
+                                        CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void s3c24xx_serial_cpufreq_deregister(struct s3c24xx_uart_port *port)
+{
+       cpufreq_unregister_notifier(&port->freq_transition,
+                                   CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+#else
+static inline int s3c24xx_serial_cpufreq_register(struct s3c24xx_uart_port *port)
+{
+       return 0;
+}
+
+static inline void s3c24xx_serial_cpufreq_deregister(struct s3c24xx_uart_port *port)
+{
+}
+#endif
+
 /* s3c24xx_serial_init_port
  *
  * initialise a single serial port from the platform device given
@@ -914,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;
@@ -943,18 +1095,26 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
 
        dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
 
-       port->mapbase   = res->start;
-       port->membase   = S3C24XX_VA_UART + (res->start - S3C24XX_PA_UART);
+       port->mapbase = res->start;
+       port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000);
        ret = platform_get_irq(platdev, 0);
        if (ret < 0)
                port->irq = 0;
-       else
+       else {
                port->irq = ret;
+               ourport->rx_irq = ret;
+               ourport->tx_irq = ret + 1;
+       }
+       
+       ret = platform_get_irq(platdev, 1);
+       if (ret > 0)
+               ourport->tx_irq = ret;
 
        ourport->clk    = clk_get(&platdev->dev, "uart");
 
-       dbg("port: map=%08x, mem=%08x, irq=%d, clock=%ld\n",
-           port->mapbase, port->membase, port->irq, port->uartclk);
+       dbg("port: map=%08x, mem=%08x, irq=%d (%d,%d), clock=%ld\n",
+           port->mapbase, port->membase, port->irq,
+           ourport->rx_irq, ourport->tx_irq, port->uartclk);
 
        /* reset the fifos (and setup the uart) */
        s3c24xx_serial_resetport(port, cfg);
@@ -1002,6 +1162,10 @@ int s3c24xx_serial_probe(struct platform_device *dev,
        if (ret < 0)
                printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__);
 
+       ret = s3c24xx_serial_cpufreq_register(ourport);
+       if (ret < 0)
+               dev_err(&dev->dev, "failed to add cpufreq notifier\n");
+
        return 0;
 
  probe_err:
@@ -1010,11 +1174,12 @@ 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);
 
        if (port) {
+               s3c24xx_serial_cpufreq_deregister(to_ourport(port));
                device_remove_file(&dev->dev, &dev_attr_clock_source);
                uart_remove_one_port(&s3c24xx_uart_drv, port);
        }
@@ -1106,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;
@@ -1209,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;
@@ -1219,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;
@@ -1240,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;
@@ -1286,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];