ZYNQ MPSOC 软件无线电开发[软件部分(2)] - 在Petalinux工程中添加驱动程序与自启动脚本

ZYNQ MPSOC 软件无线电开发[软件部分(2)] -在Petalinux工程中添加驱动与自启动脚本

ZYNQ MPSOC 软件无线电开发[软件部分(1)] - 导出Vivado硬件设计并构建基本的Petalinux工程

接上一章,现在已经完成了基本工程的创建,硬件上是Alinx AXU3EGB开发板,PL端连接了AD9238和AD9767。

从该部分往下,大部分内容都可以在Xilinx的官方Petalinux Userguide里找到,而且恰好有官方简中
PetaLinux 工具文档参考指南 (ug1144)

1. 添加自启动脚本

进入Petalinux工程目录,创建一个apps,命名autostart,–enable是启用该apps

petalinux-create -t apps --template install --name autostart --enable
INFO: Create apps: autostart
INFO: New apps successfully created in /home/xlnxdev-2020-1/peta_prj/adda_probtest/petalinux/project-spec/meta-user/recipes-apps/autostart
INFO: Enabling created component...
INFO: sourcing build environment
INFO: silentconfig rootfs
INFO: autostart has been enabled

上面的输出告诉了我们apps的路径,打开文件

~/peta_prj/adda_probtest/petalinux/project-spec/meta-user/recipes-apps/autostart/files/autostart

修改为

#!/bin/sh

echo "Simple applecations autostart"

USERHOOK_SD0=/media/sd-mmcblk0p1/autostart.sh
USERHOOK_SD1=/media/sd-mmcblk1p1/autostart.sh

if [ -f $USERHOOK_SD0 ]; then 
    sh $USERHOOK_SD0 & 
fi 

if [ -f $USERHOOK_SD1 ]; then 
    sh $USERHOOK_SD1 & 
fi

该脚本会被编译到Petalinux中,自动查找SD卡(sd-mmcblk1)或者板载emmc(sd-mmcblk0)第一分区的autostart.sh并执行。

现在打开

~/peta_prj/adda_probtest/petalinux/project-spec/meta-user/recipes-apps/autostart/autostart.bb

修改为

#
# This file is the autostart recipe.
#

SUMMARY = "Simple autostart application"
SECTION = "PETALINUX/apps"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://autostart \
	"

S = "${WORKDIR}"

inherit update-rc.d 
INITSCRIPT_NAME = "autostart" 
INITSCRIPT_PARAMS = "start 99 5 ."

do_install() {
	     install -d ${D}/${sysconfdir}/init.d
	     install -m 0755 ${S}/autostart ${D}/${sysconfdir}/init.d/autostart
}

RDEPENDS_${PN}_append += "bash"

功能是复制autostart到根文件系统 init.d,然后添加 0755 权限。 至于INITSCRIPT_PARAMS的内容可以参考其他博客,主要是控制启动顺序和权限。RDEPENDS_${PN}_append += "bash"是指该脚本依赖bash。

特别指出RDEPENDS_${PN}_append,如果你用的是新版的Petalinux,这部分需要修改成RDEPENDS:${PN}:append = "bash",新的yocto bitbake对override语法有改动。参考https://docs.yoctoproject.org/migration-guides/migration-3.4.html或者ug1144

2. 添加驱动程序

执行命令

petalinux-create -t modules -n ad9767 --enable

刚才的自启动脚本是apps,对于驱动程序一类使用modules。

INFO: Create modules: ad9767
INFO: New modules successfully created in /home/xlnxdev-2020-1/peta_prj/adda_probtest/petalinux/project-spec/meta-user/recipes-modules/ad9767
INFO: Enabling created component...
INFO: sourcing build environment
INFO: silentconfig rootfs
INFO: ad9767 has been enabled

打开生成的.c文件

~/peta_prj/adda_probtest/petalinux/project-spec/meta-user/recipes-modules/ad9767/files/ad9767.c

写入以下驱动程序源码

#include <asm/uaccess.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/delay.h>

