|
@@ -81,6 +81,8 @@ static void dbg(const char *fmt, ...)
|
|
#define S3C24XX_SERIAL_MAJOR 204
|
|
#define S3C24XX_SERIAL_MAJOR 204
|
|
#define S3C24XX_SERIAL_MINOR 64
|
|
#define S3C24XX_SERIAL_MINOR 64
|
|
|
|
|
|
|
|
+#define S3C24XX_TX_PIO 1
|
|
|
|
+#define S3C24XX_TX_DMA 2
|
|
/* macros to change one thing to another */
|
|
/* macros to change one thing to another */
|
|
|
|
|
|
#define tx_enabled(port) ((port)->unused[0])
|
|
#define tx_enabled(port) ((port)->unused[0])
|
|
@@ -157,33 +159,217 @@ static void s3c24xx_serial_rx_disable(struct uart_port *port)
|
|
static void s3c24xx_serial_stop_tx(struct uart_port *port)
|
|
static void s3c24xx_serial_stop_tx(struct uart_port *port)
|
|
{
|
|
{
|
|
struct s3c24xx_uart_port *ourport = to_ourport(port);
|
|
struct s3c24xx_uart_port *ourport = to_ourport(port);
|
|
|
|
+ struct s3c24xx_uart_dma *dma = ourport->dma;
|
|
|
|
+ struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
+ struct dma_tx_state state;
|
|
|
|
+ int count;
|
|
|
|
|
|
- if (tx_enabled(port)) {
|
|
|
|
- if (s3c24xx_serial_has_interrupt_mask(port))
|
|
|
|
- __set_bit(S3C64XX_UINTM_TXD,
|
|
|
|
- portaddrl(port, S3C64XX_UINTM));
|
|
|
|
- else
|
|
|
|
- disable_irq_nosync(ourport->tx_irq);
|
|
|
|
- tx_enabled(port) = 0;
|
|
|
|
- if (port->flags & UPF_CONS_FLOW)
|
|
|
|
- s3c24xx_serial_rx_enable(port);
|
|
|
|
|
|
+ if (!tx_enabled(port))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ if (s3c24xx_serial_has_interrupt_mask(port))
|
|
|
|
+ __set_bit(S3C64XX_UINTM_TXD,
|
|
|
|
+ portaddrl(port, S3C64XX_UINTM));
|
|
|
|
+ else
|
|
|
|
+ disable_irq_nosync(ourport->tx_irq);
|
|
|
|
+
|
|
|
|
+ if (dma && dma->tx_chan && ourport->tx_in_progress == S3C24XX_TX_DMA) {
|
|
|
|
+ dmaengine_pause(dma->tx_chan);
|
|
|
|
+ dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
|
|
|
|
+ dmaengine_terminate_all(dma->tx_chan);
|
|
|
|
+ dma_sync_single_for_cpu(ourport->port.dev,
|
|
|
|
+ dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE);
|
|
|
|
+ async_tx_ack(dma->tx_desc);
|
|
|
|
+ count = dma->tx_bytes_requested - state.residue;
|
|
|
|
+ xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
|
|
|
|
+ port->icount.tx += count;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ tx_enabled(port) = 0;
|
|
|
|
+ ourport->tx_in_progress = 0;
|
|
|
|
+
|
|
|
|
+ if (port->flags & UPF_CONS_FLOW)
|
|
|
|
+ s3c24xx_serial_rx_enable(port);
|
|
|
|
+
|
|
|
|
+ ourport->tx_mode = 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport);
|
|
|
|
+
|
|
|
|
+static void s3c24xx_serial_tx_dma_complete(void *args)
|
|
|
|
+{
|
|
|
|
+ struct s3c24xx_uart_port *ourport = args;
|
|
|
|
+ struct uart_port *port = &ourport->port;
|
|
|
|
+ struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
+ struct s3c24xx_uart_dma *dma = ourport->dma;
|
|
|
|
+ struct dma_tx_state state;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ int count;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
|
|
|
|
+ count = dma->tx_bytes_requested - state.residue;
|
|
|
|
+ async_tx_ack(dma->tx_desc);
|
|
|
|
+
|
|
|
|
+ dma_sync_single_for_cpu(ourport->port.dev, dma->tx_transfer_addr,
|
|
|
|
+ dma->tx_size, DMA_TO_DEVICE);
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&port->lock, flags);
|
|
|
|
+
|
|
|
|
+ xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
|
|
|
|
+ port->icount.tx += count;
|
|
|
|
+ ourport->tx_in_progress = 0;
|
|
|
|
+
|
|
|
|
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
|
|
+ uart_write_wakeup(port);
|
|
|
|
+
|
|
|
|
+ s3c24xx_serial_start_next_tx(ourport);
|
|
|
|
+ spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void enable_tx_dma(struct s3c24xx_uart_port *ourport)
|
|
|
|
+{
|
|
|
|
+ struct uart_port *port = &ourport->port;
|
|
|
|
+ u32 ucon;
|
|
|
|
+
|
|
|
|
+ /* Mask Tx interrupt */
|
|
|
|
+ if (s3c24xx_serial_has_interrupt_mask(port))
|
|
|
|
+ __set_bit(S3C64XX_UINTM_TXD,
|
|
|
|
+ portaddrl(port, S3C64XX_UINTM));
|
|
|
|
+ else
|
|
|
|
+ disable_irq_nosync(ourport->tx_irq);
|
|
|
|
+
|
|
|
|
+ /* Enable tx dma mode */
|
|
|
|
+ ucon = rd_regl(port, S3C2410_UCON);
|
|
|
|
+ ucon &= ~(S3C64XX_UCON_TXBURST_MASK | S3C64XX_UCON_TXMODE_MASK);
|
|
|
|
+ ucon |= (dma_get_cache_alignment() >= 16) ?
|
|
|
|
+ S3C64XX_UCON_TXBURST_16 : S3C64XX_UCON_TXBURST_1;
|
|
|
|
+ ucon |= S3C64XX_UCON_TXMODE_DMA;
|
|
|
|
+ wr_regl(port, S3C2410_UCON, ucon);
|
|
|
|
+
|
|
|
|
+ ourport->tx_mode = S3C24XX_TX_DMA;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void enable_tx_pio(struct s3c24xx_uart_port *ourport)
|
|
|
|
+{
|
|
|
|
+ struct uart_port *port = &ourport->port;
|
|
|
|
+ u32 ucon, ufcon;
|
|
|
|
+
|
|
|
|
+ /* Set ufcon txtrig */
|
|
|
|
+ ourport->tx_in_progress = S3C24XX_TX_PIO;
|
|
|
|
+ ufcon = rd_regl(port, S3C2410_UFCON);
|
|
|
|
+ wr_regl(port, S3C2410_UFCON, ufcon);
|
|
|
|
+
|
|
|
|
+ /* Enable tx pio mode */
|
|
|
|
+ ucon = rd_regl(port, S3C2410_UCON);
|
|
|
|
+ ucon &= ~(S3C64XX_UCON_TXMODE_MASK);
|
|
|
|
+ ucon |= S3C64XX_UCON_TXMODE_CPU;
|
|
|
|
+ wr_regl(port, S3C2410_UCON, ucon);
|
|
|
|
+
|
|
|
|
+ /* Unmask Tx interrupt */
|
|
|
|
+ if (s3c24xx_serial_has_interrupt_mask(port))
|
|
|
|
+ __clear_bit(S3C64XX_UINTM_TXD,
|
|
|
|
+ portaddrl(port, S3C64XX_UINTM));
|
|
|
|
+ else
|
|
|
|
+ enable_irq(ourport->tx_irq);
|
|
|
|
+
|
|
|
|
+ ourport->tx_mode = S3C24XX_TX_PIO;
|
|
}
|
|
}
|
|
|
|
|
|
-static void s3c24xx_serial_start_tx(struct uart_port *port)
|
|
|
|
|
|
+static void s3c24xx_serial_start_tx_pio(struct s3c24xx_uart_port *ourport)
|
|
|
|
+{
|
|
|
|
+ if (ourport->tx_mode != S3C24XX_TX_PIO)
|
|
|
|
+ enable_tx_pio(ourport);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int s3c24xx_serial_start_tx_dma(struct s3c24xx_uart_port *ourport,
|
|
|
|
+ unsigned int count)
|
|
|
|
+{
|
|
|
|
+ struct uart_port *port = &ourport->port;
|
|
|
|
+ struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
+ struct s3c24xx_uart_dma *dma = ourport->dma;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (ourport->tx_mode != S3C24XX_TX_DMA)
|
|
|
|
+ enable_tx_dma(ourport);
|
|
|
|
+
|
|
|
|
+ while (xmit->tail & (dma_get_cache_alignment() - 1)) {
|
|
|
|
+ if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
|
|
|
|
+ return 0;
|
|
|
|
+ wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
|
|
|
|
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
|
|
|
+ port->icount.tx++;
|
|
|
|
+ count--;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dma->tx_size = count & ~(dma_get_cache_alignment() - 1);
|
|
|
|
+ dma->tx_transfer_addr = dma->tx_addr + xmit->tail;
|
|
|
|
+
|
|
|
|
+ dma_sync_single_for_device(ourport->port.dev, dma->tx_transfer_addr,
|
|
|
|
+ dma->tx_size, DMA_TO_DEVICE);
|
|
|
|
+
|
|
|
|
+ dma->tx_desc = dmaengine_prep_slave_single(dma->tx_chan,
|
|
|
|
+ dma->tx_transfer_addr, dma->tx_size,
|
|
|
|
+ DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
|
|
|
|
+ if (!dma->tx_desc) {
|
|
|
|
+ dev_err(ourport->port.dev, "Unable to get desc for Tx\n");
|
|
|
|
+ return -EIO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dma->tx_desc->callback = s3c24xx_serial_tx_dma_complete;
|
|
|
|
+ dma->tx_desc->callback_param = ourport;
|
|
|
|
+ dma->tx_bytes_requested = dma->tx_size;
|
|
|
|
+
|
|
|
|
+ ourport->tx_in_progress = S3C24XX_TX_DMA;
|
|
|
|
+ dma->tx_cookie = dmaengine_submit(dma->tx_desc);
|
|
|
|
+ dma_async_issue_pending(dma->tx_chan);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport)
|
|
|
|
+{
|
|
|
|
+ struct uart_port *port = &ourport->port;
|
|
|
|
+ struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
+ unsigned long count;
|
|
|
|
+
|
|
|
|
+ /* Get data size up to the end of buffer */
|
|
|
|
+ count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
|
|
|
|
+
|
|
|
|
+ if (!count) {
|
|
|
|
+ s3c24xx_serial_stop_tx(port);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!ourport->dma || !ourport->dma->tx_chan || count < port->fifosize)
|
|
|
|
+ s3c24xx_serial_start_tx_pio(ourport);
|
|
|
|
+ else
|
|
|
|
+ s3c24xx_serial_start_tx_dma(ourport, count);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void s3c24xx_serial_start_tx(struct uart_port *port)
|
|
{
|
|
{
|
|
struct s3c24xx_uart_port *ourport = to_ourport(port);
|
|
struct s3c24xx_uart_port *ourport = to_ourport(port);
|
|
|
|
+ struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
|
|
if (!tx_enabled(port)) {
|
|
if (!tx_enabled(port)) {
|
|
if (port->flags & UPF_CONS_FLOW)
|
|
if (port->flags & UPF_CONS_FLOW)
|
|
s3c24xx_serial_rx_disable(port);
|
|
s3c24xx_serial_rx_disable(port);
|
|
|
|
|
|
- if (s3c24xx_serial_has_interrupt_mask(port))
|
|
|
|
- __clear_bit(S3C64XX_UINTM_TXD,
|
|
|
|
- portaddrl(port, S3C64XX_UINTM));
|
|
|
|
- else
|
|
|
|
- enable_irq(ourport->tx_irq);
|
|
|
|
tx_enabled(port) = 1;
|
|
tx_enabled(port) = 1;
|
|
|
|
+ if (!ourport->dma || !ourport->dma->tx_chan) {
|
|
|
|
+ if (s3c24xx_serial_has_interrupt_mask(port))
|
|
|
|
+ __clear_bit(S3C64XX_UINTM_TXD,
|
|
|
|
+ portaddrl(port, S3C64XX_UINTM));
|
|
|
|
+ else
|
|
|
|
+ enable_irq(ourport->tx_irq);
|
|
|
|
+
|
|
|
|
+ s3c24xx_serial_start_tx_pio(ourport);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (ourport->dma && ourport->dma->tx_chan) {
|
|
|
|
+ if (!uart_circ_empty(xmit) && !ourport->tx_in_progress)
|
|
|
|
+ s3c24xx_serial_start_next_tx(ourport);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -333,10 +519,17 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
|
|
struct uart_port *port = &ourport->port;
|
|
struct uart_port *port = &ourport->port;
|
|
struct circ_buf *xmit = &port->state->xmit;
|
|
struct circ_buf *xmit = &port->state->xmit;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
- int count = port->fifosize;
|
|
|
|
|
|
+ int count;
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
|
|
|
|
|
|
+ count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
|
|
|
|
+
|
|
|
|
+ if (ourport->dma && ourport->dma->tx_chan && count >= port->fifosize) {
|
|
|
|
+ s3c24xx_serial_start_tx_dma(ourport, count);
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
if (port->x_char) {
|
|
if (port->x_char) {
|
|
wr_regb(port, S3C2410_UTXH, port->x_char);
|
|
wr_regb(port, S3C2410_UTXH, port->x_char);
|
|
port->icount.tx++;
|
|
port->icount.tx++;
|
|
@@ -355,6 +548,7 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
|
|
|
|
|
|
/* try and drain the buffer... */
|
|
/* try and drain the buffer... */
|
|
|
|
|
|
|
|
+ count = port->fifosize;
|
|
while (!uart_circ_empty(xmit) && count-- > 0) {
|
|
while (!uart_circ_empty(xmit) && count-- > 0) {
|
|
if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
|
|
if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
|
|
break;
|
|
break;
|
|
@@ -572,6 +766,7 @@ static void s3c24xx_serial_shutdown(struct uart_port *port)
|
|
if (ourport->dma)
|
|
if (ourport->dma)
|
|
s3c24xx_serial_release_dma(ourport);
|
|
s3c24xx_serial_release_dma(ourport);
|
|
|
|
|
|
|
|
+ ourport->tx_in_progress = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int s3c24xx_serial_startup(struct uart_port *port)
|
|
static int s3c24xx_serial_startup(struct uart_port *port)
|
|
@@ -650,8 +845,19 @@ static int s3c64xx_serial_startup(struct uart_port *port)
|
|
tx_enabled(port) = 0;
|
|
tx_enabled(port) = 0;
|
|
ourport->tx_claimed = 1;
|
|
ourport->tx_claimed = 1;
|
|
|
|
|
|
|
|
+ spin_lock_irqsave(&port->lock, flags);
|
|
|
|
+
|
|
|
|
+ ufcon = rd_regl(port, S3C2410_UFCON);
|
|
|
|
+ ufcon |= S3C2410_UFCON_RESETRX | S3C2410_UFCON_RESETTX;
|
|
|
|
+ wr_regl(port, S3C2410_UFCON, ufcon);
|
|
|
|
+
|
|
|
|
+ enable_rx_pio(ourport);
|
|
|
|
+
|
|
|
|
+ spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
+
|
|
/* Enable Rx Interrupt */
|
|
/* Enable Rx Interrupt */
|
|
__clear_bit(S3C64XX_UINTM_RXD, portaddrl(port, S3C64XX_UINTM));
|
|
__clear_bit(S3C64XX_UINTM_RXD, portaddrl(port, S3C64XX_UINTM));
|
|
|
|
+
|
|
dbg("s3c64xx_serial_startup ok\n");
|
|
dbg("s3c64xx_serial_startup ok\n");
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|