AD采集 DMA传输 Linux端显示数据 demo

gitlab地址:http://120.55.75.228:23080/zhdyz/ad_dma

本文是阶段性实验,实现了将PL端的模拟AD采集数据通过dma方式搬运到PS端内存中,然后PS端运行的Linux系统可以获取显示采集到的AD数据

之前使用Vitis实现了dma传输:https://www.limfx.pro/ReadArticle/3731/dma-jiang-pl-shu-ju-ban-yun-dao-ps-nei-cun

本文与上篇文章有许多相似之处,可以对比参考

实验目的

在PL部分模拟产生8个通道16位的AD数据(假的),然后通过DMA传输到PS端,通过Linux驱动实现内存映射等功能,最后通过Linux应用在命令行(以原始形式)显示产生的AD数据

实验流程

Verilog部分

这部分可参考 DMA将PL数据搬运到PS内存 里的Vivado部分,几乎完全一致

就是配置Zynq,配置DMA,处理数据的产生和转换

值得注意的是,这里DMA的一些配置参数和后面的设备树文件紧密相关,如果这里修改了有关参数,后面的设备树文件内的相关参数也要修改成一样的,否则会无法进行DMA传输 (犯过的错误)

整体设计如图所示

bd

Vitis部分

生成平台工程,产生需要的三个FSBL文件,在前面文章有多次提过

设备树文件 zynqmp-mzux.dts,下面只显示PL部分,就是这里axi_dma_0的参数注意和刚才Vivado内设置的是否相同