#define MODULE_NAME "axi_dac9767_dma"
#define AXI_ADC_MINOR_START 0
#define AXI_ADC_MINOR_COUNT 16
#define AXI_ADC_CALLBACK_TIMEOUTMSEC 200
#define SUCCESS 0
#define FAILURE -1
#define MAX_BUF_COUNT 8
/* IOCTL defines */
#define AXI_ADC_IOCTL_BASE 'W'
#define AXI_ADC_SET_SAMPLE_NUM _IO(AXI_ADC_IOCTL_BASE, 0)
#define AXI_ADC_SET_DMA_LEN_BYTES _IO(AXI_ADC_IOCTL_BASE, 1)
#define AXI_ADC_DMA_INIT _IO(AXI_ADC_IOCTL_BASE, 2)
#define AXI_ADC_DMA_START _IO(AXI_ADC_IOCTL_BASE, 3)
#define AXI_ADC_DMA_DEINIT _IO(AXI_ADC_IOCTL_BASE, 4)
#define AXI_ADC_DMA_CYCLIC_SEND _IO(AXI_ADC_IOCTL_BASE, 5)
#define AXI_ADC_DMA_CYCLIC_STOP _IO(AXI_ADC_IOCTL_BASE, 6)

struct axi_adc_dev
{
	struct mutex mutex;
	struct platform_device *pdev;
	/* ADC Hardware device constants */
	void *adc_virtaddr;
	/* DMA stuff */
	struct dma_chan *tx_chan;
	dma_cookie_t tx_cookie;
	struct completion tx_cmp;
	unsigned long tx_tmo;
	int bd_cnt;
	struct scatterlist *tx_sg;
	/*DMA address of buffer */
	dma_addr_t *dma_dsts;
	u8 **dsts;
	int adc_sample_num;
	int dma_len_bytes;
	size_t cyclic_len;
};

/*ADC channel name */
static const char adc_channels[][20] =
	{
		{"dac0"},
		{"dac1"},
		{"dac2"},
		{"dac3"},
		{"dac4"},
		{"dac5"},
		{"dac6"},
		{"dac7"},
		{"dac8"},
		{"dac9"},
		{"dac10"},
		{"dac11"},
		{"dac12"},
		{"dac13"},
		{"dac14"},
		{"dac15"},
};

static struct axi_adc_dev *axi_adc_dev[16];
static int dev_index = 0;
static dev_t devno;
static struct cdev adc_cdev;
static struct class *axi_adc_class;
static void dma_slave_tx_callback(void *completion)
{
	complete(completion);
}

/* File operations */
int axi_adc_dma_open(struct inode *inode, struct file *filp)
{
	unsigned int mn;
	mn = iminor(inode);
	/*Assign minor number for later reference */
	filp->private_data = (void *)mn;

	return SUCCESS;
}

int axi_adc_dma_release(struct inode *inode, struct file *filp)
{
	return SUCCESS;
}

ssize_t axi_adc_dma_write(struct file *filep, const char __user *buf,
						  size_t count, loff_t *f_pos)
{
	int minor = 0;
	/* Query minor number.
	 * @To be extended for multiple channel support
	 */
	minor = (int)filep->private_data;
	axi_adc_dev[minor]->cyclic_len = count;

	/* Validation for read size */
	if (count > axi_adc_dev[minor]->dma_len_bytes)
	{
		axi_adc_dev[minor]->cyclic_len = axi_adc_dev[minor]->dma_len_bytes;
	}

	copy_from_user(axi_adc_dev[minor]->dsts[0], buf, axi_adc_dev[minor]->cyclic_len);
	return count;
}

/* IOCTL calls provide interface to configure ,start and stop
   DMA engine */
