ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据

一.目标
在米尔科技的z-turn 开发板上实现PL数据流送往PS。
二.流程分析
由于单个DMA每次只能发送一定量的数据,但对于数据源来说数据时源源不断产生的,所以在单个DMA单次发送完成至下一次传输期间,这段时间的数据流失了,所以采用两个DMA实现循环发送数据,防止数据丢失。
自定义一个IP核用于产生源源不断的测试数据模拟数据源,再自定义一个IP用于切换DMA发送数据。
系统框图如下:
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第1张图片
通过axi-gpio启动数据源产生数据,数据流再通过DMA切换模块分批次将数据送往DMA。每个DMA发送10万个数据便进行切换,所以数据源采用从1开始累加产生数据,每次在DMA中断中验证最后一个数据是否是10万,20万,30万等等。
三.代码
①数据源模块data_product.v

module data_product(
clk,
rst,
enable,
data_out,
data_enable,
cnt
);
input clk;
input rst;
input enable;
output [31:0]data_out;
output data_enable;
output[3:0]cnt;
reg [31:0]data_out;
reg data_enable;
reg[3:0]cnt;
always@(posedge clk or negedge rst)
begin
    if(!rst)
    begin
        data_out <= 0;
        data_enable <= 0;
        cnt <= 0;
    end
    else
    begin
        if(enable)
        begin
            if(cnt>=13)cnt <= 0;
            else cnt <= cnt + 1;
            if(cnt>=1&&cnt<=8)
            begin
                data_out <= data_out + 1;
                data_enable <= 1;
            end
            else
            begin
                data_out <= data_out;
                data_enable <= 0;
            end
        end
        else
        begin
            data_enable <= 0;
            data_out   <= data_out;
            cnt <= cnt;
        end
    end
end
endmodule

②DMA切换模块:selet_dma.v

module selet_dma(
 clk,
 rst,
 data_valid,
 data_in,
 //dma1
 dma1_tvalid,
 dma1_tkeep,
 dma1_tlast,
 dma1_tready,
 dma1_tdata,
 //dma2
 dma2_tvalid,
 dma2_tkeep,
 dma2_tlast,
 dma2_tready,
 dma2_tdata,
 //
 count_dma1,
 count_dma2
);
input clk;
input rst;
input data_valid;
input [31:0]data_in;
//dma1
output dma1_tvalid;
output [3:0]dma1_tkeep;
output dma1_tlast;
output [31:0]dma1_tdata;
input dma1_tready;
//dma2
output dma2_tvalid;
output [3:0]dma2_tkeep;
output dma2_tlast;
output [31:0]dma2_tdata;
input dma2_tready;
//count data
output[19:0]count_dma1;//数据输出个数
output[19:0]count_dma2;
//
reg dma1_tvalid;
reg [3:0]dma1_tkeep;
reg dma1_tlast;
reg [31:0]dma1_tdata;
//
reg dma2_tvalid;
reg [3:0]dma2_tkeep;
reg dma2_tlast;
reg [31:0]dma2_tdata;
//
reg[19:0]count_dma1;//数据输出个数
reg[19:0]count_dma2;
reg selet;
reg temp_selet;
always@(negedge clk or negedge rst)
begin
    if(!rst)selet <= 0;
    else selet <= temp_selet;
