ARM: 6030/1: KS8695: enable console
[safe/jmp/linux-2.6] / drivers / serial / sunhv.c
index c3a6bd2..d14cca7 100644 (file)
@@ -1,6 +1,6 @@
 /* sunhv.c: Serial driver for SUN4V hypervisor console.
  *
- * Copyright (C) 2006 David S. Miller (davem@davemloft.net)
+ * Copyright (C) 2006, 2007 David S. Miller (davem@davemloft.net)
  */
 
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/delay.h>
 #include <linux/init.h>
+#include <linux/of_device.h>
 
 #include <asm/hypervisor.h>
 #include <asm/spitfire.h>
 #include <asm/prom.h>
-#include <asm/of_device.h>
 #include <asm/irq.h>
 
 #if defined(CONFIG_MAGIC_SYSRQ)
 #define CON_BREAK      ((long)-1)
 #define CON_HUP                ((long)-2)
 
-static inline long hypervisor_con_getchar(long *status)
-{
-       register unsigned long func asm("%o5");
-       register unsigned long arg0 asm("%o0");
-       register unsigned long arg1 asm("%o1");
-
-       func = HV_FAST_CONS_GETCHAR;
-       arg0 = 0;
-       arg1 = 0;
-       __asm__ __volatile__("ta        %6"
-                            : "=&r" (func), "=&r" (arg0), "=&r" (arg1)
-                            : "0" (func), "1" (arg0), "2" (arg1),
-                              "i" (HV_FAST_TRAP));
+#define IGNORE_BREAK   0x1
+#define IGNORE_ALL     0x2
 
-       *status = arg0;
+static char *con_write_page;
+static char *con_read_page;
 
-       return (long) arg1;
-}
+static int hung_up = 0;
 
-static inline long hypervisor_con_putchar(long ch)
+static void transmit_chars_putchar(struct uart_port *port, struct circ_buf *xmit)
 {
-       register unsigned long func asm("%o5");
-       register unsigned long arg0 asm("%o0");
+       while (!uart_circ_empty(xmit)) {
+               long status = sun4v_con_putchar(xmit->buf[xmit->tail]);
 
-       func = HV_FAST_CONS_PUTCHAR;
-       arg0 = ch;
-       __asm__ __volatile__("ta        %4"
-                            : "=&r" (func), "=&r" (arg0)
-                            : "0" (func), "1" (arg0), "i" (HV_FAST_TRAP));
+               if (status != HV_EOK)
+                       break;
 
-       return (long) arg0;
+               xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+               port->icount.tx++;
+       }
 }
 
-#define IGNORE_BREAK   0x1
-#define IGNORE_ALL     0x2
+static void transmit_chars_write(struct uart_port *port, struct circ_buf *xmit)
+{
+       while (!uart_circ_empty(xmit)) {
+               unsigned long ra = __pa(xmit->buf + xmit->tail);
+               unsigned long len, status, sent;
 
-static int hung_up = 0;
+               len = CIRC_CNT_TO_END(xmit->head, xmit->tail,
+                                     UART_XMIT_SIZE);
+               status = sun4v_con_write(ra, len, &sent);
+               if (status != HV_EOK)
+                       break;
+               xmit->tail = (xmit->tail + sent) & (UART_XMIT_SIZE - 1);
+               port->icount.tx += sent;
+       }
+}
 
