summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/tty/serial/samsung.c101
1 files changed, 101 insertions, 0 deletions
diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c
index 7196a3e586cf..825c8192b944 100644
--- a/drivers/tty/serial/samsung.c
+++ b/drivers/tty/serial/samsung.c
@@ -28,6 +28,9 @@
#define SUPPORT_SYSRQ
#endif
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
@@ -453,6 +456,93 @@ static void s3c24xx_serial_break_ctl(struct uart_port *port, int break_state)
spin_unlock_irqrestore(&port->lock, flags);
}
+static int s3c24xx_serial_request_dma(struct s3c24xx_uart_port *p)
+{
+ struct s3c24xx_uart_dma *dma = p->dma;
+ dma_cap_mask_t mask;
+ unsigned long flags;
+
+ /* Default slave configuration parameters */
+ dma->rx_conf.direction = DMA_DEV_TO_MEM;
+ dma->rx_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma->rx_conf.src_addr = p->port.mapbase + S3C2410_URXH;
+ dma->rx_conf.src_maxburst = 16;
+
+ dma->tx_conf.direction = DMA_MEM_TO_DEV;
+ dma->tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma->tx_conf.dst_addr = p->port.mapbase + S3C2410_UTXH;
+ if (dma_get_cache_alignment() >= 16)
+ dma->tx_conf.dst_maxburst = 16;
+ else
+ dma->tx_conf.dst_maxburst = 1;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ dma->rx_chan = dma_request_slave_channel_compat(mask, dma->fn,
+ dma->rx_param, p->port.dev, "rx");
+ if (!dma->rx_chan)
+ return -ENODEV;
+
+ dmaengine_slave_config(dma->rx_chan, &dma->rx_conf);
+
+ dma->tx_chan = dma_request_slave_channel_compat(mask, dma->fn,
+ dma->tx_param, p->port.dev, "tx");
+ if (!dma->tx_chan) {
+ dma_release_channel(dma->rx_chan);
+ return -ENODEV;
+ }
+
+ dmaengine_slave_config(dma->tx_chan, &dma->tx_conf);
+
+ /* RX buffer */
+ dma->rx_size = PAGE_SIZE;
+
+ dma->rx_buf = kmalloc(dma->rx_size, GFP_KERNEL);
+
+ if (!dma->rx_buf) {
+ dma_release_channel(dma->rx_chan);
+ dma_release_channel(dma->tx_chan);
+ return -ENOMEM;
+ }
+
+ dma->rx_addr = dma_map_single(dma->rx_chan->device->dev, dma->rx_buf,
+ dma->rx_size, DMA_FROM_DEVICE);
+
+ spin_lock_irqsave(&p->port.lock, flags);
+
+ /* TX buffer */
+ dma->tx_addr = dma_map_single(dma->tx_chan->device->dev,
+ p->port.state->xmit.buf,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+ spin_unlock_irqrestore(&p->port.lock, flags);
+
+ return 0;
+}
+
+static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p)
+{
+ struct s3c24xx_uart_dma *dma = p->dma;
+
+ if (dma->rx_chan) {
+ dmaengine_terminate_all(dma->rx_chan);
+ dma_unmap_single(dma->rx_chan->device->dev, dma->rx_addr,
+ dma->rx_size, DMA_FROM_DEVICE);
+ kfree(dma->rx_buf);
+ dma_release_channel(dma->rx_chan);
+ dma->rx_chan = NULL;
+ }
+
+ if (dma->tx_chan) {
+ dmaengine_terminate_all(dma->tx_chan);
+ dma_unmap_single(dma->tx_chan->device->dev, dma->tx_addr,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+ dma_release_channel(dma->tx_chan);
+ dma->tx_chan = NULL;
+ }
+}
+
static void s3c24xx_serial_shutdown(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
@@ -478,6 +568,10 @@ static void s3c24xx_serial_shutdown(struct uart_port *port)
wr_regl(port, S3C64XX_UINTP, 0xf);
wr_regl(port, S3C64XX_UINTM, 0xf);
}
+
+ if (ourport->dma)
+ s3c24xx_serial_release_dma(ourport);
+
}
static int s3c24xx_serial_startup(struct uart_port *port)
@@ -535,6 +629,13 @@ static int s3c64xx_serial_startup(struct uart_port *port)
port, (unsigned long long)port->mapbase, port->membase);
wr_regl(port, S3C64XX_UINTM, 0xf);
+ if (ourport->dma) {
+ ret = s3c24xx_serial_request_dma(ourport);
+ if (ret < 0) {
+ dev_warn(port->dev, "DMA request failed\n");
+ return ret;
+ }
+ }
ret = request_irq(port->irq, s3c64xx_serial_handle_irq, IRQF_SHARED,
s3c24xx_serial_portname(port), ourport);