drm/i915: Set the multiplier for SDVO on G33 platform
[safe/jmp/linux-2.6] / drivers / serial / sunhv.c
index ba22e25..1df5325 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/vdev.h>
-#include <asm/oplib.h>
+#include <asm/prom.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, struct pt_regs *regs)
+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;
@@ -106,31 +100,94 @@ static struct tty_struct *receive_chars(struct uart_port *port, struct pt_regs *
                }
 
                if (tty == NULL) {
-                       uart_handle_sysrq_char(port, c, regs);
+                       uart_handle_sysrq_char(port, c);
                        continue;
                }
 
-               flag = TTY_NORMAL;
                port->icount.rx++;
-               if (c == CON_BREAK) {
-                       port->icount.brk++;
-                       if (uart_handle_break(port))
+
+               if (uart_handle_sysrq_char(port, c))
+                       continue;
+
+               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;
-                       flag = TTY_BREAK;
+                       } else {
+                               /* HV_EWOULDBLOCK, etc.  */
+                               break;
+                       }
                }
 
-               if (uart_handle_sysrq_char(port, c, regs))
-                       continue;
+               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 ((port->ignore_status_mask & IGNORE_ALL) ||
-                   ((port->ignore_status_mask & IGNORE_BREAK) &&
-                    (c == CON_BREAK)))
+               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->info != NULL)         /* Unopened serial console */
+               tty = port->info->port.tty;
+
+       if (sunhv_ops->receive_chars(port, tty))
                sun_do_break();
 
        return tty;
@@ -147,28 +204,20 @@ static void transmit_chars(struct uart_port *port)
        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);
 }
 
-static irqreturn_t sunhv_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+static irqreturn_t sunhv_interrupt(int irq, void *dev_id)
 {
        struct uart_port *port = dev_id;
        struct tty_struct *tty;
        unsigned long flags;
 
        spin_lock_irqsave(&port->lock, flags);
-       tty = receive_chars(port, regs);
+       tty = receive_chars(port);
        transmit_chars(port);
        spin_unlock_irqrestore(&port->lock, flags);
 
@@ -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);
@@ -281,8 +321,8 @@ static void sunhv_shutdown(struct uart_port *port)
 }
 
 /* port->lock is not held.  */