-static struct tty_struct *receive_chars(struct uart_port *port)
+static int receive_chars_getchar(struct uart_port *port, struct tty_struct *tty)
 {
-       struct tty_struct *tty = NULL;
        int saw_console_brk = 0;
        int limit = 10000;
 
-       if (port->info != NULL)         /* Unopened serial console */
-               tty = port->info->tty;
-
        while (limit-- > 0) {
                long status;
-               long c = hypervisor_con_getchar(&status);
-               unsigned char flag;
+               long c = sun4v_con_getchar(&status);
 
                if (status == HV_EWOULDBLOCK)
                        break;
@@ -110,27 +104,90 @@ static struct tty_struct *receive_chars(struct uart_port *port)
                        continue;
                }
 
-               flag = TTY_NORMAL;
                port->icount.rx++;
-               if (c == CON_BREAK) {
-                       port->icount.brk++;
-                       if (uart_handle_break(port))
-                               continue;
-                       flag = TTY_BREAK;
-               }
 
                if (uart_handle_sysrq_char(port, c))
                        continue;
 
-               if ((port->ignore_status_mask & IGNORE_ALL) ||
-                   ((port->ignore_status_mask & IGNORE_BREAK) &&
-                    (c == CON_BREAK)))
+               tty_insert_flip_char(tty, c, TTY_NORMAL);
+       }
+
+       return saw_console_brk;
+}
+
+static int receive_chars_read(struct uart_port *port, struct tty_struct *tty)
+{
+       int saw_console_brk = 0;
+       int limit = 10000;
+
+       while (limit-- > 0) {
+               unsigned long ra = __pa(con_read_page);
+               unsigned long bytes_read, i;
+               long stat = sun4v_con_read(ra, PAGE_SIZE, &bytes_read);
+
+               if (stat != HV_EOK) {
+                       bytes_read = 0;
+
+                       if (stat == CON_BREAK) {
+                               if (uart_handle_break(port))
+                                       continue;
+                               saw_console_brk = 1;
+                               *con_read_page = 0;
+                               bytes_read = 1;
+                       } else if (stat == CON_HUP) {
+                               hung_up = 1;
+                               uart_handle_dcd_change(port, 0);
+                               continue;
+                       } else {
+                               /* HV_EWOULDBLOCK, etc.  */
+                               break;
+                       }
+               }
+
+               if (hung_up) {
+                       hung_up = 0;
+                       uart_handle_dcd_change(port, 1);
+               }
+
+               for (i = 0; i < bytes_read; i++)
+                       uart_handle_sysrq_char(port, con_read_page[i]);
+
+               if (tty == NULL)
                        continue;
 
-               tty_insert_flip_char(tty, c, flag);
+               port->icount.rx += bytes_read;
+
+               tty_insert_flip_string(tty, con_read_page, bytes_read);
        }
 
-       if (saw_console_brk)
+       return saw_console_brk;
+}
+
+struct sunhv_ops {
+       void (*transmit_chars)(struct uart_port *port, struct circ_buf *xmit);
+       int (*receive_chars)(struct uart_port *port, struct tty_struct *tty);
+};
+
+static struct sunhv_ops bychar_ops = {
+       .transmit_chars = transmit_chars_putchar,
+       .receive_chars = receive_chars_getchar,
+};
+
+static struct sunhv_ops bywrite_ops = {
+       .transmit_chars = transmit_chars_write,
+       .receive_chars = receive_chars_read,
+};
+
+static struct sunhv_ops *sunhv_ops = &bychar_ops;
+
+static struct tty_struct *receive_chars(struct uart_port *port)
+{
+       struct tty_struct *tty = NULL;
+
+       if (port->state != NULL)                /* Unopened serial console */
+               tty = port->state->port.tty;
+
+       if (sunhv_ops->receive_chars(port, tty))
                sun_do_break();
 
        return tty;
@@ -140,22 +197,14 @@ static void transmit_chars(struct uart_port *port)
 {
        struct circ_buf *xmit;
 
-       if (!port->info)
+       if (!port->state)
                return;
 
-       xmit = &port->info->xmit;
+       xmit = &port->state->xmit;
        if (uart_circ_empty(xmit) || uart_tx_stopped(port))
                return;
 
-       while (!uart_circ_empty(xmit)) {
-               long status = hypervisor_con_putchar(xmit->buf[xmit->tail]);
-
-               if (status != HV_EOK)
-                       break;
-
-               xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
-               port->icount.tx++;
-       }
+       sunhv_ops->transmit_chars(port, xmit);
 
        if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
                uart_write_wakeup(port);
@@ -209,17 +258,7 @@ static void sunhv_stop_tx(struct uart_port *port)
 /* port->lock held by caller.  */
 static void sunhv_start_tx(struct uart_port *port)
 {
-       struct circ_buf *xmit = &port->info->xmit;
-
-       while (!uart_circ_empty(xmit)) {
-               long status = hypervisor_con_putchar(xmit->buf[xmit->tail]);
-
-               if (status != HV_EOK)
-                       break;
-
-               xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
-               port->icount.tx++;
-       }
+       transmit_chars(port);
 }
 
 /* port->lock is not held.  */
@@ -231,9 +270,10 @@ static void sunhv_send_xchar(struct uart_port *port, char ch)
        spin_lock_irqsave(&port->lock, flags);
 
        while (limit-- > 0) {
-               long status = hypervisor_con_putchar(ch);
+               long status = sun4v_con_putchar(ch);
                if (status == HV_EOK)
                        break;
+               udelay(1);
        }
 
        spin_unlock_irqrestore(&port->lock, flags);
@@ -254,15 +294,15 @@ static void sunhv_break_ctl(struct uart_port *port, int break_state)
 {
        if (break_state) {
                unsigned long flags;
-               int limit = 1000000;
+               int limit = 10000;
 
                spin_lock_irqsave(&port->lock, flags);
 
                while (limit-- > 0) {
-                       long status = hypervisor_con_putchar(CON_BREAK);
+                       long status = sun4v_con_putchar(CON_BREAK);
                        if (status == HV_EOK)
                                break;
-                       udelay(2);
+                       udelay(1);
                }
 
                spin_unlock_irqrestore(&port->lock, flags);
@@ -352,64 +392,137 @@ static struct uart_ops sunhv_pops = {
 
 static struct uart_driver sunhv_reg = {
        .owner                  = THIS_MODULE,
-       .driver_name            = "serial",
+       .driver_name            = "sunhv",
        .dev_name               = "ttyS",
        .major                  = TTY_MAJOR,
 };
 
 static struct uart_port *sunhv_port;
 
-static inline void sunhv_console_putchar(struct uart_port *port, char c)
+/* Copy 's' into the con_write_page, decoding "\n" into
+ * "\r\n" along the way.  We have to return two lengths
+ * because the caller needs to know how much to advance
+ * 's' and also how many bytes to output via con_write_page.
+ */
+static int fill_con_write_page(const char *s, unsigned int n,
+                              unsigned long *page_bytes)
+{
+       const char *orig_s = s;
+       char *p = con_write_page;
+       int left = PAGE_SIZE;
+
+       while (n--) {
+               if (*s == '\n') {
+                       if (left < 2)
+                               break;
+                       *p++ = '\r';
+                       left--;
+               } else if (left < 1)
+                       break;
+               *p++ = *s++;
+               left--;
+       }
+       *page_bytes = p - con_write_page;
+       return s - orig_s;
+}
+
+static void sunhv_console_write_paged(struct console *con, const char *s, unsigned n)
 {
+       struct uart_port *port = sunhv_port;
        unsigned long flags;
-       int limit = 1000000;
+       int locked = 1;
+
+       local_irq_save(flags);
+       if (port->sysrq) {
+               locked = 0;
+       } else if (oops_in_progress) {
+               locked = spin_trylock(&port->lock);
+       } else
+               spin_lock(&port->lock);
+
+       while (n > 0) {
+               unsigned long ra = __pa(con_write_page);
+               unsigned long page_bytes;
+               unsigned int cpy = fill_con_write_page(s, n,
+                                                      &page_bytes);
+
+               n -= cpy;
+               s += cpy;
+               while (page_bytes > 0) {
+                       unsigned long written;
+                       int limit = 1000000;
+
+                       while (limit--) {
+                               unsigned long stat;
+
+                               stat = sun4v_con_write(ra, page_bytes,
+                                                      &written);
+                               if (stat == HV_EOK)
+                                       break;
+                               udelay(1);
+                       }
+                       if (limit < 0)
+                               break;
+                       page_bytes -= written;
+                       ra += written;
+               }
+       }
 
-       spin_lock_irqsave(&port->lock, flags);
+       if (locked)
+               spin_unlock(&port->lock);
+       local_irq_restore(flags);
+}
+
+static inline void sunhv_console_putchar(struct uart_port *port, char c)
+{
+       int limit = 1000000;
 
        while (limit-- > 0) {
-               long status = hypervisor_con_putchar(c);
+               long status = sun4v_con_putchar(c);
                if (status == HV_EOK)
                        break;
-               udelay(2);
+               udelay(1);
        }
-
-       spin_unlock_irqrestore(&port->lock, flags);
 }
 
-static void sunhv_console_write(struct console *con, const char *s, unsigned n)
+static void sunhv_console_write_bychar(struct console *con, const char *s, unsigned n)
 {
        struct uart_port *port = sunhv_port;
-       int i;
+       unsigned long flags;
+       int i, locked = 1;
+
+       local_irq_save(flags);
+       if (port->sysrq) {
+               locked = 0;
+       } else if (oops_in_progress) {
+               locked = spin_trylock(&port->lock);
+       } else
+               spin_lock(&port->lock);
 
        for (i = 0; i < n; i++) {
                if (*s == '\n')
                        sunhv_console_putchar(port, '\r');
                sunhv_console_putchar(port, *s++);
        }
+
+       if (locked)
+               spin_unlock(&port->lock);
+       local_irq_restore(flags);
 }
 
 static struct console sunhv_console = {
        .name   =       "ttyHV",
-       .write  =       sunhv_console_write,
+       .write  =       sunhv_console_write_bychar,
        .device =       uart_console_device,
        .flags  =       CON_PRINTBUFFER,
        .index  =       -1,
        .data   =       &sunhv_reg,
 };
 
-static inline struct console *SUNHV_CONSOLE(void)
-{
-       if (con_is_present())
-               return NULL;
-
-       sunhv_console.index = 0;
-
-       return &sunhv_console;
-}
-
 static int __devinit hv_probe(struct of_device *op, const struct of_device_id *match)
 {
        struct uart_port *port;
+       unsigned long minor;
        int err;
 
        if (op->irqs[0] == 0xffffffff)
@@ -419,6 +532,22 @@ static int __devinit hv_probe(struct of_device *op, const struct of_device_id *m
        if (unlikely(!port))
                return -ENOMEM;
 
+       minor = 1;
+       if (sun4v_hvapi_register(HV_GRP_CORE, 1, &minor) == 0 &&
+           minor >= 1) {
+               err = -ENOMEM;
+               con_write_page = kzalloc(PAGE_SIZE, GFP_KERNEL);
+               if (!con_write_page)
+                       goto out_free_port;
+
+               con_read_page = kzalloc(PAGE_SIZE, GFP_KERNEL);
+               if (!con_read_page)
+                       goto out_free_con_write_page;
+
+               sunhv_console.write = sunhv_console_write_paged;
+               sunhv_ops = &bywrite_ops;
+       }
+
        sunhv_port = port;
 
        port->line = 0;
@@ -432,17 +561,12 @@ static int __devinit hv_probe(struct of_device *op, const struct of_device_id *m
 
        port->dev = &op->dev;
 
-       sunhv_reg.minor = sunserial_current_minor;
-       sunhv_reg.nr = 1;
-
-       err = uart_register_driver(&sunhv_reg);
+       err = sunserial_register_minors(&sunhv_reg, 1);
        if (err)
-               goto out_free_port;
+               goto out_free_con_read_page;
 
-       sunhv_reg.tty_driver->name_base = sunhv_reg.minor - 64;
-       sunserial_current_minor += 1;
-
-       sunhv_reg.cons = SUNHV_CONSOLE();
+       sunserial_console_match(&sunhv_console, op->node,
+                               &sunhv_reg, port->line, false);
 
        err = uart_add_one_port(&sunhv_reg, port);
        if (err)
@@ -460,8 +584,13 @@ out_remove_port:
        uart_remove_one_port(&sunhv_reg, port);
 
 out_unregister_driver:
-       sunserial_current_minor -= 1;
-       uart_unregister_driver(&sunhv_reg);
+       sunserial_unregister_minors(&sunhv_reg, 1);
+
+out_free_con_read_page:
+       kfree(con_read_page);
+
+out_free_con_write_page:
+       kfree(con_write_page);
 
 out_free_port:
        kfree(port);
@@ -477,8 +606,7 @@ static int __devexit hv_remove(struct of_device *dev)
 
        uart_remove_one_port(&sunhv_reg, port);
 
-       sunserial_current_minor -= 1;
-       uart_unregister_driver(&sunhv_reg);
+       sunserial_unregister_minors(&sunhv_reg, 1);
 
        kfree(port);
        sunhv_port = NULL;
@@ -488,7 +616,7 @@ static int __devexit hv_remove(struct of_device *dev)
        return 0;
 }
 
-static struct of_device_id hv_match[] = {
+static const struct of_device_id hv_match[] = {
        {
                .name = "console",
                .compatible = "qcn",