static int (*atmel_open_hook)(struct uart_port *);
 static void (*atmel_close_hook)(struct uart_port *);
 
+struct atmel_uart_char {
+       u16             status;
+       u16             ch;
+};
+
+#define ATMEL_SERIAL_RINGSIZE 1024
+
 /*
  * We wrap our port structure around the generic uart_port.
  */
        struct clk              *clk;           /* uart clock */
        unsigned short          suspended;      /* is port suspended? */
        int                     break_active;   /* break being received */
+
+       struct tasklet_struct   tasklet;
+       unsigned int            irq_status;
+       unsigned int            irq_status_prev;
+
+       struct circ_buf         rx_ring;
 };
 
 static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART];
                UART_PUT_CR(port, ATMEL_US_STPBRK);     /* stop break */
 }
 
+/*
+ * Stores the incoming character in the ring buffer
+ */
+static void
+atmel_buffer_rx_char(struct uart_port *port, unsigned int status,
+                    unsigned int ch)
+{
+       struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
+       struct circ_buf *ring = &atmel_port->rx_ring;
+       struct atmel_uart_char *c;
+
+       if (!CIRC_SPACE(ring->head, ring->tail, ATMEL_SERIAL_RINGSIZE))
+               /* Buffer overflow, ignore char */
+               return;
+
+       c = &((struct atmel_uart_char *)ring->buf)[ring->head];
+       c->status       = status;
+       c->ch           = ch;
+
+       /* Make sure the character is stored before we update head. */
+       smp_wmb();
+
+       ring->head = (ring->head + 1) & (ATMEL_SERIAL_RINGSIZE - 1);
+}
+
 /*
  * Characters received (called from interrupt handler)
  */
 static void atmel_rx_chars(struct uart_port *port)
 {
        struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
-       struct tty_struct *tty = port->info->tty;
-       unsigned int status, ch, flg;
+       unsigned int status, ch;
 
        status = UART_GET_CSR(port);
        while (status & ATMEL_US_RXRDY) {
                ch = UART_GET_CHAR(port);
 
-               port->icount.rx++;
-
-               flg = TTY_NORMAL;
-
                /*
                 * note that the error handling code is
                 * out of the main execution path
                if (unlikely(status & (ATMEL_US_PARE | ATMEL_US_FRAME
                                       | ATMEL_US_OVRE | ATMEL_US_RXBRK)
                             || atmel_port->break_active)) {
+
                        /* clear error */
                        UART_PUT_CR(port, ATMEL_US_RSTSTA);
+
                        if (status & ATMEL_US_RXBRK
                            && !atmel_port->break_active) {
-                               /* ignore side-effect */
-                               status &= ~(ATMEL_US_PARE | ATMEL_US_FRAME);
-                               port->icount.brk++;
                                atmel_port->break_active = 1;
                                UART_PUT_IER(port, ATMEL_US_RXBRK);
-                               if (uart_handle_break(port))
-                                       goto ignore_char;
                        } else {
                                /*
                                 * This is either the end-of-break
                                status &= ~ATMEL_US_RXBRK;
                                atmel_port->break_active = 0;
                        }
-                       if (status & ATMEL_US_PARE)
-                               port->icount.parity++;
-                       if (status & ATMEL_US_FRAME)
-                               port->icount.frame++;
-                       if (status & ATMEL_US_OVRE)
-                               port->icount.overrun++;
-
-                       status &= port->read_status_mask;
-
-                       if (status & ATMEL_US_RXBRK)
-                               flg = TTY_BREAK;
-                       else if (status & ATMEL_US_PARE)
-                               flg = TTY_PARITY;
-                       else if (status & ATMEL_US_FRAME)
-                               flg = TTY_FRAME;
                }
 
-               if (uart_handle_sysrq_char(port, ch))
-                       goto ignore_char;
-
-               uart_insert_char(port, status, ATMEL_US_OVRE, ch, flg);
-
-ignore_char:
+               atmel_buffer_rx_char(port, status, ch);
                status = UART_GET_CSR(port);
        }
 
-       tty_flip_buffer_push(tty);
+       tasklet_schedule(&atmel_port->tasklet);
 }
 
 /*
- * Transmit characters (called from interrupt handler)
+ * Transmit characters (called from tasklet with TXRDY interrupt
+ * disabled)
  */
 static void atmel_tx_chars(struct uart_port *port)
 {
        struct circ_buf *xmit = &port->info->xmit;
 
-       if (port->x_char) {
+       if (port->x_char && UART_GET_CSR(port) & ATMEL_US_TXRDY) {
                UART_PUT_CHAR(port, port->x_char);
                port->icount.tx++;
                port->x_char = 0;
-               return;
        }
-       if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
-               atmel_stop_tx(port);
+       if (uart_circ_empty(xmit) || uart_tx_stopped(port))
                return;
-       }
 
        while (UART_GET_CSR(port) & ATMEL_US_TXRDY) {
                UART_PUT_CHAR(port, xmit->buf[xmit->tail]);
        if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
                uart_write_wakeup(port);
 
-       if (uart_circ_empty(xmit))
-               atmel_stop_tx(port);
+       if (!uart_circ_empty(xmit))
+               UART_PUT_IER(port, ATMEL_US_TXRDY);
 }
 
 /*
 }
 
 /*
- * transmit interrupt handler.
+ * transmit interrupt handler. (Transmit is IRQF_NODELAY safe)
  */
 static void
 atmel_handle_transmit(struct uart_port *port, unsigned int pending)
 {
+       struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
+
        /* Interrupt transmit */
-       if (pending & ATMEL_US_TXRDY)
-               atmel_tx_chars(port);
+       if (pending & ATMEL_US_TXRDY) {
+               UART_PUT_IDR(port, ATMEL_US_TXRDY);
+               tasklet_schedule(&atmel_port->tasklet);
+       }
 }
 
 /*
 atmel_handle_status(struct uart_port *port, unsigned int pending,
                    unsigned int status)
 {
-       /* TODO: All reads to CSR will clear these interrupts! */
-       if (pending & ATMEL_US_RIIC)
-               port->icount.rng++;
-       if (pending & ATMEL_US_DSRIC)
-               port->icount.dsr++;
-       if (pending & ATMEL_US_DCDIC)
-               uart_handle_dcd_change(port, !(status & ATMEL_US_DCD));
-       if (pending & ATMEL_US_CTSIC)
-               uart_handle_cts_change(port, !(status & ATMEL_US_CTS));
+       struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
+
        if (pending & (ATMEL_US_RIIC | ATMEL_US_DSRIC | ATMEL_US_DCDIC
-                               | ATMEL_US_CTSIC))
-               wake_up_interruptible(&port->info->delta_msr_wait);
+                               | ATMEL_US_CTSIC)) {
+               atmel_port->irq_status = status;
+               tasklet_schedule(&atmel_port->tasklet);
+       }
 }
 
 /*
        return IRQ_HANDLED;
 }
 
+static void atmel_rx_from_ring(struct uart_port *port)
+{
+       struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
+       struct circ_buf *ring = &atmel_port->rx_ring;
+       unsigned int flg;
+       unsigned int status;
+
+       while (ring->head != ring->tail) {
+               struct atmel_uart_char c;
+
+               /* Make sure c is loaded after head. */
+               smp_rmb();
+
+               c = ((struct atmel_uart_char *)ring->buf)[ring->tail];
+
+               ring->tail = (ring->tail + 1) & (ATMEL_SERIAL_RINGSIZE - 1);
+
+               port->icount.rx++;
+               status = c.status;
+               flg = TTY_NORMAL;
+
+               /*
+                * note that the error handling code is
+                * out of the main execution path
+                */
+               if (unlikely(status & (ATMEL_US_PARE | ATMEL_US_FRAME
+                                      | ATMEL_US_OVRE | ATMEL_US_RXBRK))) {
+                       if (status & ATMEL_US_RXBRK) {
+                               /* ignore side-effect */
+                               status &= ~(ATMEL_US_PARE | ATMEL_US_FRAME);
+
+                               port->icount.brk++;
+                               if (uart_handle_break(port))
+                                       continue;
+                       }
+                       if (status & ATMEL_US_PARE)
+                               port->icount.parity++;
+                       if (status & ATMEL_US_FRAME)
+                               port->icount.frame++;
+                       if (status & ATMEL_US_OVRE)
+                               port->icount.overrun++;
+
+                       status &= port->read_status_mask;
+
+                       if (status & ATMEL_US_RXBRK)
+                               flg = TTY_BREAK;
+                       else if (status & ATMEL_US_PARE)
+                               flg = TTY_PARITY;
+                       else if (status & ATMEL_US_FRAME)
+                               flg = TTY_FRAME;
+               }
+
+
+               if (uart_handle_sysrq_char(port, c.ch))
+                       continue;
+
+               uart_insert_char(port, status, ATMEL_US_OVRE, c.ch, flg);
+       }
+
+       /*
+        * Drop the lock here since it might end up calling
+        * uart_start(), which takes the lock.
+        */
+       spin_unlock(&port->lock);
+       tty_flip_buffer_push(port->info->tty);
+       spin_lock(&port->lock);
+}
+
+/*
+ * tasklet handling tty stuff outside the interrupt handler.
+ */
+static void atmel_tasklet_func(unsigned long data)
+{
+       struct uart_port *port = (struct uart_port *)data;
+       struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
+       unsigned int status;
+       unsigned int status_change;
+
+       /* The interrupt handler does not take the lock */
+       spin_lock(&port->lock);
+
+       atmel_tx_chars(port);
+
+       status = atmel_port->irq_status;
+       status_change = status ^ atmel_port->irq_status_prev;
+
+       if (status_change & (ATMEL_US_RI | ATMEL_US_DSR
+                               | ATMEL_US_DCD | ATMEL_US_CTS)) {
+               /* TODO: All reads to CSR will clear these interrupts! */
+               if (status_change & ATMEL_US_RI)
+                       port->icount.rng++;
+               if (status_change & ATMEL_US_DSR)
+                       port->icount.dsr++;
+               if (status_change & ATMEL_US_DCD)
+                       uart_handle_dcd_change(port, !(status & ATMEL_US_DCD));
+               if (status_change & ATMEL_US_CTS)
+                       uart_handle_cts_change(port, !(status & ATMEL_US_CTS));
+
+               wake_up_interruptible(&port->info->delta_msr_wait);
+
+               atmel_port->irq_status_prev = status;
+       }
+
+       atmel_rx_from_ring(port);
+
+       spin_unlock(&port->lock);
+}
+
 /*
  * Perform initialization and enable port for reception
  */
        port->mapbase   = pdev->resource[0].start;
        port->irq       = pdev->resource[1].start;
 