/ {
	amba_pl: amba_pl@0 {
		#address-cells = <2>;
		#size-cells = <2>;
		compatible = "simple-bus";
		ranges ;
		axi_dma_0: dma@80000000 {
			#dma-cells = <1>;
			clock-names = "m_axi_s2mm_aclk", "s_axi_lite_aclk";
			clocks = <&zynqmp_clk 71>, <&zynqmp_clk 71>;
			compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
			interrupt-names = "s2mm_introut";
			interrupt-parent = <&gic>;
			interrupts = <0 89 4>;
			reg = <0x0 0x80000000 0x0 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,sg-length-width = <0x17>;
			dma-channel@80000030 {
				compatible = "xlnx,axi-dma-s2mm-channel";
				dma-channels = <0x1>;
				interrupts = <0 89 4>;
				xlnx,datawidth = <0x80>;
				xlnx,device-id = <0x0>;
			};
		};
		gpio_user_rstn: gpio@80010000 {
			#gpio-cells = <3>;
			clock-names = "s_axi_aclk";
			clocks = <&zynqmp_clk 71>;
			compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a";
			gpio-controller ;
			reg = <0x0 0x80010000 0x0 0x10000>;
			xlnx,all-inputs = <0x0>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x0>;
			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_user_start: gpio@80020000 {
			#gpio-cells = <3>;
			clock-names = "s_axi_aclk";
			clocks = <&zynqmp_clk 71>;
			compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a";
			gpio-controller ;
			reg = <0x0 0x80020000 0x0 0x10000>;
			xlnx,all-inputs = <0x0>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x0>;
			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>;
		};
		adcdma_fun_0: adcdma_fun0@0 {
			compatible = "adcdma_demo";
			dmas = <&axi_dma_0 0>; 
			dma-names = "adc";
			num-buf = <3>;
			reset-gpios = <&gpio_user_rstn 0 0 GPIO_ACTIVE_HIGH>;
			enable-gpios = <&gpio_user_start 0 0 GPIO_ACTIVE_HIGH>;
		};
	};
};

然后把相关文件传输到Linux开发环境交叉编译,产生BOOT文件放置SD卡

Linux驱动开发

驱动文件dma_drv.c如下,带注释

内存分配有关结构体为adcdma_para,函数为of_adcdma_data

#define ADC_DMAINIT_S 1

#define ADCDATA_SIZE (1024 * 1024)
/*
struct dma_chan *dma;:DMA 通道指针,用于管理 DMA 操作。
int buf_con;:缓存数,表示 DMA 中缓存的数量,不大于 32。
int mem_switch;:当前 DMA 使用的内存块索引,表示当前写入的内存块。
int mem_flag;:缓存标识,被 DMA 写入后对应位置 1,读取后对应位置 0。
int read_mem;:当前读取的内存块索引。
phys_addr_t adc_phys[32];:DMA 物理内存地址数组,用于存储 32 个内存块的物理地址。
void __iomem *adc_virt[32];:DMA 物理内存对应的映射地址数组,用于存储 32 个内存块的虚拟地址。
struct scatterlist sglis[32];:散射列表,用于描述 DMA 操作中的内存分布情况,包括物理地址和大小等信息。
*/
struct adcdma_para
{
	struct dma_chan *dma;
	int buf_con;
	int mem_switch;
	int mem_flag;
	int read_mem;
	phys_addr_t adc_phys[32];
	void __iomem *adc_virt[32];
	struct scatterlist sglis[32];
};
/*
struct class *adcdma_class;:指向设备类的指针,用于创建设备文件。
struct device *adcdma_dev;:指向设备的指针,用于表示设备。
wait_queue_head_t read_queue;:用于实现阻塞等待的等待队列头部。
int irq_reprot;:IRQ 报告标志,用于标识是否接收到中断。
int trans_en;:传输使能标志,用于标识 DMA 是否启用。
dev_t t_dev;:设备号,用于标识设备。
struct gpio_desc *reset_gpio;:复位 GPIO 描述符,用于控制设备复位。
struct gpio_desc *enable_gpio;:使能 GPIO 描述符,用于控制设备使能。
struct adcdma_para *dmad;:指向 adcdma_para 结构体的指针,用于管理 DMA 参数。
int used;:设备使用标志,用于标识设备是否被使用。
*/
struct adcdma_fun
{
	struct class *adcdma_class;
	struct device *adcdma_dev;
	wait_queue_head_t read_queue;
	int irq_reprot;
	int trans_en;
	dev_t t_dev;
	struct gpio_desc *reset_gpio;
	struct gpio_desc *enable_gpio;
	struct adcdma_para *dmad;
	int used;
};

/*
CMD_GET_DMADATA:用于从设备中获取 ADC 数据的命令,对应的宏 _IO(ADC_IOCTL_MAGIC, 0) 表示没有参数的 IOCTL 命令。
CMD_SET_START:用于启动 ADC 传输的命令,对应的宏 _IO(ADC_IOCTL_MAGIC, 1) 表示没有参数的 IOCTL 命令。
*/
#define ADC_IOCTL_MAGIC 'M'

#define CMD_GET_DMADATA _IO(ADC_IOCTL_MAGIC, 0)
#define CMD_SET_START _IO(ADC_IOCTL_MAGIC, 1)

/*
定义了一个名为 irq_change_mem 的静态函数,用于处理中断并切换 DMA 内存块。下面是对函数的注释和解析:
*/
static void irq_change_mem(struct adcdma_para *dmap, int num)
{
	struct adcdma_para *dmad_tmp = dmap; //创建一个临时指针 dmad_tmp,指向传入的 adcdma_para 结构体。
	int next;

	dmad_tmp->mem_flag |= (1 << dmad_tmp->mem_switch); //将当前内存块的标志位置为已使用,即将对应位置 1
	next = (dmad_tmp->mem_switch + 1) % dmad_tmp->buf_con; //计算下一个要使用的内存块的索引
	if ((dmad_tmp->mem_flag & (1 << next)) == 0) //检查下一个内存块是否已被使用
	{
        //如果未被使用,则切换到下一个内存块并调用 adcdma_setup 函数进行设置,并通过 dma_async_issue_pending 函数发出 DMA 异步操作请求。
		dmad_tmp->mem_switch = next; 
		adcdma_setup(dmap, next);
		dma_async_issue_pending(dmap->dma);
	}
	else
	{
        //如果已被使用,则打印错误信息
		printk("mem change err %d\n", next);
	}
}
/*
定义了一个名为 adcdma_irq_handler 的静态函数,用于处理 ADC DMA 的中断事件。下面是对函数的注释和解析
*/
static void adcdma_irq_handler(void *data)
{
	int num = (int)data; //将 data 转换为整数类型,表示中断号

	//	printk("irq %d\n", num);
	adcdma_data->irq_reprot = 1; //将 IRQ 报告标志位置为 1,表示接收到中断

	if (adcdma_data->trans_en) //检查传输使能标志是否为真
	{
        //如果为真,则唤醒等待队列中的进程,并调用 irq_change_mem 函数切换 DMA 内存块
		wake_up_interruptible(&adcdma_data->read_queue);
		irq_change_mem(adcdma_data->dmad, num);
	}
}
/*
定义了一个名为 adcdma_setup 的函数,用于设置 ADC DMA 的参数并提交 DMA 传输请求。根据 ADC_DMAINIT_S 宏的定义,函数会选择不同的初始化方式
*/
static int adcdma_setup(struct adcdma_para *dmap, int nfb)
{
	struct dma_async_tx_descriptor *desc; //DMA 异步传输描述符,用于描述 DMA 传输的参数和状态
	struct scatterlist *rx_sg = &dmap->sglis[nfb]; //散射-聚集列表(scatter-gather list,SG),用于描述 DMA 操作中的内存分布情况
	struct dma_device *rx_dev = dmap->dma->device; //DMA 设备指针,用于表示 DMA 控制器的设备
	enum dma_ctrl_flags flags; //DMA 控制标志,用于设置 DMA 控制参数
	int ret = 0;

	flags = DMA_PREP_INTERRUPT;

//如果 ADC_DMAINIT_S 宏为真,表示使用 dmaengine_prep_slave_single 函数进行初始化:
#if ADC_DMAINIT_S
	desc = dmaengine_prep_slave_single(dmap->dma, dmap->adc_phys[nfb],
									   ADCDATA_SIZE, DMA_DEV_TO_MEM, flags);  //用于准备一个单一的 DMA 传输,将数据从设备传输到内存。
	if (!desc)
	{
		pr_err("Failed to prepare DMA descriptor\n");
		return -ENOMEM;
	}
	else
	{
        //如果成功准备了 DMA 描述符,则将其提交给 DMA 引擎进行传输,并设置回调函数 adcdma_irq_handler 处理传输完成的中断
		ret = dmaengine_submit(desc);
		if (ret < 0)
			printk("submit err\n");
		desc->callback = adcdma_irq_handler;
		desc->callback_param = (void *)nfb;
	}
//如果 ADC_DMAINIT_S 宏为假,表示使用手动分配和映射内存的方式进行初始化:
#else
	dmap->adc_virt[nfb] = kmalloc(ADCDATA_SIZE, GFP_KERNEL);
	dma_addr_t adc_dma_rx;
	dma_cookie_t rx_cookie;

	memset(dmap->adc_virt[nfb], 0, ADCDATA_SIZE);

	adc_dma_rx = dma_map_single(rx_dev->dev, dmap->adc_virt[nfb],
								ADCDATA_SIZE, DMA_MEM_TO_DEV);

	dma_unmap_single(rx_dev->dev, adc_dma_rx,
					 ADCDATA_SIZE,
					 DMA_MEM_TO_DEV);

	adc_dma_rx = dma_map_single(rx_dev->dev, dmap->adc_virt[nfb],
								ADCDATA_SIZE, DMA_DEV_TO_MEM);

	sg_init_table(rx_sg, 1);
	sg_dma_address(rx_sg) = adc_dma_rx;
	sg_dma_len(rx_sg) = ADCDATA_SIZE;

	desc = rx_dev->device_prep_slave_sg(dmap->dma, rx_sg, 1,
										DMA_DEV_TO_MEM, flags, NULL);

	rx_cookie = desc->tx_submit(desc);
	desc->callback = adcdma_irq_handler;
	desc->callback_param = (void *)nfb;
#endif

	return 0;
}

static int data_to_user(struct adcdma_para *dmap, void __user *user_buf)
{
	int size;
	int ret = 0;

	size = ADCDATA_SIZE;
	ret = copy_to_user(user_buf, dmap->adc_virt[dmap->read_mem], size) ? -EFAULT : 0;
	// if (adcdma_data->trans_en == 1)
	// adcdma_setup(dmap,dmap->read_mem);
	//	dma_async_issue_pending(dmap->dma);
	dmap->mem_flag &= ~(1 << dmap->read_mem);
	//	printk("ioctl free mem %d\n", dmap->read_mem);
	dmap->read_mem = (dmap->read_mem + 1) % dmap->buf_con;
	return ret;
}

/*
实现了设备驱动程序的 ioctl 函数,用于处理用户空间程序发起的设备控制命令
*/
static long adcdma_func_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	void __user *user_arg = (void __user *)arg; //将 arg 转换为用户空间指针,用于访问用户空间的数据
	int flag;
	int n;

	switch (cmd)
	{
	//从设备获取 AD 数据
	case CMD_GET_DMADATA:
		ret = data_to_user(adcdma_data->dmad, user_arg);
		break;
	//启动 DMA 传输
	case CMD_SET_START:
		copy_from_user(&flag, user_arg, sizeof(flag));
		printk("flag = %d\n", flag);

		adcdma_data->dmad->mem_switch = 0;
		adcdma_data->dmad->mem_flag = 0;
		adcdma_data->dmad->read_mem = 0;
		//根据 flag 设置传输使能标志,并根据需要进行 DMA 设置和传输发起
		if (flag == 0) 
		{
			adcdma_data->trans_en = 0;
			dmaengine_terminate_all(adcdma_data->dmad->dma); //函数终止当前 DMA 通道上的所有传输,确保传输完全停止
		}
		else
		{
			adcdma_data->trans_en = 1;

			ret = adcdma_setup(adcdma_data->dmad, 0);
			dma_async_issue_pending(adcdma_data->dmad->dma); //发出 DMA 异步操作请求,开始数据传输
		}
		gpiod_set_value_cansleep(adcdma_data->enable_gpio, flag);

		break;

	default:
		ret = -EFAULT;
		break;
	}
	return ret;
}
/*
处理用户空间程序对设备文件的轮询操作
*/
static unsigned int adcdma_func_poll(struct file *file, struct poll_table_struct *wait)
{
	int mask = 0; //初始化轮询事件掩码为 0

	poll_wait(file, &(adcdma_data->read_queue), wait); //将当前进程添加到设备等待队列中,以便在设备事件发生时唤醒进程
	if (adcdma_data->irq_reprot == 1) //如果 IRQ 报告标志为 1,则清除 IRQ 报告标志并设置轮询事件掩码为可读事件(POLLIN | POLLRDNORM)
	{
		adcdma_data->irq_reprot = 0;
		mask |= (POLLIN | POLLRDNORM);
	}

	return mask;
}
/*
重置 ADC DMA 设备
*/
void adcdma_reset(void)
{
	gpiod_set_value_cansleep(adcdma_data->reset_gpio, 0);
	gpiod_set_value_cansleep(adcdma_data->enable_gpio, 0);
	gpiod_set_value_cansleep(adcdma_data->reset_gpio, 1);
}
/*
打开设备文件
*/
int adcdma_func_open(struct inode *inode, struct file *file)
{
	int ret = 0;

	if (adcdma_data->used >= 1) //如果设备已被打开(used >= 1),则打印驱动程序忙的消息并返回 -EBUSY 错误码
	{
		printk("%s driver busy\n", file->f_path.dentry->d_iname);
		return -EBUSY;
	}
	adcdma_data->used = 1;

	adcdma_data->trans_en = 1;

	// dmaengine_terminate_all(adcdma_data->dmad[i]->dma);

	//将 DMA 参数重置,包括内存块切换、内存块标志和读取的内存块索引
	adcdma_data->dmad->mem_switch = 0;
	adcdma_data->dmad->mem_flag = 0;
	adcdma_data->dmad->read_mem = 0;
	// adcdma_reset();

	return ret;
}
/*
关闭设备文件
*/
int adcdma_func_release(struct inode *inode, struct file *file)

