一、ADC子系统--创建设备文件
本部分包括创建设备节点和在应用层通过操作/dev目录下的设备节点来控制设备。
1.1、创建设备节点
在第一篇文章中的ADC子系统核心的注册函数中,调用了adc_dev_init();函数,该函数如下:
点击(此处)折叠或打开
- void __init adc_dev_init(void)
- {
- int err;
- err = alloc_chrdev_region(&adc_devt, 0, ADC_DEV_MAX, "adc");
- if (err < 0) {
- DBG("!!!!!!alloc chrdev region error!!!!!!\n");
- printk(KERN_ERR "%s: failed to allocate char dev region\n", __FILE__);
- }
- }
对应退出函数,调用adc_dev_exit();函数,函数如下:
点击(此处)折叠或打开
- void __exit adc_dev_exit(void)
- {
- if (adc_devt)
- unregister_chrdev_region(adc_devt, ADC_DEV_MAX);
- }
说明:
1、初始化函数主要为创建设备节点做准备。
2、退出函数是初始化函数的相反过程。
在第一篇文章的ADC子系统核心注册和注销函数中,调用了dev增加设备函数adc_dev_add_device()
和dev删除函数adc_dev_del_device(),程序如下:
点击(此处)折叠或打开
- void adc_dev_add_device(struct adc_core_dev *adc)
- {
- if (cdev_add(&adc->char_dev, adc->dev.devt, 1)) {
- DBG("!!!!!!cdev add error.!!!!!!\n");
- printk(KERN_WARNING "%s: failed to add char device %d:%d\n",
- adc->name, MAJOR(adc_devt), adc->id);
- }
- else {
- pr_debug("%s: dev (%d:%d)\n", adc->name, MAJOR(adc_devt), adc->id);
- }
- }
- void adc_dev_del_device(struct adc_core_dev *adc)
- {
- if (adc->dev.devt)
- cdev_del(&adc->char_dev);
- }
在ADC子系统核心注册函数中,调用了adc_dev_prepare()函数实现增加dev操作函数集,具体内容
如下:
点击(此处)折叠或打开
- static const struct file_operations adc_dev_fops = {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .unlocked_ioctl = adc_dev_ioctl,
- .open = adc_dev_open,
- .release = adc_dev_release,
- };
- void adc_dev_prepare(struct adc_core_dev *adc)
- {
- if (!adc_devt) {
- DBG("!!!!!!adc_devt = 0!!!!!!\n");
- return;
- }
- if (adc->id >= ADC_DEV_MAX) {
- DBG("!!!!!!adc dev prepare error,id too many!!!!!!\n");
- pr_debug("%s: too many ADC devices\n", adc->name);
- return;
- }
- adc->dev.devt = MKDEV(MAJOR(adc_devt), adc->id);
- cdev_init(&adc->char_dev, &adc_dev_fops);
- adc->char_dev.owner = adc->owner;
- }
说明:
1、首先对设备号进行检查。
2、计算设备号。
3、增加操作函数集,该函数集主要涉及到两个函数,一个打开函数adc_dev_open()和一个控制函
数adc_dev_ioctl(),首先看下打开函数:
点击(此处)折叠或打开
- static int adc_dev_open(struct inode *inode, struct file *file)
- {
- int err;
- struct adc_core_dev *adc = container_of(inode->i_cdev, struct adc_core_dev, char_dev);
- if (test_and_set_bit_lock(ADC_DEV_BUSY, &adc->flags))
- return -EBUSY;
- file->private_data = adc;
- err = adc->ops->open ? adc->ops->open(adc->dev.parent) : 0;
- if (err == 0) {
- return 0;
- }
- /* something has gone wrong */
- clear_bit_unlock(ADC_DEV_BUSY, &adc->flags);
- return err;
- }
说明:
1、使用container_of通过设备号获取在adc_device_register()函数中创建的adc-core结构体。
2、测试设备是否忙,如果忙,直接退出。如果不忙,设置忙标志。
3、判断adc-core的操作函数集是否包括open函数,通过第一篇文章我们知道,adc-core的函数
操作集只有一个转换函数,所以此处err直接等于0,退出。
命令控制函数adc_dev_ioctl()如下:
点击(此处)折叠或打开
- #define ADC_DEV_IOC_MAGIC 'a'
- #define ADC_DEV_IOC_MAXNR 2
- #define ADC_DEV_CON_PBAT _IOR(ADC_DEV_IOC_MAGIC, 0, int)
- #define ADC_DEV_CON_CHX _IOWR(ADC_DEV_IOC_MAGIC, 1, int)
- static long adc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
- {
- int err = 0, channel = 0;
- unsigned short adc_cmd = 0;
- struct adc_core_dev *adc = file->private_data;
- void __user *argp = (void __user *)arg;
- int __user *p = argp;
- err = mutex_lock_interruptible(&adc->ops_lock);
- if (err)
- return err;
- if ((_IOC_TYPE(cmd) != ADC_DEV_IOC_MAGIC) || (_IOC_NR(cmd) > ADC_DEV_IOC_MAXNR)) {
- err = -ENOTTY;
- goto exit;
- }
- switch(cmd) {
- case ADC_DEV_CON_PBAT:
- adc_cmd = CMD_AD_CON_PBAT;
- break;
- case ADC_DEV_CON_CHX:
- if (get_user(channel, p)) {
- err = -EFAULT;
- goto exit;
- }
- switch (channel) {
- case 0:
- adc_cmd = CMD_AD_CON_CH0;
- break;
- case 1:
- adc_cmd = CMD_AD_CON_CH1;
- break;
- case 2:
- adc_cmd = CMD_AD_CON_CH2;
- break;
- case 3:
- adc_cmd = CMD_AD_CON_CH3;
- break;
- }
- break;
- default:
- err = -ENOTTY;
- goto exit;
- }
- DBG("adc_cmd = 0x%x\n", adc_cmd);
- put_user(adc->ops->convert(adc_cmd), p);
- exit:
- mutex_unlock(&adc->ops_lock);
- return err;
- }
说明:
1、此处共有两条命令。
2、命令控制函数首先对命令有效性进行检查。
3、第一条是测量pbat命令,直接赋命令值。
4、第二条命令是对AD转换通道进行测量,此处包括一个通道值,所以先从应用层获取通道值。
5、最后调用adc-core操作函数集中的转换函数,也就是第一篇文章中的gsc3280AdcCon()进行AD转换。
6、将转换结果发送给应用层。
dev释放函数adc_dev_release()如下:
点击(此处)折叠或打开
- static int adc_dev_release(struct inode *inode, struct file *file)
- {
- struct adc_core_dev *adc = file->private_data;
- if (adc->ops->release)
- adc->ops->release(adc->dev.parent);
- clear_bit_unlock(ADC_DEV_BUSY, &adc->flags);
- return 0;
- }
说明:
1、判断adc-core的操作函数集是否包括release函数,通过第一篇文章我们知道,adc-core的函数操作集
只有一个转换函数,所以此处不执行if语句里面内容。
2、清除忙标志。
1.2、应用层测试程序
应用层测试程序如下:
点击(此处)折叠或打开
- #include <fcntl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/ioctl.h>
- #include <linux/ioctl.h>
- #define ADC_DEV_IOC_MAGIC 'a'
- #define ADC_DEV_IOC_MAXNR 2
- #define ADC_DEV_CON_PBAT _IOR(ADC_DEV_IOC_MAGIC, 0, int)
- #define ADC_DEV_CON_CHX _IOWR(ADC_DEV_IOC_MAGIC, 1, int)
- int main(int argc, char **argv)
- {
- //unsigned char buff[2] = {0};
- unsigned int idCmd = 0;
- int fd = 0, ret = 0, data = 0;
-
- //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK
- fd = open("/dev/adc0", 0);
- if (fd < 0) {
- printf("Open ADC Device Faild!\n");
- exit(1);
- }
- while(1) {
- data = 1;
- idCmd = ADC_DEV_CON_CHX;
- ret = ioctl(fd, idCmd, &data);
- if (ret != 0) {
- printf("Read ADC Device Faild!\n");
- break;
- } else {
- //data = buff[0] | (buff[1] << 8);
- printf("Read ADC value is: %d\n", data);
- }
- for (ret = 0; ret < 6553600; ret++)
- ;
- }
- close(fd);
- return 0;
- }
说明:
1、首先打开设备
2、设置通道1和采集通道命令,调用ioctl实现AD采集
3、如果采集正确,打印数据
4、延时后,循环再一次采集数据,具体测试图如下:
二、sysfs文件系统和测试
2.1、sysfs文件系统
在第一篇文章中的ADC子系统核心的注册函数中,调用了adc_sysfs_init();函数,该函数如下:
点击(此处)折叠或打开
- void __init adc_sysfs_init(struct class *adc_class)
- {
- adc_class->dev_attrs = adc_attrs;
- }
该函数增加了一个device_attribute属性--adc_attrs,device_attribute属性将在第三篇文章讲述,
adc_attrs具体内容如下:
点击(此处)折叠或打开
- #define to_adc_device(d) container_of(d, struct adc_core_dev, dev)
- /* device attributes */
- static ssize_t
- adc_sysfs_show_name(struct device *dev, struct device_attribute *attr, char *buf)
- {
- return sprintf(buf, "%s\n", to_adc_device(dev)->name);
- }
- static ssize_t
- adc_sysfs_show_pbat(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- int result = 0;
- struct adc_core_dev *adc = to_adc_device(dev);
- result = adc->ops->convert(CMD_AD_CON_PBAT);
- printk(KERN_INFO "pbat = %d\n", result);
- sprintf(buf, "%d\n", result);
- return 0;
- }
- static ssize_t
- adc_sysfs_show_ch0(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- int result = 0;
- struct adc_core_dev *adc = to_adc_device(dev);
- result = adc->ops->convert(CMD_AD_CON_CH0);
- printk(KERN_INFO "ch0 = %d\n", result);
- sprintf(buf, "%d\n", result);
- return 0;
- }
- static ssize_t
- adc_sysfs_show_ch1(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- int result = 0;
- struct adc_core_dev *adc = to_adc_device(dev);
- result = adc->ops->convert(CMD_AD_CON_CH1);
- printk(KERN_INFO "ch1 = %d\n", result);
- sprintf(buf, "%d\n", result);
- return 0;
- }
- static ssize_t
- adc_sysfs_show_ch2(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- int result = 0;
- struct adc_core_dev *adc = to_adc_device(dev);
- result = adc->ops->convert(CMD_AD_CON_CH2);
- printk(KERN_INFO "ch2 = %d\n", result);
- sprintf(buf, "%d\n", result);
- return 0;
- }
- static ssize_t
- adc_sysfs_show_ch3(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- int result = 0;
- struct adc_core_dev *adc = to_adc_device(dev);
- result = adc->ops->convert(CMD_AD_CON_CH3);
- printk(KERN_INFO "ch3 = %d\n", result);
- sprintf(buf, "%d\n", result);
- return 0;
- }
- static struct device_attribute adc_attrs[] = {
- __ATTR(name, S_IRUGO, adc_sysfs_show_name, NULL),
- __ATTR(pbat, S_IRUGO, adc_sysfs_show_pbat, NULL),
- __ATTR(ch0, S_IRUGO, adc_sysfs_show_ch0, NULL),
- __ATTR(ch1, S_IRUGO, adc_sysfs_show_ch1, NULL),
- __ATTR(ch2, S_IRUGO, adc_sysfs_show_ch2, NULL),
- __ATTR(ch3, S_IRUGO, adc_sysfs_show_ch3, NULL),
- { },
- };
由以上程序可知,该属性成员总共有六个成员,通过这种赋值,系统起来后,在根文件目录
/sys/class/adc/adc0下就有相应的节点,操作相应的节点,就相当于调相应的函数,比如操作节
点pbat,则调用函数adc_sysfs_show_pbat()。其他节点类似。
各个节点函数类似,首先都是获取adc-core结构体,然后根据节点内容不同,赋值不同命令,
最后调用adc-core中的函数操作集中的转换函数实现AD转换。
2.2、应用层测试
应用层测试采用命令行的方式,具体如下图:
三、proc文件系统和测试
3.1、proc文件系统
在ADC子系统注册函数adc_device_register()中,调用了adc-proc.c中的adc_proc_add_device(adc);
函数,具体内容如下:
点击(此处)折叠或打开
- static const struct file_operations adc_proc_fops = {
- .open = adc_proc_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = adc_proc_release,
- };
- void adc_proc_add_device(struct adc_core_dev *adc)
- {
- if (adc->id == 0)
- proc_create_data("driver/adc", 0, NULL, &adc_proc_fops, adc);
- }
- void adc_proc_del_device(struct adc_core_dev *adc)
- {
- if (adc->id == 0)
- remove_proc_entry("driver/adc", NULL);
- }
说明:
1、使用proc_create_data()函数创建了函数操作函数集adc_proc_fops。
2、adc_proc_del_device()函数在ADC子系统注销函数中调用,是adc_proc_add_device()函数的相反过程。
3、操作函数集adc_proc_fops涉及的函数如下:
点击(此处)折叠或打开
- static int adc_proc_show(struct seq_file *seq, void *offset)
- {
- int result = 0;
- struct adc_core_dev *adc = seq->private;
-
- result = adc->ops->convert(CMD_AD_CON_PBAT);
- seq_printf(seq, "PBAT:%d\n", result);
- result = adc->ops->convert(CMD_AD_CON_CH0);
- seq_printf(seq, "CH0:%d\n", result);
- result = adc->ops->convert(CMD_AD_CON_CH1);
- seq_printf(seq, "CH1:%d\n", result);
- result = adc->ops->convert(CMD_AD_CON_CH2);
- seq_printf(seq, "CH2:%d\n", result);
- result = adc->ops->convert(CMD_AD_CON_CH3);
- seq_printf(seq, "CH3:%d\n", result);
- if (adc->ops->proc)
- adc->ops->proc(adc->dev.parent, seq);
- return 0;
- }
- static int adc_proc_open(struct inode *inode, struct file *file)
- {
- int ret;
- struct adc_core_dev *adc = PDE(inode)->data;
- if (!try_module_get(THIS_MODULE)) {
- DBG("!!!!!!try_module_get error!!!!!!\n");
- return -ENODEV;
- }
- ret = single_open(file, adc_proc_show, adc);
- if (ret)
- module_put(THIS_MODULE);
- return ret;
- }
- static int adc_proc_release(struct inode *inode, struct file *file)
- {
- int res = single_release(inode, file);
- module_put(THIS_MODULE);
- return res;
- }
说明:
1、open函数中首先获得adc-core设备结构体,使用single_open()函数调用adc_proc_show()函数。
2、释放函数调用single_release()释放设备。
3、adc_proc_show()函数中,使用adc-core的操作函数集的AD转换函数,实现AD转换,也就是调用
第一篇文章中设备驱动的gsc3280AdcCon()函数。
4、gsc3280AdcCon()分别实现5路AD的转换。
3.2、应用层测试
应用层测试采用命令行的方式,具体如下图:
四、总结
在调试过程中出现过内存泄露的问题,经过调试发现是在系统启动过程中,根据模块启动宏的不同,初始化
是有顺序的。把先使用的模块先初始化,比如subsys_initcall(gsc_adc_init);。
ADC核心使底层硬件对用户来说是透明的,并且减少了编写驱动程序的工作量。ADC新的驱动接口提供了更
多的功能,使系统可以同时存在多个ADC。 /dev,sysfs,proc这三种机制的实现使得应用程序能灵活的使用ADC。
ADC核心代码的组织方式值得学习,不同功能的代码放在不同的文件中,简单明了。
如果系统有另外一个AD转换芯片,那么只需新增加设备驱动程序,在新的设备驱动程序中,使用函数
adc_device_register() 注册ADC子系统,通过idr的id即可区分不同的ADC设备。