+       tasklet_init(&atmel_port->tasklet, atmel_tasklet_func,
+                       (unsigned long)port);
+
+       memset(&atmel_port->rx_ring, 0, sizeof(atmel_port->rx_ring));
+
        if (data->regs)
                /* Already mapped by setup code */
                port->membase = data->regs;
 static int __devinit atmel_serial_probe(struct platform_device *pdev)
 {
        struct atmel_uart_port *port;
+       void *data;
        int ret;
 
+       BUILD_BUG_ON(!is_power_of_2(ATMEL_SERIAL_RINGSIZE));
+
        port = &atmel_ports[pdev->id];
        atmel_init_port(port, pdev);
 
+       ret = -ENOMEM;
+       data = kmalloc(ATMEL_SERIAL_RINGSIZE, GFP_KERNEL);
+       if (!data)
+               goto err_alloc_ring;
+       port->rx_ring.buf = data;
+
        ret = uart_add_one_port(&atmel_uart, &port->uart);
        if (ret)
                goto err_add_port;
        return 0;
 
 err_add_port:
+       kfree(port->rx_ring.buf);
+       port->rx_ring.buf = NULL;
+err_alloc_ring:
        if (!atmel_is_console_port(&port->uart)) {
                clk_disable(port->clk);
                clk_put(port->clk);
 
        ret = uart_remove_one_port(&atmel_uart, port);
 
+       tasklet_kill(&atmel_port->tasklet);
+       kfree(atmel_port->rx_ring.buf);
+
        /* "port" is allocated statically, so we shouldn't free it */
 
        clk_disable(atmel_port->clk);