end
always@(negedge clk or negedge rst)
begin
    if(!rst)temp_selet <= 0;
    else
    begin
        if(count_dma1==20'd99999) temp_selet <= 1;
        else if(count_dma2==20'd99999)temp_selet <= 0;
        else temp_selet <= temp_selet;
    end
end
always@(negedge clk or negedge rst)
begin
    if(!rst)
    begin
        count_dma1 <= 0;
        count_dma2 <= 0;
    end
    else
    begin
        if(selet==0 && dma1_tready==1 && data_valid==1 )
        begin
            count_dma2 <= 0;
            count_dma1 <= count_dma1 + 1;
        end
        else if(selet==1 && dma2_tready==1 && data_valid==1)
        begin
            count_dma1 <= 0;
            count_dma2 <= count_dma2 + 1;
        end
        else
        begin
            count_dma2 <= count_dma2;
            count_dma1 <= count_dma1;
        end
    end
end
always@(negedge clk or negedge rst)
begin
    if(!rst)
    begin
        dma1_tvalid <= 0;
        dma1_tkeep  <= 0;
        dma1_tlast  <= 0;
        dma1_tdata  <= 0;
        
        dma2_tvalid <= 0;
        dma2_tkeep  <= 0;
        dma2_tlast  <= 0;
        dma2_tdata  <= 0;
    end
    else
    begin
        if(data_valid)
        begin
            if(selet==0)//dma1
            begin
                dma1_tvalid <= 1;
                dma1_tkeep  <= 4'b1111;
                dma1_tdata  <= data_in;
                
                dma2_tvalid <= 0;
                dma2_tkeep  <= 0;
                dma2_tlast  <= 0;
                dma2_tdata  <= 0;
                               
                if(count_dma1>=20'd99999)
                begin
                    dma1_tlast <= 1;
                end
                else
                begin
                    dma1_tlast <= 0;
                end    
            end
            else //dma2
            begin
                dma2_tvalid <= 1;                    
                dma2_tkeep  <= 4'b1111;              
                dma2_tdata  <= data_in;              
                                                     
                dma1_tvalid <= 0;                    
                dma1_tkeep  <= 0;                    
                dma1_tlast  <= 0;                    
                dma1_tdata  <= 0;                    
                                                                
                if(count_dma2>=20'd99999)        
                begin                                      
                    dma2_tlast <= 1;   
                end                              
                else                             
                begin                            
                    dma2_tlast <= 0;     
                end                                                             
            end
        end
        else
        begin           
            dma1_tvalid <= 0;
            dma1_tkeep  <= 0;
            dma1_tdata  <= dma1_tdata;
            dma1_tlast  <= dma1_tlast;
            
            dma2_tvalid <= 0;         
            dma2_tkeep  <= 0;         
            dma2_tdata  <= dma2_tdata;
            dma2_tlast  <= dma2_tlast;         
        end
    end
end
endmodule

③设备树代码 pl.dtsi

/ {
	amba_pl: amba_pl {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "simple-bus";
		ranges ;
		axi_dma_0: dma@40400000 {
			#dma-cells = <1>;
			clock-names = "s_axi_lite_aclk", "m_axi_s2mm_aclk";
			clocks = <&clkc 15>, <&clkc 15>;
			compatible = "xlnx,axi-dma0";
			interrupt-names = "s2mm_introut";
			interrupt-parent = <&intc>;
			interrupts = <0 29 4>;
			reg = <0x40400000 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,sg-length-width = <0x14>;
			dma-channel@40400030 {
				compatible = "xlnx,axi-dma-s2mm-channel";
				dma-channels = <0x1>;
				interrupts = <0 29 4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x0>;
			};
		};
		axi_dma_1: dma@40410000 {
			#dma-cells = <1>;
			clock-names = "s_axi_lite_aclk", "m_axi_s2mm_aclk";
			clocks = <&clkc 15>, <&clkc 15>;
			compatible = "xlnx,axi-dma1";
			interrupt-names = "s2mm_introut";
			interrupt-parent = <&intc>;
			interrupts = <0 30 4>;
			reg = <0x40410000 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,sg-length-width = <0x14>;
			dma-channel@40410030 {
				compatible = "xlnx,axi-dma-s2mm-channel";
				dma-channels = <0x1>;
				interrupts = <0 30 4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x1>;
			};
		};
		axi_gpio_0: gpio@41200000 {
			#gpio-cells = <3>;
			clock-names = "s_axi_aclk";
			clocks = <&clkc 15>;
			compatible = "xlnx,axi-gpio-test";
			gpio-controller ;
			reg = <0x41200000 0x10000>;
			xlnx,all-inputs = <0x0>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x1>;
			xlnx,all-outputs-2 = <0x0>;
			xlnx,dout-default = <0x00000000>;
			xlnx,dout-default-2 = <0x00000000>;
			xlnx,gpio-width = <0x1>;
			xlnx,gpio2-width = <0x20>;
			xlnx,interrupt-present = <0x0>;
			xlnx,is-dual = <0x0>;
			xlnx,tri-default = <0xFFFFFFFF>;
			xlnx,tri-default-2 = <0xFFFFFFFF>;
		};
	};
};

④gpio驱动代码gpio_driver.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  /* Needed for Sequence File Operations */
#include 
#include 
#include 
#include 
#include "linux/device.h"
#include "linux/miscdevice.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "linux/kthread.h"
#include "linux/wait.h"
#include "linux/completion.h"
#include "linux/workqueue.h"
#include 
#include 

int major;
static struct class *gpio_class   = NULL;
void __iomem *base_regs;

/*
 *open 接口函数
 *
 * */
static int axi_gpio_open(struct inode *inode,struct file *file)
{
    printk("enter axi_gpio_open \r\n");
    return 0;
}
/*
 * write 接口函数
 *
 * */
static int axi_gpio_write(struct file *file,const char __user *buf, size_t count,loff_t *ppos)
{
    unsigned int ret=0;
	int data;
    printk("gpio write start !\n");
	ret=copy_from_user(&data,buf,count);
	if(ret!=0)
	{
		printk("data read fail\n");
		return -1;
	}
    iowrite32(data,base_regs);

    return 0;
}
/*
 * read 接口函数
 *
 * */
static int axi_gpio_read(struct file *file,char __user *buf,size_t size,loff_t *ppos)
{

    printk("gpio read start!\n");
    return 0;
}

static struct file_operations axi_gpio_fops={
	.owner = THIS_MODULE,
	.read  = axi_gpio_read,
	.write = axi_gpio_write,
	.open  = axi_gpio_open,
};
unsigned int irq_num;
static int gpio_probe(struct platform_device *pdev)
{
	struct resource *io;
	printk("enter gpio probe\r\n");
	//从设备树中获取gpio基地址,同时进行地址映射,虚拟地址保存在 base_regs 中
	io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base_regs = devm_ioremap_resource(&pdev->dev, io);
	printk("gpio base address is: 0X:%x\r\n",base_regs);
	iowrite32(0x0,base_regs);
	major         = register_chrdev( 0 , "axi_gpio_dev" , &axi_gpio_fops);
    gpio_class     = class_create (THIS_MODULE , "axi_gpio_dev");
    device_create(gpio_class, NULL, MKDEV(major,0), NULL, "axi_gpio_dev");
	printk("major dev number= %d",major);
	return 0;
}
static int gpio_remove(struct platform_device *pdev)
{
	unregister_chrdev(major,"axi_gpio_dev");
    device_destroy(gpio_class,MKDEV(major,0));
    class_destroy(gpio_class);

	iounmap(base_regs);
	return 0;
	
}
static const struct of_device_id my_axigpio_of_ids[] = {
	{ .compatible = "xlnx,axi-gpio-test",},
	{}
};

static struct platform_driver my_axigpio_driver = {
	.driver = {
		.name = "my_axi_gpio",
		.of_match_table = my_axigpio_of_ids,
	},
	.probe  = gpio_probe,
	.remove = gpio_remove,
};

module_platform_driver(my_axigpio_driver);

MODULE_AUTHOR("TES@gpio");
MODULE_DESCRIPTION("my gpio driver");
MODULE_ALIAS("my gpio linux driver");
MODULE_LICENSE("GPL");

⑥dma驱动代码dma0_driver.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  /* Needed for Sequence File Operations */
#include 
#include 
#include 
#include 
#include "linux/device.h"
#include "linux/miscdevice.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "linux/kthread.h"
#include "linux/wait.h"
#include "linux/completion.h"
#include "linux/workqueue.h"
#include 
#include 
/**
 *DMA驱动程序
 *
 * **/
//DMA MM2S控制寄存器
volatile unsigned int  * mm2s_cr;
#define MM2S_DMACR		0X00000000

//DMA MM2S状态控制寄存器
volatile unsigned int * mm2s_sr;
#define MM2S_DMASR		0X00000004

//DMA MM2S源地址低32位
volatile unsigned int * mm2s_sa;
#define MM2S_SA			0X00000018

//DMA MM2S传输长度(字节)
volatile unsigned int * mm2s_len;
#define MM2S_LENGTH		0X00000028

//DMA S2MM控制寄存器
volatile unsigned int  * s2mm_cr;
#define S2MM_DMACR		0X00000030

//DMA S2MM状态控制寄存器
volatile unsigned int  * s2mm_sr;
#define S2MM_DMASR		0X00000034

//DMA S2MM目标地址低32位
volatile unsigned int  * s2mm_da;
#define S2MM_DA			0X00000048

//DMA S2MM传输长度(字节)
volatile unsigned int  * s2mm_len;
#define S2MM_LENGTH		0X00000058


#define DMA_BUF_SIZE  1024*1024
void __iomem *base_regs;
int major;
dma_addr_t phyaddr;//物理地址
char *buf;//虚拟地址
static struct class *dma_class   = NULL;
//DMA interrupt functions
int dma_flag=0;
unsigned int status=0;
static int irq_cnt=0;
static irqreturn_t dma0_s2mm_irq(int irq,void *dev_id)
{
	unsigned int data;
	irq_cnt++;
	if(irq_cnt%5==0)
	{
	data = (buf[399999]<<24)|(buf[399998]<<16)|(buf[399997]<<8)|(buf[399996]);
	printk(" %d",data);
	}
	iowrite32(0x0000f000,base_regs  +  S2MM_DMASR);
	iowrite32(0x00001001,base_regs  +  S2MM_DMACR);//open int & enable DMA
	iowrite32(phyaddr   ,base_regs  +  S2MM_DA);
	iowrite32(4000000   ,base_regs  +  S2MM_LENGTH);//write transmission length and DMA start transmission
    return IRQ_HANDLED;
}


/*
 *open 接口函数
 *
 * */
static int axi_dma_open(struct inode *inode,struct file *file)
{
    printk("enter axi_dma_open \r\n");
	iowrite32(0x00001001,base_regs  +  S2MM_DMACR);//open int & enable DMA
	iowrite32(phyaddr   ,base_regs  +  S2MM_DA);
	iowrite32(400000   ,base_regs  +  S2MM_LENGTH);//write transmission length and DMA start transmission
    return 0;
}
/*
 * write 接口函数
 *
 * */
static int axi_dma_write(struct file *file,const char __user *buff, size_t count,loff_t *ppos)
{

    printk("dma write start !\n");
    return 0;
}
/*
 * read 接口函数
 *
 * */
static int axi_dma_read(struct file *file,char __user *buff,size_t size,loff_t *ppos)
{

    printk("dma read start !\n");
    return 0;
}
static unsigned int axi_dma_poll(struct file *file, poll_table *wait)
{
	printk("enter axi_dma_poll \r\n");
	return 0;		
}
static int axi_dma_mmap (struct file *file, struct vm_area_struct *vma)
{
	printk("enter axi_dma_mmap \r\n");
	vma->vm_flags |= VM_IO;
    vma->vm_flags |= VM_LOCKED;
    vma->vm_pgoff = phyaddr >> PAGE_SHIFT;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    if(remap_pfn_range(vma, vma->vm_start,vma->vm_pgoff, vma->vm_end-vma->vm_start, vma->vm_page_prot)){
        printk("axi_dma_mmap failed.\n");
        return -EAGAIN;
    }
    printk("base start=0x%08lx, \n",(unsigned long)phyaddr);
    printk("vma start=0x%08lx, size=%ld.\n",
        (unsigned long)vma->vm_start,
        (unsigned long)vma->vm_end - (unsigned long)vma->vm_start);   
	return 0;
}
static struct file_operations axi_dma_fops={
	.owner = THIS_MODULE,
	.read  = axi_dma_read,
	.write = axi_dma_write,
	.open  = axi_dma_open,
	.mmap = axi_dma_mmap,               /* mmap method */ 
};
unsigned int irq_num;
static int my_dma_probe(struct platform_device *pdev)
{
	struct resource *io;
	struct device_node *node = pdev->dev.of_node;
	struct device_node *child;
	int err;
	struct device *dev;
	//从设备树中获取dma基地址,同时进行地址映射,虚拟地址保存在 base_regs 中
	io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base_regs = devm_ioremap_resource(&pdev->dev, io);
	printk("dma base address is: 0X:%x\r\n",base_regs);
	//申请中断
	for_each_child_of_node(node, child){
		if(of_device_is_compatible(child, "xlnx,axi-dma-s2mm-channel"))
		{
			printk("axi dma s2mm valid\r\n");
			irq_num = irq_of_parse_and_map(child, 0);
			err = request_irq(irq_num, dma0_s2mm_irq, IRQF_TRIGGER_RISING, "dma0_s2mm_irq", NULL);
			if (err) 
			{
				printk( "dma0 unable to request IRQ %d\n", irq_num);
				return err;
			}
		}
		else
		{
			printk("dma0 Invalid channel compatible node\n");
			return -EINVAL;
		}
	}
	///申请dma缓冲区--起始
	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;
	dev = &pdev->dev;
	dev->coherent_dma_mask = 0xffffffff;
	buf = dma_alloc_coherent(dev, PAGE_ALIGN(DMA_BUF_SIZE), &phyaddr, GFP_KERNEL);
	if(!buf)
	{
		printk("axi vdma  buffer memory allocation failed\r\n");
        	return -ENOMEM;
	}
	else
	{
		printk("buf=0x%x\n",buf);
		printk(" phyaddr=0x%x\r\n",phyaddr);
	}
	///申请dma缓冲区--结束
	major         = register_chrdev( 0 , "axi_dma0_dev" , &axi_dma_fops);
    dma_class     = class_create (THIS_MODULE , "axi_dma0_dev");
    device_create(dma_class, NULL, MKDEV(major,0), NULL, "axi_dma0_dev");
	printk("major dev number= %d",major);
	return 0;
}
static int my_dma_remove(struct platform_device *pdev)
{
	unregister_chrdev(major,"axi_dma0_dev");
    device_destroy(dma_class,MKDEV(major,0));
    class_destroy(dma_class);
    free_irq(irq_num,NULL);
	dma_free_coherent(NULL, PAGE_ALIGN(DMA_BUF_SIZE), buf, phyaddr);
	iounmap(base_regs);
	return 0;
	
}
static const struct of_device_id my_dma_of_ids[] = {
	{ .compatible = "xlnx,axi-dma0",},
	{}
};

static struct platform_driver my_dma_driver = {
	.driver = {
		.name = "my_axi_dma0",
		.of_match_table = my_dma_of_ids,
	},
	.probe  = my_dma_probe,
	.remove = my_dma_remove,
};

module_platform_driver(my_dma_driver);

MODULE_AUTHOR("TEST@dma0");
MODULE_DESCRIPTION("dma0 driver");
MODULE_ALIAS("dma0 linux driver");
MODULE_LICENSE("GPL");

⑦Makefile 文件

KDIR = /home/python/Hard_disk_21G/04-Linux_Source/Kernel/linux-xlnx
PWD := $(shell pwd)
CC   = $(CROSS_COMPILE)gcc
ARCH =arm
MAKE =make
obj-m:=dma0_driver.o

modules:
	$(MAKE) -C $(KDIR) ARCH=$(ARCH) CROSS_COMPLE=$(CROSS_COMPLE) M=$(PWD) modules
clean:
	make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPLE=$(CROSS_COMPLE) M=$(PWD) clean

⑧测试文件 test_my_dma.c

#include 
#include 
#include 
#include 
#include            
#include             
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DMA_BUF_SIZE  1024*1024

void delay()
{
	int i=0,j=0;
	for(i=0;i<10000;i++)
		for(j=0;j<1000;j++)j++;
}
int main()
{
	int dma0_fd,dma1_fd,gpio_fd;
	int i=0,j=0;
	unsigned int * dma0_mmapBuf;
	unsigned int * dma1_mmapBuf;
	int dma0_flag,dma1_flag,gpio_flag;
	int *dest;
	dma0_fd = open("/dev/axi_dma0_dev", O_RDWR, 0);
	if (dma0_fd < 0) {
        	printf("Open %s failed\n", "/dev/axivdma_dev");
        	return -1;
	}
	dma0_mmapBuf = mmap(NULL, DMA_BUF_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, dma0_fd, 0);
	
	dma1_fd = open("/dev/axi_dma1_dev", O_RDWR, 0);
	if (dma1_fd < 0) {

        	printf("Open %s failed\n", "/dev/axivdma_dev");

        	return -1;

	}
	dma1_mmapBuf = mmap(NULL, DMA_BUF_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, dma1_fd, 0);
	

	gpio_fd = open("/dev/axi_gpio_dev", O_RDWR, 0);
	if (gpio_fd < 0) {
        	printf("Open %s failed\n", "/dev/axi_gpio_dev");
        	return -1;
	}
	gpio_flag=1;
	printf("======start====\r\n");

	delay();

	write(gpio_fd,&gpio_flag,4);

	delay();

	while(1);

	printf("dma test Done.\n");

	return 0;

}

四.linux设备树编译
①在Ubuntu里下载device-tree-xlnx,执行下面代码

git clone https://github.com/Xilinx/device-tree-xlnx.git
cd device-tree-xlnx
git checkout xilinx-v2015.4

会得到下面文件
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第2张图片ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第3张图片
将其拷贝到windows上,进入SDK进行配置,可以得到以下文件
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第4张图片
其中pl.dtsi为使用的设备树驱动文件。将pcw.dtsi、pl.dtsi、system-top.dts、zynq-7000.dtsi拷贝到ubuntu中,使用内核自带工具dtc工具(位于linux-xlnx/scripts/dtc/dtc下)将其编译成设备树。但需要先将system-top.dts修改成如下

/dts-v1/;
/include/ "zynq-7000.dtsi"
/include/ "pl.dtsi"
/include/ "pcw.dtsi"

再使用dtc工具编译设备树,指令如下:

Kernel/linux-xlnx/scripts/dtc/dtc -I dts -O dtb -o devicetree.dtb system-top.dts

五.下板验证
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第5张图片
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第6张图片
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据_第7张图片
在DMA0驱动中,每次传输完成在中断里进行计数,每5次中断输出一次数据,可以看见结果符合预期。

你可能感兴趣的:(zynq,linux,嵌入式)