static long axi_adc_dma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	enum dma_ctrl_flags flags;
	enum dma_status status;
	int ret = 0;
	int i;
	int minor = (int)file->private_data;
	struct dma_device *tx_dev = axi_adc_dev[minor]->tx_chan->device;
	struct dma_async_tx_descriptor *txd = NULL;
	struct scatterlist tx_sg[MAX_BUF_COUNT];
	// dma_addr_t dma_dsts[bd_cnt];

	switch (cmd)
	{
	case AXI_ADC_SET_SAMPLE_NUM:
		axi_adc_dev[minor]->adc_sample_num = arg;
		break;
	case AXI_ADC_SET_DMA_LEN_BYTES:
		axi_adc_dev[minor]->dma_len_bytes = arg;
		break;
	case AXI_ADC_DMA_INIT:
		axi_adc_dev[minor]->bd_cnt = 1;
		axi_adc_dev[minor]->dsts = kcalloc(axi_adc_dev[minor]->bd_cnt + 1, sizeof(u8 *), GFP_KERNEL);
		if (!axi_adc_dev[minor]->dsts)
		{
			return ret;
		}
		for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
		{
			axi_adc_dev[minor]->dsts[i] = kmalloc(axi_adc_dev[minor]->dma_len_bytes, GFP_KERNEL);
		}
		axi_adc_dev[minor]->dsts[i] = NULL;
		axi_adc_dev[minor]->dma_dsts = kcalloc(axi_adc_dev[minor]->bd_cnt + 1, sizeof(dma_addr_t), GFP_KERNEL);
		if (!axi_adc_dev[minor]->dma_dsts)
		{
			return ret;
		}
		for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
		{
			axi_adc_dev[minor]->dma_dsts[i] =
				dma_map_single(tx_dev->dev, axi_adc_dev[minor]->dsts[i],
							   axi_adc_dev[minor]->dma_len_bytes, DMA_TO_DEVICE);
		}
		writel(axi_adc_dev[minor]->adc_sample_num, axi_adc_dev[minor]->adc_virtaddr + 4);
		writel(1, axi_adc_dev[minor]->adc_virtaddr);
		break;
	case AXI_ADC_DMA_START:
		/* Start the DMA transaction */
		flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
		// write timeout value
		axi_adc_dev[minor]->tx_tmo = msecs_to_jiffies(AXI_ADC_CALLBACK_TIMEOUTMSEC);
		// printk(KERN_EMERG "sg_init_table\n");
		sg_init_table(tx_sg, axi_adc_dev[minor]->bd_cnt);
		// printk(KERN_EMERG "sg_dma_address\n");
		// initial sg addr
		for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
		{
			sg_dma_address(&tx_sg[i]) = axi_adc_dev[minor]->dma_dsts[i];
			sg_dma_len(&tx_sg[i]) = axi_adc_dev[minor]->dma_len_bytes;
		}

		// cache sync
		dma_sync_single_for_device(tx_dev->dev,
								   axi_adc_dev[minor]->dma_dsts[0],
								   axi_adc_dev[minor]->cyclic_len,
								   DMA_TO_DEVICE);
		dma_sync_single_for_cpu(tx_dev->dev,
								axi_adc_dev[minor]->dma_dsts[0],
								axi_adc_dev[minor]->cyclic_len,
								DMA_TO_DEVICE);

		// get devices describle
		txd = tx_dev->device_prep_slave_sg(axi_adc_dev[minor]->tx_chan, tx_sg, axi_adc_dev[minor]->bd_cnt,
										   DMA_TO_DEVICE, flags, NULL);

		// check devices describle
		if (!txd)
		{
			printk(KERN_EMERG "if (!txd)\n");
			dev_err(&axi_adc_dev[minor]->pdev->dev, "TXD is NULL.\n");
			for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
				dma_unmap_single(tx_dev->dev, axi_adc_dev[minor]->dma_dsts[i],
								 axi_adc_dev[minor]->dma_len_bytes, DMA_TO_DEVICE);
		}

		// initial callback func
		init_completion(&axi_adc_dev[minor]->tx_cmp);
		txd->callback = dma_slave_tx_callback;
		txd->callback_param = &axi_adc_dev[minor]->tx_cmp;

		// submit dma transmit
		axi_adc_dev[minor]->tx_cookie = txd->tx_submit(txd);
		// printk(KERN_EMERG "dma_submit_error\n");
		if (dma_submit_error(axi_adc_dev[minor]->tx_cookie))
		{
			dev_err(&axi_adc_dev[minor]->pdev->dev, "DMA submit error.\n");
		}
		// start dma transmit
		dma_async_issue_pending(axi_adc_dev[minor]->tx_chan);

		// wait dma transmit finish
		// printk(KERN_EMERG "wait %d ms",axi_adc_dev[minor]->tx_tmo);
		axi_adc_dev[minor]->tx_tmo = wait_for_completion_timeout(&axi_adc_dev[minor]->tx_cmp,
																 axi_adc_dev[minor]->tx_tmo);

		// get dma transmit status
		status = dma_async_is_tx_complete(axi_adc_dev[minor]->tx_chan,
										  axi_adc_dev[minor]->tx_cookie, NULL, NULL);

		// check status & timeout
		if (0 == axi_adc_dev[minor]->tx_tmo)
		{
			printk(KERN_EMERG "TX test timed out.");
			// return 1;
		}
		else if (status != DMA_COMPLETE)
		{
			printk(KERN_EMERG "TX got completion callback, but status is error.");
			// return 1;
		}
		break;
	case AXI_ADC_DMA_CYCLIC_SEND:
		dmaengine_terminate_all(axi_adc_dev[minor]->tx_chan);

		dma_sync_single_for_device(tx_dev->dev,
								   axi_adc_dev[minor]->dma_dsts[0],
								   axi_adc_dev[minor]->cyclic_len,
								   DMA_TO_DEVICE);
		dma_sync_single_for_cpu(tx_dev->dev,
								axi_adc_dev[minor]->dma_dsts[0],
								axi_adc_dev[minor]->cyclic_len,
								DMA_TO_DEVICE);

		txd = tx_dev->device_prep_dma_cyclic(axi_adc_dev[minor]->tx_chan,
											 axi_adc_dev[minor]->dma_dsts[0],
											 axi_adc_dev[minor]->cyclic_len,
											 axi_adc_dev[minor]->cyclic_len,
											 DMA_TO_DEVICE, DMA_CTRL_REUSE);

		if (txd == NULL)
			return 0;
		axi_adc_dev[minor]->tx_cookie = txd->tx_submit(txd);

		if (dma_submit_error(axi_adc_dev[minor]->tx_cookie))
		{
			pr_warn("Start DMA cyclic fail.\n");
		}
		else
		{
			dma_async_issue_pending(axi_adc_dev[minor]->tx_chan);
			writel(axi_adc_dev[minor]->adc_sample_num, axi_adc_dev[minor]->adc_virtaddr + 4);
			writel(1, axi_adc_dev[minor]->adc_virtaddr);
		}
		break;
	case AXI_ADC_DMA_CYCLIC_STOP:
		dmaengine_terminate_all(axi_adc_dev[minor]->tx_chan);
		break;
	case AXI_ADC_DMA_DEINIT:
		break;
	default:
		return -EOPNOTSUPP;
	}
	return SUCCESS;
}

