This patch adds the DMA-patch build by Chip Coldwell for the AT91/AT32 serial USARTS to the patch stack, but the order of the patches has been changed. Because the DMA-patch does not seem to work on AT32 and has not been fixed since months and several kernel releases, this DMA patch is holding things up. Currently the DBGU and the USART does not work on Preempt-RT, and needs a splitup of the interrupt handler. The biggest part of the interrupt handler splitup patch is not related to RT, and can therefor be integrated in mainline seperately. When that is done, the RT-patch can be fixed. Due to the bug in the DMA-patch this integration gets too complicated, and a reorder of patches is required. Previously this order was valid: * http://maxim.org.za/AT91RM9200/2.6/2.6.23-at91.patch.gz * Serial-port interrupt handler splitup patches With this patch, the order needs to be: * Serial-port splitup patches. * This patch for adding DMA-support. The older DMA-patch has to be removed from the patch at: http://maxim.org.za/AT91RM9200/2.6/2.6.23-at91.patch.gz Signed-off-by: Remy Bohmer --- drivers/serial/atmel_serial.c | 325 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 319 insertions(+), 6 deletions(-) Index: linux-2.6.23/drivers/serial/atmel_serial.c =================================================================== --- linux-2.6.23.orig/drivers/serial/atmel_serial.c 2007-12-14 12:00:20.000000000 +0100 +++ linux-2.6.23/drivers/serial/atmel_serial.c 2007-12-14 12:00:23.000000000 +0100 @@ -7,6 +7,8 @@ * Based on drivers/char/serial_sa1100.c, by Deep Blue Solutions Ltd. * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. * + * DMA support added by Chip Coldwell. + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -33,6 +35,7 @@ #include #include #include +#include #include #include @@ -47,6 +50,11 @@ #include "atmel_serial.h" +#define SUPPORT_PDC +#define PDC_BUFFER_SIZE (L1_CACHE_BYTES << 3) +#warning "Revisit" +#define PDC_RX_TIMEOUT (3 * 10) /* 3 bytes */ + #if defined(CONFIG_SERIAL_ATMEL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) #define SUPPORT_SYSRQ #endif @@ -111,6 +119,13 @@ static int (*atmel_open_hook) (struct uart_port *); static void (*atmel_close_hook) (struct uart_port *); +struct atmel_dma_buffer { + unsigned char *buf; + dma_addr_t dma_addr; + size_t dma_size; + unsigned int ofs; +}; + struct atmel_uart_char { unsigned int status; unsigned int overrun; @@ -136,6 +151,13 @@ struct atmel_uart_port { unsigned short suspended; /* is port suspended? */ int break_active; /* break being received */ + short use_dma_rx; /* enable PDC receiver */ + short pdc_rx_idx; /* current PDC RX buffer */ + struct atmel_dma_buffer pdc_rx[2]; /* PDC receier */ + + short use_dma_tx; /* enable PDC transmitter */ + struct atmel_dma_buffer pdc_tx; /* PDC transmitter */ + struct tasklet_struct rx_task; struct tasklet_struct status_task; unsigned int irq_pending; @@ -146,6 +168,9 @@ struct atmel_uart_port { static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART]; +#define PDC_RX_BUF(port) &(port)->pdc_rx[(port)->pdc_rx_idx] +#define PDC_RX_SWITCH(port) (port)->pdc_rx_idx = !(port)->pdc_rx_idx + #ifdef SUPPORT_SYSRQ static struct console atmel_console; #endif @@ -231,7 +256,14 @@ static u_int atmel_get_mctrl(struct uart */ static void atmel_stop_tx(struct uart_port *port) { - UART_PUT_IDR(port, ATMEL_US_TXRDY); + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + + if (atmel_port->use_dma_tx) { + /* disable PDC transmit */ + UART_PUT_PTCR(port, ATMEL_PDC_TXTDIS); + UART_PUT_IDR(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE); + } else + UART_PUT_IDR(port, ATMEL_US_TXRDY); } /* @@ -239,7 +271,19 @@ static void atmel_stop_tx(struct uart_po */ static void atmel_start_tx(struct uart_port *port) { - UART_PUT_IER(port, ATMEL_US_TXRDY); + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + + if (atmel_port->use_dma_tx) { + if (UART_GET_PTSR(port) & ATMEL_PDC_TXTEN) + /* The transmitter is already running. Yes, we + really need this.*/ + return; + + UART_PUT_IER(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE); + /* re-enable PDC transmit */ + UART_PUT_PTCR(port, ATMEL_PDC_TXTEN); + } else + UART_PUT_IER(port, ATMEL_US_TXRDY); } /* @@ -247,7 +291,14 @@ static void atmel_start_tx(struct uart_p */ static void atmel_stop_rx(struct uart_port *port) { - UART_PUT_IDR(port, ATMEL_US_RXRDY); + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + + if (atmel_port->use_dma_rx) { + /* disable PDC receive */ + UART_PUT_PTCR(port, ATMEL_PDC_RXTDIS); + UART_PUT_IDR(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT); + } else + UART_PUT_IDR(port, ATMEL_US_RXRDY); } /* @@ -302,6 +353,148 @@ out: } /* + * Receive data via the PDC. A buffer has been fulled. + */ +static void atmel_pdc_endrx(struct uart_port *port) +{ + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + struct tty_struct *tty = port->info->tty; + struct atmel_dma_buffer *pdc = PDC_RX_BUF(atmel_port); + unsigned int count; + + count = pdc->dma_size - pdc->ofs; + if (likely(count > 0)) { + dma_sync_single_for_cpu(port->dev, + pdc->dma_addr, + pdc->dma_size, + DMA_FROM_DEVICE); + tty_insert_flip_string(tty, pdc->buf + pdc->ofs, count); + tty_flip_buffer_push(tty); + + port->icount.rx += count; + } + + /* Set this buffer as the next receive buffer */ + pdc->ofs = 0; + UART_PUT_RNPR(port, pdc->dma_addr); + UART_PUT_RNCR(port, pdc->dma_size); + + /* Switch to next buffer */ + PDC_RX_SWITCH(atmel_port); /* next PDC buffer */ +} + +/* + * Receive data via the PDC. At least one byte was received, but the + * buffer was not full when the inter-character timeout expired. + */ +static void atmel_pdc_timeout(struct uart_port *port) +{ + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + struct tty_struct *tty = port->info->tty; + struct atmel_dma_buffer *pdc = PDC_RX_BUF(atmel_port); + /* unsigned */ int ofs, count; + + ofs = UART_GET_RPR(port) - pdc->dma_addr; /* current DMA adress */ + count = ofs - pdc->ofs; + + if (likely(count > 0)) { + dma_sync_single_for_cpu(port->dev, + pdc->dma_addr, + pdc->dma_size, + DMA_FROM_DEVICE); + tty_insert_flip_string(tty, pdc->buf + pdc->ofs, count); + tty_flip_buffer_push(tty); + + pdc->ofs = ofs; + port->icount.rx += count; + } + + /* reset the UART timeout */ + UART_PUT_CR(port, ATMEL_US_STTTO); +} + +/* + * Deal with parity, framing and overrun errors. + */ +static void atmel_pdc_rxerr(struct uart_port *port, unsigned int status) +{ + /* clear error */ + UART_PUT_CR(port, ATMEL_US_RSTSTA); + + if (status & ATMEL_US_RXBRK) { + /* ignore side-effect */ + status &= ~(ATMEL_US_PARE | ATMEL_US_FRAME); + port->icount.brk++; + } + if (status & ATMEL_US_PARE) + port->icount.parity++; + if (status & ATMEL_US_FRAME) + port->icount.frame++; + if (status & ATMEL_US_OVRE) + port->icount.overrun++; +} + +/* + * A transmission via the PDC is complete. + */ +static void atmel_pdc_endtx(struct uart_port *port) +{ + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + struct circ_buf *xmit = &port->info->xmit; + struct atmel_dma_buffer *pdc = &atmel_port->pdc_tx; + + xmit->tail += pdc->ofs; + if (xmit->tail >= SERIAL_XMIT_SIZE) + xmit->tail -= SERIAL_XMIT_SIZE; + + port->icount.tx += pdc->ofs; + pdc->ofs = 0; + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); +} + +/* + * The PDC transmitter is idle, so either start the next transfer or + * disable the transmitter. + */ +static void atmel_pdc_txbufe(struct uart_port *port) +{ + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; + struct circ_buf *xmit = &port->info->xmit; + struct atmel_dma_buffer *pdc = &atmel_port->pdc_tx; + int count; + + if (!uart_circ_empty(xmit)) { + /* more to transmit - setup next transfer */ + + /* disable PDC transmit */ + UART_PUT_PTCR(port, ATMEL_PDC_TXTDIS); + dma_sync_single_for_device(port->dev, + pdc->dma_addr, + pdc->dma_size, + DMA_TO_DEVICE); + + if (xmit->tail < xmit->head) + count = xmit->head - xmit->tail; + else + count = SERIAL_XMIT_SIZE - xmit->tail; + pdc->ofs = count; + + UART_PUT_TPR(port, pdc->dma_addr + xmit->tail); + UART_PUT_TCR(port, count); + /* re-enable PDC transmit */ + UART_PUT_PTCR(port, ATMEL_PDC_TXTEN); + } else { + /* nothing left to transmit - disable the transmitter */ + + /* disable PDC transmit */ + UART_PUT_PTCR(port, ATMEL_PDC_TXTDIS); + UART_PUT_IDR(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE); + } +} + +/* * Characters received (called from interrupt handler) */ static void atmel_rx_chars(struct uart_port *port) @@ -417,6 +610,16 @@ atmel_handle_receive(struct uart_port *p { struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port; + /* PDC receive */ + if (pending & ATMEL_US_ENDRX) + atmel_pdc_endrx(port); + if (pending & ATMEL_US_TIMEOUT) + atmel_pdc_timeout(port); + if (atmel_port->use_dma_rx && + (pending & (ATMEL_US_RXBRK | ATMEL_US_OVRE | + ATMEL_US_FRAME | ATMEL_US_PARE))) + atmel_pdc_rxerr(port, pending); + /* Interrupt receive */ if (pending & ATMEL_US_RXRDY) atmel_rx_chars(port); @@ -438,6 +641,12 @@ atmel_handle_receive(struct uart_port *p static inline void atmel_handle_transmit(struct uart_port *port, unsigned int pending) { + /* PDC transmit */ + if (pending & ATMEL_US_ENDTX) + atmel_pdc_endtx(port); + if (pending & ATMEL_US_TXBUFE) + atmel_pdc_txbufe(port); + /* Interrupt transmit */ if (pending & ATMEL_US_TXRDY) atmel_tx_chars(port); @@ -551,6 +760,7 @@ static void atmel_status_handler_task(un */ static int atmel_startup(struct uart_port *port) { + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port; int retval; /* @@ -572,6 +782,56 @@ static int atmel_startup(struct uart_por } /* + * Initialize DMA (if necessary) + */ + if (atmel_port->use_dma_rx) { + int i; + + for (i = 0; i < 2; i++) { + struct atmel_dma_buffer *pdc = &atmel_port->pdc_rx[i]; + + pdc->buf = kmalloc(PDC_BUFFER_SIZE, GFP_KERNEL); + if (pdc->buf == NULL) { + if (i != 0) { + dma_unmap_single(port->dev, + atmel_port->pdc_rx[0].dma_addr, + PDC_BUFFER_SIZE, + DMA_FROM_DEVICE); + kfree(atmel_port->pdc_rx[0].buf); + } + free_irq(port->irq, port); + return -ENOMEM; + } + pdc->dma_addr = dma_map_single(port->dev, + pdc->buf, + PDC_BUFFER_SIZE, + DMA_FROM_DEVICE); + pdc->dma_size = PDC_BUFFER_SIZE; + pdc->ofs = 0; + } + + atmel_port->pdc_rx_idx = 0; + + UART_PUT_RPR(port, atmel_port->pdc_rx[0].dma_addr); + UART_PUT_RCR(port, PDC_BUFFER_SIZE); + + UART_PUT_RNPR(port, atmel_port->pdc_rx[1].dma_addr); + UART_PUT_RNCR(port, PDC_BUFFER_SIZE); + } + if (atmel_port->use_dma_tx) { + struct atmel_dma_buffer *pdc = &atmel_port->pdc_tx; + struct circ_buf *xmit = &port->info->xmit; + + pdc->buf = xmit->buf; + pdc->dma_addr = dma_map_single(port->dev, + pdc->buf, + SERIAL_XMIT_SIZE, + DMA_TO_DEVICE); + pdc->dma_size = SERIAL_XMIT_SIZE; + pdc->ofs = 0; + } + + /* * If there is a specific "open" function (to register * control line interrupts) */ @@ -590,8 +850,18 @@ static int atmel_startup(struct uart_por /* enable xmit & rcvr */ UART_PUT_CR(port, ATMEL_US_TXEN | ATMEL_US_RXEN); - /* enable receive only */ - UART_PUT_IER(port, ATMEL_US_RXRDY); + if (atmel_port->use_dma_rx) { + /* set UART timeout */ + UART_PUT_RTOR(port, PDC_RX_TIMEOUT); + UART_PUT_CR(port, ATMEL_US_STTTO); + + UART_PUT_IER(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT); + /* enable PDC controller */ + UART_PUT_PTCR(port, ATMEL_PDC_RXTEN); + } else { + /* enable receive only */ + UART_PUT_IER(port, ATMEL_US_RXRDY); + } return 0; } @@ -601,6 +871,38 @@ static int atmel_startup(struct uart_por */ static void atmel_shutdown(struct uart_port *port) { + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port; + /* + * Ensure everything is stopped. + */ + atmel_stop_rx(port); + atmel_stop_tx(port); + + /* + * Shut-down the DMA. + */ + if (atmel_port->use_dma_rx) { + int i; + + for (i = 0; i < 2; i++) { + struct atmel_dma_buffer *pdc = &atmel_port->pdc_rx[i]; + + dma_unmap_single(port->dev, + pdc->dma_addr, + pdc->dma_size, + DMA_FROM_DEVICE); + kfree(pdc->buf); + } + } + if (atmel_port->use_dma_tx) { + struct atmel_dma_buffer *pdc = &atmel_port->pdc_tx; + + dma_unmap_single(port->dev, + pdc->dma_addr, + pdc->dma_size, + DMA_TO_DEVICE); + } + /* * Disable all interrupts, port and break condition. */ @@ -654,6 +956,7 @@ static void atmel_serial_pm(struct uart_ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { + struct atmel_uart_port *atmel_port = (struct atmel_uart_port *) port; unsigned long flags; unsigned int mode, imr, quot, baud; @@ -713,6 +1016,9 @@ static void atmel_set_termios(struct uar if (termios->c_iflag & (BRKINT | PARMRK)) port->read_status_mask |= ATMEL_US_RXBRK; + if (atmel_port->use_dma_rx) /* need to enable error interrupts */ + UART_PUT_IER(port, port->read_status_mask); + /* * Characters to ignore */ @@ -900,6 +1206,13 @@ static void __devinit atmel_init_port(st clk_enable(atmel_port->clk); port->uartclk = clk_get_rate(atmel_port->clk); } + +#ifdef SUPPORT_PDC + atmel_port->use_dma_rx = data->use_dma_rx; + atmel_port->use_dma_tx = data->use_dma_tx; + if (atmel_port->use_dma_tx) + port->fifosize = PDC_BUFFER_SIZE; +#endif } /* @@ -1085,7 +1398,7 @@ static int atmel_serial_suspend(struct p struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port; if (device_may_wakeup(&pdev->dev) - && !at91_suspend_entering_slow_clock()) + && !clk_must_disable(atmel_port->clk)) enable_irq_wake(port->irq); else { uart_suspend_port(&atmel_uart, port);