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
可以看到已经识别到设备了。