struct file_operations axi_adc_fops =
	{
		.owner = THIS_MODULE,
		.write = axi_adc_dma_write,
		.open = axi_adc_dma_open,
		.unlocked_ioctl = axi_adc_dma_ioctl,
		.release = axi_adc_dma_release};

static int axi_adc_remove(struct platform_device *pdev)
{
	int i;
	for (i = 0; i < dev_index; i++)
	{
		device_destroy(axi_adc_class, MKDEV(MAJOR(devno), i));

		/* Free up the DMA channel */
		dma_release_channel(axi_adc_dev[i]->tx_chan);

		/* Unmap the adc I/O memory */
		if (axi_adc_dev[i]->adc_virtaddr)
			iounmap(axi_adc_dev[i]->adc_virtaddr);

		if (axi_adc_dev[i])
		{
			kfree(axi_adc_dev[i]);
		}
		dev_info(&pdev->dev, "dac9767 DMA Unload :: Success! \n");
	}
	class_destroy(axi_adc_class);
	cdev_del(&adc_cdev);
	unregister_chrdev_region(devno, AXI_ADC_MINOR_COUNT);
	return SUCCESS;
}

static int axi_adc_probe(struct platform_device *pdev)
{
	int status = 0;

	struct device_node *node = NULL;

	/*Allocate device node */
	node = pdev->dev.of_node;

	/* Allocate a private structure to manage this device */
	axi_adc_dev[dev_index] = kmalloc(sizeof(struct axi_adc_dev), GFP_KERNEL);
	if (axi_adc_dev[dev_index] == NULL)
	{
		dev_err(&pdev->dev, "Unable to allocate device structure.\n");
		return -ENOMEM;
	}
	memset(axi_adc_dev[dev_index], 0, sizeof(struct axi_adc_dev));

	axi_adc_dev[dev_index]->tx_chan = dma_request_slave_channel(&pdev->dev, "axidma0");
	if (IS_ERR(axi_adc_dev[dev_index]->tx_chan))
	{
		dev_err(&pdev->dev, "No DMA Tx channel\n");
		goto free_tx;
	}

	if (axi_adc_dev[dev_index]->tx_chan == NULL)
	{
		dev_err(&pdev->dev, "No DMA Tx channel\n");
		goto fail1;
	}

	/* IOMAP adc registers */
	axi_adc_dev[dev_index]->adc_virtaddr = of_iomap(node, 0);
	if (!axi_adc_dev[dev_index]->adc_virtaddr)
	{
		dev_err(&pdev->dev, "Unable to IOMAP dac9767 registers.\n");
		status = -ENOMEM;
		goto fail1;
	}

	axi_adc_dev[dev_index]->pdev = pdev;
	/* Initialize our device mutex */
	mutex_init(&axi_adc_dev[dev_index]->mutex);

	if (dev_index == 0)
	{

		status = alloc_chrdev_region(&devno, 0, AXI_ADC_MINOR_COUNT, MODULE_NAME);
		if (status < 0)
		{
			dev_err(&pdev->dev, "Unable to alloc chrdev.\n");
			goto fail2;
		}

		/* Register with the kernel as a character device */
		cdev_init(&adc_cdev, &axi_adc_fops);
		adc_cdev.owner = THIS_MODULE;
		adc_cdev.ops = &axi_adc_fops;
		status = cdev_add(&adc_cdev, devno, AXI_ADC_MINOR_COUNT);
		axi_adc_class = class_create(THIS_MODULE, MODULE_NAME);
	}

	// Creating device node for each ADC channel
	device_create(axi_adc_class, NULL,
				  MKDEV(MAJOR(devno), dev_index),
				  NULL, adc_channels[dev_index]);

	dev_info(&pdev->dev, "Xilinx PL dac9767 added successfully!\n");
	dev_index++;
	return SUCCESS;

fail2:
	iounmap(axi_adc_dev[dev_index]->adc_virtaddr);
free_tx:
	dma_release_channel(axi_adc_dev[dev_index]->tx_chan);
fail1:
	kfree(axi_adc_dev[dev_index]);
	return status;
}