{
	int j = 0;

	dmaengine_terminate_all(adcdma_data->dmad->dma); //终止当前 DMA 通道上的所有传输
	adcdma_data->trans_en = 0;
	adcdma_data->used = 0;

#if !ADC_DMAINIT_S

	for (j = 0; j < adcdma_data->dmad->buf_con; j++)
		kfree(adcdma_data->dmad->adc_virt[j]);
#endif
	return 0;
}

const struct file_operations adcdma_fops =
	{
		.owner = THIS_MODULE,
		.open = adcdma_func_open,
		.release = adcdma_func_release,
		.unlocked_ioctl = adcdma_func_ioctl,
		.poll = adcdma_func_poll,
};
/*
初始化 ADC DMA 的字符设备
*/
int adcdma_cdev_init(struct adcdma_fun *pdata)
{
	int rc;
	struct cdev *c_dev;

	rc = alloc_chrdev_region(&pdata->t_dev, 0, 1, "adcdma_fun"); //分配字符设备的主次设备号
	if (rc)
		goto out_err;

	c_dev = cdev_alloc(); //分配一个字符设备结构体
	if (!c_dev)
		goto out_err;

	cdev_init(c_dev, &adcdma_fops); //初始化字符设备结构体
	rc = cdev_add(c_dev, pdata->t_dev, 1); //将字符设备添加到系统
	if (rc)
		goto out_unreg;

	pdata->adcdma_class = class_create(THIS_MODULE, "adcdma"); //创建设备类
	if (IS_ERR(pdata->adcdma_class))
	{
		printk("[err]class_create error\n");
		rc = -1;
		goto out_devdel;
	}

	pdata->adcdma_dev = device_create(pdata->adcdma_class, NULL, pdata->t_dev, NULL, "adcdma_fun"); //创建设备
	if (!pdata->adcdma_dev)
	{
		rc = -1;
		goto class_err;
	}

	return 0;

class_err:
	class_destroy(pdata->adcdma_class);

out_devdel:
	cdev_del(c_dev);

out_unreg:
	unregister_chrdev_region(pdata->t_dev, 1);

out_err:

	return rc;
}
/*
从设备树中读取 ADC DMA 的配置信息并初始化相关数据结构
*/
int of_adcdma_data(struct adcdma_fun *pdata, struct platform_device *pdev)
{
	int cnt = 0;
	int ret = 0;
	int i = 0;
	int hsize = 0;

	pdata->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset", //获取复位 GPIO,如果未找到则返回错误
												GPIOD_OUT_LOW);
	if (IS_ERR(pdata->reset_gpio))
	{

		printk("[zgq]get reset gpio err\n");
		return PTR_ERR(pdata->reset_gpio);
	}

	pdata->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", //获取使能 GPIO,如果未找到则返回错误
												 GPIOD_OUT_LOW);
	if (IS_ERR(pdata->enable_gpio))
	{
		printk("[zgq]get enable gpio err\n");
		return PTR_ERR(pdata->enable_gpio);
	}
	adcdma_reset();

	pdata->dmad = devm_kmalloc(&pdev->dev, sizeof(struct adcdma_para), GFP_KERNEL); //分配 DMA 参数结构体的内存
	if (pdata->dmad == NULL)
	{
		printk("kmalloc err\n");
		return -1;
	}
	memset(pdata->dmad, 0, sizeof(struct adcdma_para)); //将 DMA 参数结构体清零
	pdata->dmad->dma = dma_request_chan(&pdev->dev, "adc"); //请求 DMA 通道
	if (IS_ERR_OR_NULL(pdata->dmad->dma))
	{
		printk("get dma0 err\n");
		return PTR_ERR(pdata->dmad->dma);
	}

	ret = of_property_read_u32(pdev->dev.of_node, //从设备树中读取缓存数量
							   "num-buf", &cnt);
	if (ret < 0)
	{
		pr_err("adcdmatest: missing num-frm property\n");
		return ret;
	}
	cnt = cnt > 32 ? 32 : cnt;
	pdata->dmad->buf_con = cnt; //设置 DMA 参数结构体中的缓存数量

	hsize = ADCDATA_SIZE;