-static void sunhv_set_termios(struct uart_port *port, struct termios *termios,
-                             struct termios *old)
+static void sunhv_set_termios(struct uart_port *port, struct ktermios *termios,
+                             struct ktermios *old)
 {
        unsigned int baud = uart_get_baud_rate(port, termios, old, 0, 4000000);
        unsigned int quot = uart_get_divisor(port, baud);
@@ -352,200 +392,267 @@ static struct uart_ops sunhv_pops = {
 
 static struct uart_driver sunhv_reg = {
        .owner                  = THIS_MODULE,
-       .driver_name            = "serial",
-       .devfs_name             = "tts/",
+       .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)
+static int __devinit hv_probe(struct of_device *op, const struct of_device_id *match)
 {
-       if (con_is_present())
-               return NULL;
-
-       sunhv_console.index = 0;
+       struct uart_port *port;
+       unsigned long minor;
+       int err;
 
-       return &sunhv_console;
-}
+       if (op->irqs[0] == 0xffffffff)
+               return -ENODEV;
 
-static int __init hv_console_compatible(char *buf, int len)
-{
-       while (len) {
-               int this_len;
+       port = kzalloc(sizeof(struct uart_port), GFP_KERNEL);
+       if (unlikely(!port))
+               return -ENOMEM;
 
-               if (!strcmp(buf, "qcn"))
-                       return 1;
+       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;
 
-               this_len = strlen(buf) + 1;
+               con_read_page = kzalloc(PAGE_SIZE, GFP_KERNEL);
+               if (!con_read_page)
+                       goto out_free_con_write_page;
 
-               buf += this_len;
-               len -= this_len;
+               sunhv_console.write = sunhv_console_write_paged;
+               sunhv_ops = &bywrite_ops;
        }
 
-       return 0;
-}
+       sunhv_port = port;
 
-static unsigned int __init get_interrupt(void)
-{
-       struct device_node *dev_node;
+       port->line = 0;
+       port->ops = &sunhv_pops;
+       port->type = PORT_SUNHV;
+       port->uartclk = ( 29491200 / 16 ); /* arbitrary */
 
-       dev_node = sun4v_vdev_root->child;
-       while (dev_node != NULL) {
-               struct property *prop;
+       port->membase = (unsigned char __iomem *) __pa(port);
 
-               if (strcmp(dev_node->name, "console"))
-                       goto next_sibling;
+       port->irq = op->irqs[0];
 
-               prop = of_find_property(dev_node, "compatible", NULL);
-               if (!prop)
-                       goto next_sibling;
+       port->dev = &op->dev;
 
-               if (hv_console_compatible(prop->value, prop->length))
-                       break;
+       err = sunserial_register_minors(&sunhv_reg, 1);
+       if (err)
+               goto out_free_con_read_page;
 
-       next_sibling:
-               dev_node = dev_node->sibling;
-       }
-       if (!dev_node)
-               return 0;
+       sunserial_console_match(&sunhv_console, op->node,
+                               &sunhv_reg, port->line);
 
-       /* Ok, the this is the OBP node for the sun4v hypervisor
-        * console device.  Decode the interrupt.
-        */
-       return sun4v_vdev_device_interrupt(dev_node);
-}
+       err = uart_add_one_port(&sunhv_reg, port);
+       if (err)
+               goto out_unregister_driver;
 
-static int __init sunhv_init(void)
-{
-       struct uart_port *port;
-       int ret;
+       err = request_irq(port->irq, sunhv_interrupt, 0, "hvcons", port);
+       if (err)
+               goto out_remove_port;
 
-       if (tlb_type != hypervisor)
-               return -ENODEV;
+       dev_set_drvdata(&op->dev, port);
 
-       port = kmalloc(sizeof(struct uart_port), GFP_KERNEL);
-       if (unlikely(!port))
-               return -ENOMEM;
+       return 0;
 
-       memset(port, 0, sizeof(struct uart_port));
+out_remove_port:
+       uart_remove_one_port(&sunhv_reg, port);
 
-       port->line = 0;
-       port->ops = &sunhv_pops;
-       port->type = PORT_SUNHV;
-       port->uartclk = ( 29491200 / 16 ); /* arbitrary */
+out_unregister_driver:
+       sunserial_unregister_minors(&sunhv_reg, 1);
 
-       /* Set this just to make uart_configure_port() happy.  */
-       port->membase = (unsigned char __iomem *) __pa(port);
+out_free_con_read_page:
+       kfree(con_read_page);
 
-       port->irq = get_interrupt();
-       if (!port->irq) {
-               kfree(port);
-               return -ENODEV;
-       }
-
-       sunhv_reg.minor = sunserial_current_minor;
-       sunhv_reg.nr = 1;
+out_free_con_write_page:
+       kfree(con_write_page);
 
-       ret = uart_register_driver(&sunhv_reg);
-       if (ret < 0) {
-               printk(KERN_ERR "SUNHV: uart_register_driver() failed %d\n",
-                      ret);
-               kfree(port);
+out_free_port:
+       kfree(port);
+       sunhv_port = NULL;
+       return err;
+}
 
-               return ret;
-       }
+static int __devexit hv_remove(struct of_device *dev)
+{
+       struct uart_port *port = dev_get_drvdata(&dev->dev);
 
-       sunhv_reg.tty_driver->name_base = sunhv_reg.minor - 64;
-       sunserial_current_minor += 1;
+       free_irq(port->irq, port);
 
-       sunhv_reg.cons = SUNHV_CONSOLE();
+       uart_remove_one_port(&sunhv_reg, port);
 
-       sunhv_port = port;
+       sunserial_unregister_minors(&sunhv_reg, 1);
 
-       ret = uart_add_one_port(&sunhv_reg, port);
-       if (ret < 0) {
-               printk(KERN_ERR "SUNHV: uart_add_one_port() failed %d\n", ret);
-               sunserial_current_minor -= 1;
-               uart_unregister_driver(&sunhv_reg);
-               kfree(port);
-               sunhv_port = NULL;
-               return -ENODEV;
-       }
+       kfree(port);
+       sunhv_port = NULL;
 
-       if (request_irq(port->irq, sunhv_interrupt,
-                       SA_SHIRQ, "serial(sunhv)", port)) {
-               printk(KERN_ERR "sunhv: Cannot register IRQ\n");
-               uart_remove_one_port(&sunhv_reg, port);
-               sunserial_current_minor -= 1;
-               uart_unregister_driver(&sunhv_reg);
-               kfree(port);
-               sunhv_port = NULL;
-               return -ENODEV;
-       }
+       dev_set_drvdata(&dev->dev, NULL);
 
        return 0;
 }
 
-static void __exit sunhv_exit(void)
-{
-       struct uart_port *port = sunhv_port;
-
-       BUG_ON(!port);
+static const struct of_device_id hv_match[] = {
+       {
+               .name = "console",
+               .compatible = "qcn",
+       },
+       {
+               .name = "console",
+               .compatible = "SUNW,sun4v-console",
+       },
+       {},
+};
+MODULE_DEVICE_TABLE(of, hv_match);
 
-       free_irq(port->irq, port);
+static struct of_platform_driver hv_driver = {
+       .name           = "hv",
+       .match_table    = hv_match,
+       .probe          = hv_probe,
+       .remove         = __devexit_p(hv_remove),
+};
 
-       uart_remove_one_port(&sunhv_reg, port);
-       sunserial_current_minor -= 1;
+static int __init sunhv_init(void)
+{
+       if (tlb_type != hypervisor)
+               return -ENODEV;
 
-       uart_unregister_driver(&sunhv_reg);
+       return of_register_driver(&hv_driver, &of_bus_type);
+}
 
-       kfree(sunhv_port);
-       sunhv_port = NULL;
+static void __exit sunhv_exit(void)
+{
+       of_unregister_driver(&hv_driver);
 }
 
 module_init(sunhv_init);
 module_exit(sunhv_exit);
 
 MODULE_AUTHOR("David S. Miller");
-MODULE_DESCRIPTION("SUN4V Hypervisor console driver")
+MODULE_DESCRIPTION("SUN4V Hypervisor console driver");
+MODULE_VERSION("2.0");
 MODULE_LICENSE("GPL");