static const struct of_device_id axi_adc_dma_of_ids[] =
	{
		{
			.compatible = "xlnx,axi-dac9767-dma",
		},
};

static struct platform_driver axi_adc_dma_of_driver =
	{
		.driver = {
			.name = MODULE_NAME,
			.owner = THIS_MODULE,
			.of_match_table = axi_adc_dma_of_ids,
		},
		.probe = axi_adc_probe,
		.remove = axi_adc_remove,
};

module_platform_driver(axi_adc_dma_of_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Xilinx AXI dac9767 DMA driver");
MODULE_AUTHOR("SSY.");
MODULE_VERSION("1.00a");

这就完成了驱动程序的创建,最终编译的驱动将在rootfs中的

/lib/modules/5.4.0/extra/ad9767.ko

ad9238的驱动添加同理,下面给出驱动代码

#include <asm/uaccess.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/delay.h>

#define MODULE_NAME             	    "axi_adc9238_dma"
#define AXI_ADC_MINOR_START             0
#define AXI_ADC_MINOR_COUNT         	16
#define AXI_ADC_CALLBACK_TIMEOUTMSEC 	10000
#define SUCCESS                 	    0
#define FAILURE                         -1
#define MAX_BUF_COUNT                   8
/* IOCTL defines */
#define AXI_ADC_IOCTL_BASE		        'W'
#define AXI_ADC_SET_SAMPLE_NUM	        _IO(AXI_ADC_IOCTL_BASE, 0)
#define AXI_ADC_SET_DMA_LEN_BYTES		_IO(AXI_ADC_IOCTL_BASE, 1)
#define AXI_ADC_DMA_INIT		        _IO(AXI_ADC_IOCTL_BASE, 2)
#define AXI_ADC_DMA_START		        _IO(AXI_ADC_IOCTL_BASE, 3)
#define AXI_ADC_DMA_DEINIT		        _IO(AXI_ADC_IOCTL_BASE, 4)

struct axi_adc_dev
{
	struct mutex mutex;
	struct platform_device *pdev;
	/* ADC Hardware device constants */
	void *adc_virtaddr;
	/* DMA stuff */
	struct dma_chan *rx_chan;
	dma_cookie_t rx_cookie;
	struct completion rx_cmp;
	unsigned long rx_tmo;
	int bd_cnt;
	struct scatterlist *rx_sg;
	/*DMA address of buffer */
	dma_addr_t *dma_dsts;
	u8 **dsts;
	int adc_sample_num;
	int dma_len_bytes;
};



/*ADC channel name */
static const char adc_channels[][20] =
{
	{"adc0"},
	{"adc1"},
	{"adc2"},
	{"adc3"},
	{"adc4"},
	{"adc5"},
	{"adc6"},
	{"adc7"},
	{"adc8"},
	{"adc9"},
	{"adc10"},
	{"adc11"},
	{"adc12"},
	{"adc13"},
	{"adc14"},
	{"adc15"},
};

static struct axi_adc_dev *axi_adc_dev[16];
static int dev_index = 0;
static dev_t devno;
static struct cdev adc_cdev;
static struct class *axi_adc_class;
static void dma_slave_rx_callback(void *completion)
{
	complete(completion);
}


/* File operations */
int axi_adc_dma_open(struct inode *inode, struct file *filp)
{

	unsigned int mn;
	mn = iminor(inode);
	/*Assign minor number for later reference */
	filp->private_data = (void *) mn;
	return SUCCESS;
}

int axi_adc_dma_release(struct inode *inode, struct file *filp)
{
	return SUCCESS;
}

ssize_t axi_adc_dma_read(struct file * filep, char __user * buf,
                         size_t count, loff_t * f_pos)
{
	int minor = 0, rx_tmo = 0, status = 0;

	struct dma_device *rx_dev = axi_adc_dev[minor]->rx_chan->device;
	/* Query minor number.
	 * @To be extended for multiple channel support
	 */
	minor = (int) filep->private_data;

	/* Validation for read size */
	if (count > axi_adc_dev[minor]->dma_len_bytes)
	{
		dev_err(&axi_adc_dev[minor]->pdev->dev, "Improper buffer size!\n");
		return EINVAL;
	}

	
	rx_tmo = wait_for_completion_timeout(&axi_adc_dev[minor]->rx_cmp,
	                                     axi_adc_dev[minor]->rx_tmo);
	/* Check the status of DMA channel */
	status =dma_async_is_tx_complete(axi_adc_dev[minor]->rx_chan,
	                                 axi_adc_dev[minor]->rx_cookie, NULL, NULL);
	if (rx_tmo == 0)
	{
		dev_err(&axi_adc_dev[minor]->pdev->dev, "RX test timed out.\n");
		return -EAGAIN;
	}
	dma_unmap_single(rx_dev->dev, axi_adc_dev[minor]->dma_dsts[0],axi_adc_dev[minor]->dma_len_bytes, DMA_DEV_TO_MEM);
	copy_to_user(buf, axi_adc_dev[minor]->dsts[0], count);
	return count;
}

/* IOCTL calls provide interface to configure ,start and stop
   DMA engine */
static long axi_adc_dma_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
{
	enum dma_status status;
	enum dma_ctrl_flags flags;
	int ret;
	int i;
	int minor = (int) file->private_data;
	struct adc_dma_cfg *cfg;
	struct dma_device *rx_dev = axi_adc_dev[minor]->rx_chan->device;
	struct dma_async_tx_descriptor *rxd = NULL;
	struct scatterlist rx_sg[MAX_BUF_COUNT];
	//dma_addr_t dma_dsts[bd_cnt];
	switch (cmd)
	{
	case AXI_ADC_SET_SAMPLE_NUM:
		axi_adc_dev[minor]->adc_sample_num = arg;
		break;
	case AXI_ADC_SET_DMA_LEN_BYTES:
		axi_adc_dev[minor]->dma_len_bytes = arg;
		break;
	case AXI_ADC_DMA_INIT:
		axi_adc_dev[minor]->bd_cnt = 1;
		axi_adc_dev[minor]->dsts = kcalloc(axi_adc_dev[minor]->bd_cnt+1, sizeof(u8 *), GFP_KERNEL);
		if (!axi_adc_dev[minor]->dsts) return ret;
		for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
		{
			axi_adc_dev[minor]->dsts[i] = kmalloc(axi_adc_dev[minor]->dma_len_bytes, GFP_KERNEL);
		}
		axi_adc_dev[minor]->dsts[i] = NULL;
		axi_adc_dev[minor]->dma_dsts = kcalloc(axi_adc_dev[minor]->bd_cnt+1, sizeof(dma_addr_t), GFP_KERNEL);
		flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
		for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
		{
			axi_adc_dev[minor]->dma_dsts[i] =
			    dma_map_single(rx_dev->dev,axi_adc_dev[minor]->dsts[i],
			                   axi_adc_dev[minor]->dma_len_bytes,DMA_MEM_TO_DEV);
		}

		break;
	case AXI_ADC_DMA_START:
		/* Start the DMA transaction */
		sg_init_table(rx_sg, axi_adc_dev[minor]->bd_cnt);
		for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
		{
			sg_dma_address(&rx_sg[i]) = axi_adc_dev[minor]->dma_dsts[i];
			sg_dma_len(&rx_sg[i]) = axi_adc_dev[minor]->dma_len_bytes;
		}
		rxd = rx_dev->device_prep_slave_sg(axi_adc_dev[minor]->rx_chan, rx_sg, axi_adc_dev[minor]->bd_cnt,
		                                   DMA_DEV_TO_MEM, flags, NULL);
		if (!rxd)
		{
			dev_err(&axi_adc_dev[minor]->pdev->dev, "RXD is NULL.\n");
			for (i = 0; i < axi_adc_dev[minor]->bd_cnt; i++)
				dma_unmap_single(rx_dev->dev, axi_adc_dev[minor]->dma_dsts[i],
				                 axi_adc_dev[minor]->dma_len_bytes,DMA_DEV_TO_MEM);
		}
		init_completion(&axi_adc_dev[minor]->rx_cmp);
		rxd->callback = dma_slave_rx_callback;
		rxd->callback_param = &axi_adc_dev[minor]->rx_cmp;
		axi_adc_dev[minor]->rx_cookie = rxd->tx_submit(rxd);

		if (dma_submit_error(axi_adc_dev[minor]->rx_cookie))
		{
			dev_err(&axi_adc_dev[minor]->pdev->dev, "DMA submit error.\n");
		}
		axi_adc_dev[minor]->rx_tmo =msecs_to_jiffies(AXI_ADC_CALLBACK_TIMEOUTMSEC); /* RX takes longer */
		dma_async_issue_pending(axi_adc_dev[minor]->rx_chan);
		writel(axi_adc_dev[minor]->adc_sample_num,axi_adc_dev[minor]->adc_virtaddr+4);
		writel(1,axi_adc_dev[minor]->adc_virtaddr);
		break;
	case AXI_ADC_DMA_DEINIT:
		break;
	default:
		return -EOPNOTSUPP;

	}
	return SUCCESS;
}

struct file_operations axi_adc_fops =
{
	.owner = THIS_MODULE,
	.read = axi_adc_dma_read,
	.open = axi_adc_dma_open,
	.unlocked_ioctl = axi_adc_dma_ioctl,
	.release = axi_adc_dma_release
};

static int axi_adc_remove(struct platform_device *pdev)
{
	int i;
	for(i = 0;i < dev_index;i++)
	{
		
		device_destroy(axi_adc_class,MKDEV(MAJOR(devno),i));

		/* Free up the DMA channel */
		dma_release_channel(axi_adc_dev[i]->rx_chan);

		/* Unmap the adc I/O memory */
		if (axi_adc_dev[i]->adc_virtaddr)
			iounmap(axi_adc_dev[i]->adc_virtaddr);


		if (axi_adc_dev[i])
		{
			kfree(axi_adc_dev[i]);
		}
		dev_info(&pdev->dev, "adc9238 DMA Unload :: Success!\n");
	}
	class_destroy(axi_adc_class);
	cdev_del(&adc_cdev);
	unregister_chrdev_region(devno, AXI_ADC_MINOR_COUNT);
	return SUCCESS;
}

static int axi_adc_probe(struct platform_device *pdev)
{
	int status = 0;

	struct device_node *node=NULL;

	/*Allocate device node */
	node = pdev->dev.of_node;

	/* Allocate a private structure to manage this device */
	axi_adc_dev[dev_index] = kmalloc(sizeof(struct axi_adc_dev), GFP_KERNEL);
	if (axi_adc_dev[dev_index] == NULL)
	{
		dev_err(&pdev->dev, "Unable to allocate device structure.\n");
		return -ENOMEM;
	}
	memset(axi_adc_dev[dev_index], 0, sizeof(struct axi_adc_dev));

	axi_adc_dev[dev_index]->rx_chan = dma_request_slave_channel(&pdev->dev, "axidma1");
	if (IS_ERR(axi_adc_dev[dev_index]->rx_chan))
	{
		dev_err(&pdev->dev, "No DMA Rx channel.\n");
		goto free_rx;
	}

	if (axi_adc_dev[dev_index]->rx_chan == NULL)
	{
		dev_err(&pdev->dev, "No DMA Rx channel.\n");
		goto fail1;
	}


	/* IOMAP adc registers */
	axi_adc_dev[dev_index]->adc_virtaddr = of_iomap(node, 0);
	if (!axi_adc_dev[dev_index]->adc_virtaddr)
	{
		dev_err(&pdev->dev, "Unable to IOMAP adc registers.\n");
		status = -ENOMEM;
		goto fail1;
	}


	axi_adc_dev[dev_index]->pdev = pdev;
	/* Initialize our device mutex */
	mutex_init(&axi_adc_dev[dev_index]->mutex);
	
	if(dev_index == 0)
	{

		status =alloc_chrdev_region(&devno,0, AXI_ADC_MINOR_COUNT,MODULE_NAME);
		if (status < 0)
		{
			dev_err(&pdev->dev, "Unable to alloc chrdev.\n");
			goto fail2;
		}

		/* Register with the kernel as a character device */
		cdev_init(&adc_cdev, &axi_adc_fops);
		adc_cdev.owner = THIS_MODULE;
		adc_cdev.ops = &axi_adc_fops;
		status = cdev_add(&adc_cdev,devno,AXI_ADC_MINOR_COUNT);
		axi_adc_class = class_create(THIS_MODULE, MODULE_NAME);
	
	}


	//Creating device node for each ADC channel
	device_create(axi_adc_class, NULL,
	              MKDEV(MAJOR(devno), dev_index),
	              NULL, adc_channels[dev_index]);


	dev_info(&pdev->dev, "Xilinx PL adc9238 added successfully.\n");
	dev_index++;
	return SUCCESS;

fail2:
	iounmap(axi_adc_dev[dev_index]->adc_virtaddr);
free_rx:
	dma_release_channel(axi_adc_dev[dev_index]->rx_chan);
fail1:
	kfree(axi_adc_dev[dev_index]);
	return status;
}

static const struct of_device_id axi_adc_dma_of_ids[] =
{
	{.compatible = "xlnx,axi-adc9238-dma",},
};

static struct platform_driver axi_adc_dma_of_driver =
{
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = axi_adc_dma_of_ids,
	},
	.probe = axi_adc_probe,
	.remove = axi_adc_remove,
  };