#if ADC_DMAINIT_S
	for (i = 0; i < cnt; i++)
	{
		/*
		分配一段连续的内存,用于 DMA 传输,并返回虚拟地址和物理地址

		&pdev->dev:设备结构体指针,用于指定分配内存的设备上下文。
		PAGE_ALIGN(hsize):将 hsize 向上对齐到页大小的倍数,以保证分配的内存大小是页大小的整数倍。
		&pdata->dmad->adc_phys[i]:用于存储分配到的物理地址的指针。
		GFP_KERNEL:内存分配标志,表示在内核堆中分配内存,可能会阻塞进程。
		*/
		pdata->dmad->adc_virt[i] = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(hsize), 
													  &pdata->dmad->adc_phys[i], GFP_KERNEL);
		if (pdata->dmad->adc_virt[i] == NULL)
		{
			printk("can't alloc mem\n");
		}
		memset(pdata->dmad->adc_virt[i], 0, hsize);
	}
#endif
	init_waitqueue_head(&pdata->read_queue);
	return 0;
}
/*
在设备被检测到时执行初始化操作
*/
static int adcdma_fun_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct adcdma_fun *pdata = dev_get_platdata(dev); //获取设备的平台数据指针
	int ret = 0;

	if (!pdata) //如果平台数据指针为空,则说明设备没有平台数据,需要进行初始化
	{
		pdata = devm_kzalloc(dev, sizeof(struct adcdma_fun), GFP_KERNEL);
		if (!pdata)
			return -ENOMEM;

		platform_set_drvdata(pdev, pdata); //将平台数据指针保存在设备结构体中,以便后续使用
	} 
	adcdma_data = pdata;
	ret = of_adcdma_data(pdata, pdev); //从设备树中读取配置信息并初始化设备
	if (ret < 0)
		goto out;

	ret = adcdma_cdev_init(pdata); //初始化字符设备
	if (ret < 0)
		goto out;

