if (up->dma_enable) {
uart_pxa_dma_init(up);
up->rx_stop = 0;
pxa_uart_receive_dma_start(up);
up->ier = UART_IER_DMAE | UART_IER_UUE | UART_IER_RTOIE;
tasklet_init(&up->tklet, uart_task_action, (unsigned long)up);
}
static void uart_pxa_dma_init(struct uart_pxa_port *up)
{
if (0 == up->rxdma) {
up->rxdma = pxa_request_dma(up->name, DMA_PRIO_LOW,
pxa_uart_receive_dma, up);
if (up->rxdma < 0) // 分配 rx channel 号
goto out;
}
if (0 == up->txdma) {
up->txdma = pxa_request_dma(up->name, DMA_PRIO_LOW,
pxa_uart_transmit_dma, up); // 分配 tx channel 号
if (up->txdma < 0)
goto err_txdma;
}
if (NULL == up->txdma_addr) {
up->txdma_addr = dma_alloc_coherent(NULL, DMA_BLOCK,
&up->txdma_addr_phys, GFP_KERNEL); // dma分配 得到虚拟地址和物理地址
if (!up->txdma_addr)
goto txdma_err_alloc;
}
if (NULL == up->rxdma_addr) {
up->rxdma_addr = dma_alloc_coherent(NULL, DMA_BLOCK,
&up->rxdma_addr_phys, GFP_KERNEL);
if (!up->rxdma_addr)
goto rxdma_err_alloc;
}
#ifdef CONFIG_PM
up->buf_save = kmalloc(DMA_BLOCK, GFP_KERNEL);
if (!up->buf_save)
goto buf_err_alloc;
#endif
writel(up->rxdma | DRCMR_MAPVLD, up->rxdrcmr); // 告诉dma channel已经映射
writel(up->txdma | DRCMR_MAPVLD, up->txdrcmr);
return;
#ifdef CONFIG_PM
buf_err_alloc:
dma_free_coherent(NULL, DMA_BLOCK, up->rxdma_addr,
up->rxdma_addr_phys);
up->rxdma_addr = NULL;
#endif
rxdma_err_alloc:
dma_free_coherent(NULL, DMA_BLOCK, up->txdma_addr,
up->txdma_addr_phys);
up->txdma_addr = NULL;
txdma_err_alloc:
pxa_free_dma(up->txdma);
up->txdma = 0;
err_txdma:
pxa_free_dma(up->rxdma);
up->rxdma = 0;
out:
return;
}
下面到pxa_uart_receive_dma_start这个函数:
static void pxa_uart_receive_dma_start(struct uart_pxa_port *up)
{
DCSR(up->rxdma) = DCSR_NODESC; // 采用没有描述符的方式
DSADR(up->rxdma) = up->port.mapbase; // source
DTADR(up->rxdma) = up->rxdma_addr_phys; // target
DCMD(up->rxdma) = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_ENDIRQEN |
DCMD_WIDTH1 | DCMD_BURST16 | DMA_BLOCK;
DCSR(up->rxdma) |= DCSR_RUN;
}
DMA Command Registers (DMA_CMDX)这个寄存器设置为: target addr 自动加1, 流控根据source, 当len为0时产生中断, 宽度为1byte, burst为16bytes, 块为4096
最后一个DCSR就是让dma run起来了,寄存器为DMA Channel Control/Status Registers, up-> rxdma 传递了通道号,通道号在plat-pxa/dma.c文件中pxa_request_dma申请,实际上就对应到了具体的0-15的某个寄存器,下面dma就run起来了。
然后开始初始化tasklet,来在中断上下文中断触发后马上执行uart_task_action函数。
然后继续写uart寄存器使能uart dma,使能uart,使能接受超时中断,然后在后面会再次清除中断相关寄存器,这样serial_pxa_startup就完成了。
下面我们重点看看uart_task_action函数,此函数最后会调用pxa_uart_transmit_dma_start这个函数,此函数的实现如下:
static void pxa_uart_transmit_dma_start(struct uart_pxa_port *up, int count)
{
unsigned long flags;
if (!(DCSR(up->txdma) & DCSR_STOPSTATE))
return;
DCSR(up->txdma) = DCSR_NODESC;
DSADR(up->txdma) = up->txdma_addr_phys;
DTADR(up->txdma) = up->port.mapbase;
DCMD(up->txdma) = DCMD_INCSRCADDR | DCMD_FLOWTRG | DCMD_ENDIRQEN |
DCMD_WIDTH1 | DCMD_BURST16 | count;
local_irq_save(flags);
#ifdef CONFIG_PXA95x
dvfm_disable_lowpower(up->dvfm_dev_idx[PXA_UART_TX]);
#elif defined(CONFIG_WAKELOCK) && defined(CONFIG_CPU_PXA910)
wake_lock(&up->idle_lock[PXA_UART_TX]);
#else
pm_qos_update_request(&up->qos_idle[PXA_UART_TX],
PM_QOS_CONSTRAINT);
#endif
DCSR(up->txdma) |= DCSR_RUN;
local_irq_restore(flags);
}
这个地方主要的不同就是source和target不同,DCMD这块是inc src addr和target流控,其他设置是一样的。这里要注意的是流控始终都是uart本身作为流控,从而避免uart慢引起的问题。
下面我们再看看serial_pxa_start_tx函数,这个函数同样也是判断了dma是否使能了,如果使能了将使用dma的方式,同样也是调用了tasklet_schedule函数来触发uart_task_action此函数的调用,从而再调用pxa_uart_transmit_dma_start这个函数。
static void serial_pxa_start_tx(struct uart_port *port)
{
struct uart_pxa_port *up = (struct uart_pxa_port *)port;
if (up->dma_enable) {
up->tx_stop = 0;
tasklet_schedule(&up->tklet);
} else {
if (!(up->ier & UART_IER_THRI)) {
up->ier |= UART_IER_THRI;
serial_out(up, UART_IER, up->ier);
}
}
}
uart_task_action这个函数的实现如下:
static void uart_task_action(unsigned long data)
{
struct uart_pxa_port *up = (struct uart_pxa_port *)data;
struct circ_buf *xmit = &up->port.state->xmit;
unsigned char *tmp = up->txdma_addr;
unsigned long flags;
int count = 0, c;
/* if the tx is stop, just return.*/
if (up->tx_stop)
return;
if ((DCSR(up->txdma) & DCSR_RUN))
return;
spin_lock_irqsave(&up->port.lock, flags);
while (1) {
c = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
if (c <= 0)
break;
memcpy(tmp, xmit->buf + xmit->tail, c);
xmit->tail = (xmit->tail + c) & (UART_XMIT_SIZE - 1);
tmp += c;
count += c;
up->port.icount.tx += c;
}
spin_unlock_irqrestore(&up->port.lock, flags);
tmp = up->txdma_addr;
up->tx_stop = 0;
pr_debug("count =%d", count);
pxa_uart_transmit_dma_start(up, count);
}
此函数的重要作用就是把buf填充到DMA所分配的虚拟地址up->txdma_addr中,最大为4096bytes,当while(1)把字节都加进来后,然后就开始dma操作了。而在dma中我们将不是使用up->txdma_addr这个虚拟地址,而直接使用物理地址up->txdma_addr_phys来作为源地址,然后让这个地址自动+1,从而来发送这块区域的buf,直到count到0后再停止。