• 易迪拓培训,专注于微波、射频、天线设计工程师的培养
首页 > 无线通信 > 技术讨论 > CC2640 uart dma实现方式分析教程

CC2640 uart dma实现方式分析教程

录入:edatop.com     点击:

CC2640 uart dma实现方式分析教程


CC2640在做uart DMA驱动期间,前后加起来也有1个月左右的时间,总的来说比较全面的了解了uart,DMA的工作原理。
在调试中,遇到了最大问题就是关于DMA操作这快的不熟悉,导致浪费了很多的时间和精力。对UART,DMA的工作原理可以看LDD3,或者设备驱动一书中也有
详细介绍,uart-dma驱动的移植可以参好drivers/serial/bfin_5xx.c
     UART-DMA总体思路如下:
1.本UART-DMA采用的是,DMA+POLLING(轮询)的方式,其中轮询采用的是定时器。
2.在驱动中发送DMA需要用户层主动发起;
    接收DMA:在UART open操作时enable_dma(rx),等待接收数据,当到达DMA counter值时,产生DMA中断,
                  在DMA中断处理中 做如下处理 disable_dma(rx),CPU取走数据,enable_dma(rx),以便接收下次数据;
3.POLLING(轮询)在这里的作用,如果在接收DMA过程中,还没有达到DMA counter值时,不会产生中断,这样接收到的数据
  就不能及时的被CPU取走,为了解决这个问题,采用轮询--定时器方式,通过每50ms 读取DMA counter寄存器一次看是否有数据上的
  变化,如果有这CPU把数据取走,没有则不做处理。
4.错误处理:如果有错误(溢出、校验……),产生错误中断,在错误处理中断中有相应的处理。
      现分析如下:
1.内核起来之后,最开始经过  setup_arch() /* init/main.c */-->arch_mem_init()-->plat_mem_setup()-->clx_serial_setup()
在clx_serial_setup()函数中
void __init clx_serial_setup(void)
{
*******************
    REG8(UART0_FCR) |= UARTFCR_UUE;   //设置uart0的 FIFO 控制寄存器,disable UART
    REG8(UART1_FCR) |= UARTFCR_UUE;   //设置uart1的 FIFO 控制寄存器,disable UART
    s.type = PORT_16550A;        //设置uart的其它属性
    s.iotype = UPIO_MEM;
    s.regshift = 2;
    s.fifosize = 1;
    s.uartclk= clx_clocks.uartclk;
    s.flags = STD_COM_FLAGS;
#if !defined(CONFIG_CLX_UART0_REMR)
    s.line = line;
    s.irq = IRQ_UART0;
    s.membase = (unsigned char __iomem *)UART0_BASE;
    if (early_serial_setup(&s) != 0)         //调用early_serial_setup()来初始化串口0
        printk(KERN_ERR "Serial ttyS0 setup failed!\n");
    line++;
#endif
#if !defined(CONFIG_CLX_UART1_REMR)
    s.line = line;
    s.line = 1;
    s.irq = IRQ_UART1;
    s.membase = (unsigned char __iomem *)UART1_BASE;
    if (early_serial_setup(&s) != 0)        //调用early_serial_setup()来初始化串口1
        printk(KERN_ERR "Serial ttyS1 setup failed!\n");
#endif
}
2.early_serial_setup()函数是在你自己写的驱动里边定义的
static struct uart_8250_port cq8401_serial_ports[UART_NR];
int __init early_serial_setup(struct uart_port *port)
{
    if (port->line >= ARRAY_SIZE(cq8401_serial_ports))
        return -ENODEV;
    cq8401_isa_init_ports();   //初始化相应的UART
    cq8401_serial_ports[port->line].port    = *port;  //将传过来的参数port,赋值给cq8401_serial_ports[port->line].port
    cq8401_serial_ports[port->line].port.ops    = &cq8401_serial8250_pops;
                            //同样,相应的OPS操作赋值
    return 0;
}
到这里,setup_arch()里面UART的初始化到这里就结束了。
3.接着是内核里面
console_init() /* init/main.c */ 的初始化
void __init console_init(void)
{
    initcall_t *call;
    /* Setup the default TTY line discipline. */
    (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
    /*
     * set up the console device so that later boot sequences can
     * inform about problems etc..
     */
    call = __con_initcall_start;
    while (call < __con_initcall_end) {
        (*call)();
        call++;
    }
}
关于console 初始化,在网上找了一段描述,就直接套用过来了。
网址:http://blog.csdn.net/lights_joy/archive/2009/01/31/3855530.aspx
************************************************
    在linux初始化过程中,除非启用了early console,否则直到console_init调用之前是没有任何输出的,它们的输出都放在__log_buf这个缓冲内的,在console_init调用时再将这个缓冲区内的数据一次性输出。
    console和串口的初始化操作应该是由__con_initcall_start到__con_initcall_end之间的函数调用来完成的
    在linux的头文件中搜索initcall,发现了这样一个定义(include\linux\init.h):
#define console_initcall(fn) \
     static initcall_t __initcall_##fn \
     __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
在此函数中的所有操作都是与硬件无关的,据此可以猜测,应该还有一个与硬件相关的文件,且在此文件中应该使用console_initcall这个宏。
    在内核源码中搜索console_initcall,会找到在clx_uart_dma.c中有  console_initcall(cq8401_console_init);这样一句话。
    关于具体console的初始化在这里不必多说,具体的可以看下别人的分析。
**************************************************
4. console初始化好了以后,后面就是UART驱动初始化的时候了
static int __init cq8401_serial8250_init(void)
{
    int ret, i;
**************
    cq8401_isa_init_ports();  //相应串口的初始化
    ret = uart_register_driver(&cq8401_serial_reg);  //串口驱动的注册
    if (ret)
            goto out;
cq8401_serial_init_ports();     //端口的添加,定义如下
**************
}
static void __exit cq8401_serial8250_exit(void)
{
        int i;
        struct uart_8250_port *up;
        for (i = 0; i < nr_uarts; i++)
        {
            up= &cq8401_serial_ports;
            uart_remove_one_port (&cq8401_serial_reg,&up->port );
        }
    uart_unregister_driver(&cq8401_serial_reg);
}
module_init(cq8401_serial8250_init);
module_exit(cq8401_serial8250_exit);
*******************************************
cq8401_serial_init_ports()函数分析
static void  cq8401_serial_init_ports(void)
{
    int  i;
    for (i = 0; i < nr_uarts; i++) {
        struct uart_8250_port *up = &cq8401_serial_ports;   //前面early_serial_setup()中以对cq8401_serial_ports[i]赋值
        up->port.line    = i;
        uart_add_one_port (&cq8401_serial_reg, &up->port);    //端口的添加
        }
}
    在这里uart驱动的初始化到这里就结束了,下面重点分析 uart对应的OPS操作。
5. UART OPS操作分析
static struct uart_ops cq8401_serial8250_pops = {
    .tx_empty   = cq8401_serial8250_tx_empty,
    .set_mctrl  = cq8401_serial8250_set_mctrl,
    .get_mctrl  = cq8401_serial8250_get_mctrl,
    .stop_tx    = cq8401_serial8250_stop_tx,
    .start_tx   = cq8401_serial8250_start_tx,
    .stop_rx    = cq8401_serial8250_stop_rx,
    .enable_ms  = cq8401_serial8250_enable_ms,
    .break_ctl  = cq8401_serial8250_break_ctl,
    .startup    = cq8401_serial8250_startup,
    .shutdown   = cq8401_serial8250_shutdown,
    .set_termios    = cq8401_serial8250_set_termios,
//  .pm     = cq8401_serial8250_pm,
    .type       = cq8401_serial8250_type,
    .release_port   = cq8401_serial8250_release_port,
    .request_port   = cq8401_serial8250_request_port,
    .config_port    = cq8401_serial8250_config_port,
    .verify_port    = cq8401_serial8250_verify_port,
};
  在分析之前,先看看uart_8250_port 结构体 的定义
struct uart_8250_port {
.............
#ifdef CONFIG_SERIAL_CQ8401_DMA
    struct tx_buffer      tx_buf;         
    int                     tx_done;
    int                     tx_count;
    wait_queue_head_t       tx_wait_queue;  //UART发送队列
    unsigned int        rx_dma_addr;   //UART接收DMA地址
//    struct circ_buf     rx_dma_buf;   
    struct timer_list       rx_dma_timer;  //接收定时器
    unsigned int            tx_dma_channel;  //发送DMA通道
    unsigned int            rx_dma_channel;  //接收DMA通道
    unsigned int            tx_channel;      //接收 DMA SOC ID  
    unsigned int            rx_channel;      //发送 DMA SOC ID
#endif

  5.1  .startup    = cq8401_serial8250_startup  //UART的OPEN操作
static int cq8401_serial8250_startup(struct uart_port *port)
{
    ...................
    if(up->port.type==PORT_16550A){
        printk("***************init cq8401  dma**************\n");
        int r;
        char *serial;
        init_waitqueue_head(&up->tx_wait_queue);
        serial_outp(up, UART_LCR, serial_in(up,UART_LCR) | UART_LCR_WLEN8); //8位数据位
        udelay (10);
        serial_outp(up,UART_FCR,0x0f);  //reset tx,rx fifo;enable dma; disable uart
        udelay (10);
        printk("irq=%d\n",up->port.irq);
        /*
         * register error handle interrupt
         */
        if(up->port.line)
             serial="serial1";
        else
             serial="serial0";
        //注册 UART 错误处理中断
        r = request_irq(up->port.irq,serialirqxx,SA_INTERRUPT, serial, up);
        printk("r=%d",r);
              if (r<0)
                       return r;
        serial_outp(up,UART_IER,0x14); //禁止接收、发送 中断,允许超时、错误处理中断
        udelay (10);
        if( serial_init_dma(up,up->port.line) > 1){    //serial_init_dma(),申请发送,接收DMA通道和 BUF
            return -EBUSY;
        }
   ......................
}
serial_init_dma(),申请发送,接收DMA通道和 BUF 如下:
参数:
up:对应串口结构体
line:串口号
static int serial_init_dma(struct uart_8250_port *up,unsigned int line)
{
    ......................
        /*
         * request transmit dma channel and dma buffer for the corresponding  uart
         */
        if(line){
            uart="uart1_tx";
        }
        else
            uart="uart0_tx";
        //片上SOC设备 DMA通道申请,  
      up->tx_channel:SOC 设备ID;
      uart:申请的DMA设备名;
      uart0_tx_dma_intr:DMA中断处理函数;
      SA_INTERRUPT:the irq handler flags
      up:the irq handler device id for shared irq
        r=clx_request_dma(up->tx_channel, uart , uart0_tx_dma_intr,
                           SA_INTERRUPT, up);
        if (r <0){
                printk("Unable to APPLY UART%d TX DMA channel\n",line);
                return -EBUSY;
                 }
        up->tx_dma_channel=r;
        up->tx_done        = 1;
        up->tx_count       = 0;
        /*
         *request receive dma channel and dma buffer for the corresponding  uart
         */
        if(line)
            uart="uart1_rx";
        else
            uart="uart0_rx";
    //接收DMA通道申请   
        r = clx_request_dma(up->rx_channel, uart , uart0_rx_dma_intr,
                   SA_INTERRUPT, up);
        if (r <0){
                printk("Unable to APPLY UART%d RX DMA channel\n",line);
                return -EBUSY;
                }
        up->rx_dma_channel=r;
        up->rx_dma_buf.head = 0;
        up->rx_dma_buf.tail = 0;
      //接收DMA  BUF 申请,这里需要说明一下,在DMA操作中,是操作的总线地址,而且为了解决 DMA 和 cache的一致性问题;
所以最好用 dma_alloc_coherent()函数
    void *dma_alloc_coherent(struct device *dev, size_t size,
    dma_addr_t * dma_handle, gfp_t gfp)
    dev:设备
        size:申请的大小
        dma_handle:申请后返回的总线地址,这里即是&up->rx_dma_addr
        gfp:申请内存的标识(GFP_KERNEL,GFP_DMA)
    该函数返回供驱动操作的虚拟地址,这里即是up->rx_dma_buf.buf
当然也可以采用其它方式申请DMA BUF,如:
     kmalloc(),get_free_pages();申请虚拟地址;
    用virt_to_bus();将上面申请到的虚拟地址转化成总线地址。但是强调一点,如果用这种方式,在DMA操作开始之后,如果CPU在还没有到达
    DMA COUNTER值时就取数据的话,必须在每次CPU取数据之前做dma_cache_wback_inv()操作,来避免 cache中有 DMA BUF 的数据备份,
    也就是CPU每次取数据必须从 DMA BUF中取。
        up->rx_dma_buf.buf=(unsigned char *)dma_alloc_coherent(NULL, PAGE_SIZE, &up->rx_dma_addr, GFP_DMA);;
        up->rx_dma_addr -=CLXSOC_PCI_CORE_START;   //这里
        /*
         * enable receive dma channel
         */
        spin_unlock(&up->port.lock);
    //设置 DMA 相关寄存器;enable_dma(rx),enable 接收DMA通道,准备接收数据,分析如下
        uart_start_dma(up->rx_dma_channel,up->rx_dma_addr,DMA_RX_XCOUNT,DMA_MODE_READ);
        serial_outp(up,UART_FCR,0x19);
        udelay (10);
        printk("enable rx dma\n");
        return 1;
}
设置 DMA 相关寄存器, enable发送,接收DMA通道函数分析
参数:
chan:通道号
phyaddr:DMA BUF的总线地址
count:DMA COUNTER寄存器的值
mode:根据读,写的不同,来设置DMA DCCR寄存器的相关值,比如说:SAI,DAI,DRTR等值
static void uart_start_dma(int chan, unsigned long phyaddr,unsigned int count, int mode)
{
    unsigned int flags;
    if (count == 0) {
        count++;
        printk(KERN_DEBUG "%s: CLXSOC DMA controller can't set dma count zero!\n",
                __FUNCTION__);
    return;
    }
    flags = claim_dma_lock();
    disable_dma(chan);      //先disable_dma
    clear_dma_ff(chan);        
    set_dma_addr(chan, phyaddr);  //设置DMA 源地址,目的地址;phyaddr:源地址,目的地址前面 clx_request_dma,中已经给结构体赋值,这里只需要赋值即可
    set_dma_count(chan, count);  //DCCR,即DMA COUNTER值的设置
    set_dma_mode(chan, mode);    //根据DMA 接收,发送的不同来设置DCCR.SAI,DCCR.DAI位
    enable_dma(chan); //enable DMA
    release_dma_lock(flags);
}
   根据各个芯片的DMA控制器的不同,详细的可参考DATASHEET。
5.2 .start_tx   = cq8401_serial8250_start_tx  //UART 发送操作
发送操作流程如下:
    用户在APP层,主动发起一次数据到外部设备的传输,先open UART,然后 write data 到 uart;这是write操作最终会调用到驱动的
start_tx操作,在UART-DMA驱动中,将用户保存在circ_buf中的数据 通过 DMA到 UART 发送寄存器中(FIFO 模式下);完成一次DMA操作,
这是DMA 产生 DMA中断,通知CPU数据已发送出去,CPU根据circ_buf的情况做相应的处理
static void cq8401_serial8250_start_tx(struct uart_port *port)
{
    struct uart_8250_port *up = (struct uart_8250_port *)port;
    serial_dma_tx_chars(up);
}   
static void serial_dma_tx_chars(struct uart_8250_port *uart)
{
        struct circ_buf *xmit = &uart->port.info->xmit;
        int flags = 0;
        if (!uart->tx_done)
               return;
        uart->tx_done = 0;
#ifdef CONFIG_SERIAL_CQ8401_CTSRTS
        check_modem_status(uart); // 如果串口有RTS,CTS流控线的话,应在最开始做流控的相应操作,这里我们不做具体介绍
#endif
     if (uart->port.x_char) {
                serial_outp(uart, UART_TX, uart->port.x_char);
                uart->port.icount.tx++;
                uart->port.x_char = 0;
                uart->tx_done = 1;
                return;
     }
    if (uart_circ_empty(xmit) || uart_tx_stopped(&uart->port)) {
                cq8401_serial8250_stop_tx(&uart->port);
                uart->tx_done = 1;
                return;
        }
    spin_lock_irqsave(&uart->port.lock, flags);
    uart->tx_count = CIRC_CNT(xmit->head, xmit->tail, UART_XMIT_SIZE);  //circ_buf中现在有多少数据就发送多少数据,好像circ_buf只有1K把,这个不是很清楚
    if (uart->tx_count > (UART_XMIT_SIZE - xmit->tail))
                uart->tx_count = UART_XMIT_SIZE - xmit->tail;
    serial_outp(uart,UART_FCR,0x19);  //enable dma and uart
    udelay (10);
      // 清空 DCache 数据缓存的数据
    dma_cache_wback_inv((unsigned int)(xmit->buf+xmit->tail),uart->tx_count);
      //发送DMA寄存器设置并 enable_dma(tx)
    uart_start_dma(uart->tx_dma_channel,virt_to_phys(xmit->buf+xmit->tail), uart->tx_count,DMA_MODE_WRITE);
     interruptible_sleep_on(&uart->tx_wait_queue);//发送队列睡眠
         spin_unlock_irqrestore(&uart->port.lock, flags);
}
完成一次DMA操作后产生,会产生发送DMA中断,如下:
static irqreturn_t uart0_tx_dma_intr(int irq, void *dev_id)
{
    struct uart_8250_port *uart = dev_id;
    struct circ_buf *xmit = &uart->port.info->xmit;
    spin_lock(&uart->port.lock);
    disable_dma(uart->tx_dma_channel);  //disbale_dma(tx)
    if(__dmac_channel_transmit_end_detected(uart->tx_dma_channel)){  //判断DCCR.CT是否等于1,1:DMA 操作结束,0:DMA 操作没结束
                xmit->tail = (xmit->tail+uart->tx_count) &(UART_XMIT_SIZE -1);
                uart->port.icount.tx+=uart->tx_count;
                if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)  //如果circ_buf数据小于WAKEUP_CHARS,唤醒上层向circ_buf写数据
                        uart_write_wakeup(&uart->port);
                if (uart_circ_empty(xmit))                         //circ_buf为空,停止发送
                        cq8401_serial8250_stop_tx(&uart->port);
        uart->tx_done = 1;
        __dmac_channel_clear_transmit_end(uart->tx_dma_channel);  //dccr.ct=0,以便下此DMA操作,具体看DATASHEET
        wake_up_interruptible(&uart->tx_wait_queue); //唤醒发送队列
        }
        spin_unlock(&uart->port.lock);
        return IRQ_HANDLED;
}
    自此发送过程以分析完成。
  5.3 接收流程
    由于在UART的操作中,接收都是被动的,所以在UART OPEN的时候,就 enable_dma(rx),只要UART 接收寄存器(FIFO 模式下)
中有数据就会 通过 DMA 到 RX DMA BUF中,为了CPU能及时取走数据,才用定时器操作,每50ms 定时器去读取 DMA COUNTER寄存器,
看是否有数据接收到,如果有则 CPU 取走数据;当传输的数据到达 DAM COUNTER值时,DMA中断产生,CPU 接管工作。
定时器函数如下:
void serial_rx_dma_timeout(struct uart_8250_port *uart)
{
    unsigned  int x_pos,pos=0;
    int flags=0;
    spin_lock_irqsave(&uart->port.lock, flags);
//读取 DMA COUNTER寄存器看时候有数据过来
    x_pos =DMA_RX_XCOUNT - get_dma_residue(uart->rx_dma_channel);
        if (x_pos == DMA_RX_XCOUNT)
                x_pos = 0;
    pos = x_pos;
    if ( pos > uart->rx_dma_buf.tail) {  //如果有数据过来,CPU 取走数据
            uart->rx_dma_buf.head = pos;
            serial_dma_rx_chars(uart);  //CPU 取走数据
            uart->rx_dma_buf.tail = uart->rx_dma_buf.head;
        }
    spin_unlock_irqrestore(&uart->port.lock, flags);
    mod_timer(&(uart->rx_dma_timer), jiffies + DMA_RX_FLUSH_JIFFIES);
}
CPU 取走数据
static void serial_dma_rx_chars(struct uart_8250_port *uart)
{
............
        //dma_cache_wback_inv((unsigned int)uart->rx_dma_buf.buf, DMA_RX_XCOUNT);  //这就是前面强调的 用kmalloc(),get_free_pages()申请的DMA BUF在每次 CPU 取数据的时候要清空 Dcache,使cpu从RAM中取数据而不是从cache 中
        uart->port.icount.rx +=CIRC_CNT(uart->rx_dma_buf.head, uart->rx_dma_buf.tail, UART_XMIT_SIZE);
        if (status & UART_LSR_BI) {
                uart->port.icount.brk++;
                if (uart_handle_break(&uart->port))
                        goto dma_ignore_char;
                status &= ~(UART_LSR_PE | UART_LSR_FE);
        }
        if (status & UART_LSR_PE)
                uart->port.icount.parity++;
        if (status & UART_LSR_OE)
                uart->port.icount.overrun++;
        if (status & UART_LSR_FE)
                uart->port.icount.frame++;
        status &= uart->port.read_status_mask;
        if (status & UART_LSR_BI)
                flg = TTY_BREAK;
       else if (status & UART_LSR_PE)
                flg = TTY_PARITY;
        else if (status & UART_LSR_FE)
                flg = TTY_FRAME;
        else
                flg = TTY_NORMAL;
    //从DMA BUF中取走数据,insert到flip_buf中
        for (i = uart->rx_dma_buf.tail; i<uart->rx_dma_buf.head;i++) {
                if (uart_handle_sysrq_char(&uart->port, uart->rx_dma_buf.buf))
                    goto dma_ignore_char;
                uart_insert_char(&uart->port, status, UART_LSR_OE, uart->rx_dma_buf.buf, flg);
        }
dma_ignore_char:
        tty_flip_buffer_push(tty);  //将flip_buf中的数据PUSH 到TTY 线路规程
}
接收DMA中断
static irqreturn_t uart0_rx_dma_intr(int irq, void *dev_id)
{
    struct uart_8250_port *uart = dev_id;
    disable_dma(uart->rx_dma_channel);  //disable_dma(rx)
    spin_lock(&uart->port.lock);
if(__dmac_channel_transmit_end_detected(uart->rx_dma_channel)){
    uart->rx_dma_buf.head = DMA_RX_XCOUNT;
    serial_dma_rx_chars(uart);   //CPU将上次tail到 DMA COUNTER值之间的数据取走
    uart->rx_dma_buf.tail = uart->rx_dma_buf.head=0;
    memset(uart->rx_dma_buf.buf, 0x00, DMA_RX_XCOUNT); //clear DMA BUF  
    dma_cache_wback_inv((unsigned int)uart->rx_dma_buf.buf, DMA_RX_XCOUNT);
    __dmac_channel_clear_transmit_end(uart->rx_dma_channel);
    //enable_dma(rx),以便下次数据的接收
    uart_start_dma(uart->rx_dma_channel,uart->rx_dma_addr,DMA_RX_XCOUNT,DMA_MODE_READ);
    }
    spin_unlock(&uart->port.lock);
    mod_timer(&(uart->rx_dma_timer), jiffies);
    return IRQ_HANDLED;
}
    自此接收DMA过程分析完毕
5.4  .shutdown   = cq8401_serial8250_shutdown  //串口的关闭
    主要做的就是释放DMA通道,DMA BUF,中断;删除定时器,
static void cq8401_serial8250_shutdown(struct uart_port *port)
{
    struct uart_8250_port *up = (struct uart_8250_port *)port;
    disable_dma(up->tx_dma_channel);
    clx_free_dma(up->tx_dma_channel);
    disable_dma(up->rx_dma_channel);
    clx_free_dma(up->rx_dma_channel);
    dma_free_coherent(NULL,DMA_RX_XCOUNT, up->rx_dma_buf.buf,up->rx_dma_addr);
    del_timer(&(up->rx_dma_timer));
    free_irq(up->port.irq, up);
}

提供CC2541 CC2640R2 CC1310等CC系列免费样片 Q.Q 122982582

上一篇:CC2640去采集lmt70芯片的温度时,程序不能运行起来?
下一篇:CC2640 广播轮训功能进不去,什么原因?

手机天线设计培训教程详情>>

手机天线设计培训教程 国内最全面、系统、专业的手机天线设计培训课程,没有之一;是您学习手机天线设计的最佳选择...【More..

射频和天线工程师培训课程详情>>

  网站地图