out:
	return ret;
}
/*
释放 DMA 缓存所占用的内存
*/
static void clean_dma(struct platform_device *pdev, struct adcdma_para *dmap)
{
	int size;
	int i = 0;

	size = ADCDATA_SIZE;

#if ADC_DMAINIT_S

	for (i = 0; i < dmap->buf_con; i++)
	{
		dma_free_coherent(&pdev->dev, PAGE_ALIGN(size),
						  dmap->adc_virt[i], dmap->adc_phys[i]);
	}
#endif
}
/*
在设备被移除时执行清理操作
*/
static int adcdma_fun_remove(struct platform_device *pdev)
{

	dma_release_channel(adcdma_data->dmad->dma);
	clean_dma(pdev, adcdma_data->dmad);

	device_unregister(adcdma_data->adcdma_dev);
	unregister_chrdev_region(adcdma_data->t_dev, 1);
	class_destroy(adcdma_data->adcdma_class);
	return 0;
}
/*
定义了一个名为 adcdma_fun_of_match 的设备树匹配表,用于匹配设备树中的设备节点
*/
static struct of_device_id adcdma_fun_of_match[] = {
	{
		.compatible = "adcdma_demo",
	},
	{},
};
/*
定义了一个名为 adcdma_fun_device_driver 的平台驱动程序,用于管理设备的探测和移除
*/
static struct platform_driver adcdma_fun_device_driver = {
	.probe = adcdma_fun_probe,
	.remove = adcdma_fun_remove,
	.driver = {
		.name = "adcdma_demo",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(adcdma_fun_of_match),
	}
};

