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; }
最后一个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); }
下面我们再看看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); }