module_platform_driver(axi_adc_dma_of_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Xilinx AXI adc9238 DMA driver");
MODULE_AUTHOR("SSY, Inc.");
MODULE_VERSION("1.00a");

3. 编译并上机测试、编写自启动脚本

保存,下面可以进行编译、打包和拷贝了

petalinux-build
petalinux-package --boot --u-boot --fpga --fsbl --force

sudo cp ./images/linux/boot.scr /media/xlnxdev-2020-1/BOOT/
sudo cp ./images/linux/BOOT.BIN /media/xlnxdev-2020-1/BOOT/
sudo cp ./images/linux/image.ub /media/xlnxdev-2020-1/BOOT/

sudo rm -rf /media/xlnxdev-2020-1/root/*
sudo tar -zxvf ./images/linux/rootfs.tar.gz -C /media/xlnxdev-2020-1/root/

sudo sync

另外,我们还需要手动在sd卡第一分区创建autostart.sh,刚才创建的autostart app就是来执行这个脚本的,以便在开机时开机自动加载驱动。

autostart.sh

 #!/bin/bash 

echo "Executing autostart.sh"

insmod /lib/modules/5.4.0/extra/ad9767.ko
insmod /lib/modules/5.4.0/extra/ad9238.ko

现在插上sd卡开机

注意到上述内容,这是autostart安装驱动的内核提示。现在进入系统到/dev下看一下设备。
登录用户名和密码均为root。

cd /dev
ls


可以看到已经识别到设备了。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Simulink在Zynq开发软件无线电是一种高效的方式。Simulink是一种图形化编程环境,可以方便地进行软件无线电系统的建模、仿真和实现。 使用Simulink,开发者可以通过简单地拖拽和连接各种预定义的模块、功能模块和信号处理模块来设计无线电通信系统。这些模块可以是数字滤波器、混频器、调制器、解调器等等。同时,Simulink也支持自定义模块,可以根据具体需求进行开发Zynq是一款强大的可编程逻辑器件,集成了FPGA和ARM处理器。在Simulink,可以通过适配器模块将软件定义无线电系统与Zynq的硬件接口进行连接。这样,可以将无线电系统设计的模型直接部署到Zynq上。通过部署到FPGA上,可以实现高性能的信号处理和并行计算。 使用Simulink在Zynq开发软件无线电具有许多优势。首先,Simulink提供了一个直观的可视化编程界面,不需要繁琐的代码编写,降低了入门门槛。其次,Simulink具有强大的信号处理和仿真功能,可以进行全面的系统验证和性能优化。此外,由于Simulink可以与Zynq硬件进行无缝集成,可以充分发挥Zynq的高性能特点,提高系统的实时性和处理能力。 综上所述,使用Simulink在Zynq开发软件无线电是一种高效、灵活和可靠的方法。它可以提高开发效率,提供全面的系统验证和优化,同时充分发挥Zynq的硬件优势。这对于软件无线电开发者来说,是一种非常有吸引力的解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值