static int __init adcdma_fun_init(void)
{
	return platform_driver_register(&adcdma_fun_device_driver); //注册平台驱动程序
}

static void __exit adcdma_fun_exit(void)
{
	platform_driver_unregister(&adcdma_fun_device_driver); //注销平台驱动程序
}

late_initcall(adcdma_fun_init);
module_exit(adcdma_fun_exit);

// module_platform_driver(adcdma_fun_device_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uisrc");

应用程序开发

应用代码dma_app.c,注意只有打开应用程序才会开始DMA传输,关闭程序会终止DMA传输,然后应用程序将AD数据以16进制形式打印在命令行

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>

#define ADC_IOCTL_MAGIC 'M'
#define CMD_GET_DMADATA _IO(ADC_IOCTL_MAGIC, 0)
#define CMD_SET_START _IO(ADC_IOCTL_MAGIC, 1)

#define ADC_SIZE (1024 * 1024)

int main()
{
    int fd;             // 文件描述符
    int ret = 0;        // 返回值
    char data[ADC_SIZE];    // 用于存储 DMA 数据的缓冲区
    uint16_t *data_tmp; // 临时指针,用于按照 16 位读取数据
    int start = 1;      // 控制 DMA 数据传输的标志
    int i, j;

    // 打开 DMA 设备文件
    fd = open("/dev/adcdma_fun", O_RDWR);
    if (fd < 0)
    {
        printf("Can't open file fdma_fun\n");
        return -1;
    }

    // 启动 DMA 数据传输
    ioctl(fd, CMD_SET_START, &start);

    while (1)
    {
        // 从 DMA 设备中读取数据
        ret = ioctl(fd, CMD_GET_DMADATA, data);
        if (ret < 0)
        {
            perror("Get data error\n");
            break;
        }

        // 将数据按照 16 位进行打印
        data_tmp = (uint16_t *)data;
        for (i = 0; i < ADC_SIZE / sizeof(uint16_t); i += 8)
        {
            for (j = 0; j < 8; j++)
            {
                printf("%04X ", data_tmp[i + j]);
            }
            printf("\n");
        }
    }

    // 停止 DMA 数据传输
    start = 0;
    ioctl(fd, CMD_SET_START, &start);

    // 关闭 DMA 设备文件
    close(fd);

    return ret;
}

实验结果

运行dma_app,成功在命令行打印数据,如下图所示

程序运行中可按Ctrl+C终止


本文章使用limfx的vscode插件快速发布