技术文摘

GSC3280的ADC子系统驱动模型(二)












一、ADC子系统--创建设备文件

本部分包括创建设备节点和在应用层通过操作/dev目录下的设备节点来控制设备。

1.1、创建设备节点

在第一篇文章中的ADC子系统核心的注册函数中,调用了adc_dev_init();函数,该函数如下:

1.  void __init adc_dev_init(void)

2.  {

3.      int err;

4.   

5.      err = alloc_chrdev_region(&adc_devt, 0, ADC_DEV_MAX, "adc");

6.      if (err < 0) {

7.          DBG("!!!!!!alloc chrdev region error!!!!!!\n");

8.          printk(KERN_ERR "%s: failed to allocate char dev region\n", __FILE__);

9.      }

10.}


对应退出函数,调用adc_dev_exit();函数,函数如下:
1.  void __exit adc_dev_exit(void)
2.  {
3.      if (adc_devt)
4.          unregister_chrdev_region(adc_devt, ADC_DEV_MAX);
5.  }

说明:

1、初始化函数主要为创建设备节点做准备。

2、退出函数是初始化函数的相反过程。

在第一篇文章的ADC子系统核心注册和注销函数中,调用了dev增加设备函数adc_dev_add_device()和dev删除函数adc_dev_del_device(),程序如下:

1.  void adc_dev_add_device(struct adc_core_dev *adc)

2.  {

3.      if (cdev_add(&adc->char_dev, adc->dev.devt, 1)) {

4.          DBG("!!!!!!cdev add error.!!!!!!\n");

5.          printk(KERN_WARNING "%s: failed to add char device %d:%d\n",

6.                  adc->name, MAJOR(adc_devt), adc->id);

7.      }

8.      else {

9.          pr_debug("%s: dev (%d:%d)\n", adc->name, MAJOR(adc_devt), adc->id);

10.    }

11.}

12. 

13.void adc_dev_del_device(struct adc_core_dev *adc)

14.{

15.    if (adc->dev.devt)

16.        cdev_del(&adc->char_dev);

17.}


在ADC子系统核心注册函数中,调用了adc_dev_prepare()函数实现增加dev操作函数集,具体内容如下:

1.  static const struct file_operations adc_dev_fops = {

2.      .owner            = THIS_MODULE,

3.      .llseek            = no_llseek,

4.      .unlocked_ioctl    = adc_dev_ioctl,

5.      .open            = adc_dev_open,

6.      .release            = adc_dev_release,

7.  };

8.   

9.  void adc_dev_prepare(struct adc_core_dev *adc)

10.{

11.    if (!adc_devt) {

12.        DBG("!!!!!!adc_devt = 0!!!!!!\n");

13.        return;

14.    }

15.    if (adc->id >= ADC_DEV_MAX) {

16.        DBG("!!!!!!adc dev prepare error,id too many!!!!!!\n");

17.        pr_debug("%s: too many ADC devices\n", adc->name);

18.        return;

19.    }

20.    adc->dev.devt = MKDEV(MAJOR(adc_devt), adc->id);

21.    cdev_init(&adc->char_dev, &adc_dev_fops);

22.    adc->char_dev.owner = adc->owner;

23.}


说明:

1、首先对设备号进行检查。

2、计算设备号。

3、增加操作函数集,该函数集主要涉及到两个函数,一个打开函数adc_dev_open()和一个控制函数adc_dev_ioctl(),首先看下打开函数:

1.  static int adc_dev_open(struct inode *inode, struct file *file)

2.  {

3.      int err;

4.      struct adc_core_dev *adc = container_of(inode->i_cdev, struct adc_core_dev, char_dev);

5.   

6.      if (test_and_set_bit_lock(ADC_DEV_BUSY, &adc->flags))

7.          return -EBUSY;

8.      file->private_data = adc;

9.      err = adc->ops->open ? adc->ops->open(adc->dev.parent) : 0;

10.    if (err == 0) {

11.        return 0;

12.    }

13.    /* something has gone wrong */

14.    clear_bit_unlock(ADC_DEV_BUSY, &adc->flags);

15.    return err;

16.}


说明:

1、使用container_of通过设备号获取在adc_device_register()函数中创建的adc-core结构体。

2、测试设备是否忙,如果忙,直接退出。如果不忙,设置忙标志。

3、判断adc-core的操作函数集是否包括open函数,通过第一篇文章我们知道,adc-core的函数操作集只有一个转换函数,所以此处err直接等于0,退出。

命令控制函数adc_dev_ioctl()如下:

1.  #define ADC_DEV_IOC_MAGIC            'a'

2.  #define ADC_DEV_IOC_MAXNR            2

3.  #define ADC_DEV_CON_PBAT            _IOR(ADC_DEV_IOC_MAGIC, 0, int)

4.  #define ADC_DEV_CON_CHX            _IOWR(ADC_DEV_IOC_MAGIC, 1, int)

5.  static long adc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

6.  {

7.      int err = 0, channel = 0;

8.      unsigned short adc_cmd = 0;

9.      struct adc_core_dev *adc = file->private_data;

10.    void __user *argp = (void __user *)arg;

11.    int __user *p = argp;

12. 

13.    err = mutex_lock_interruptible(&adc->ops_lock);

14.    if (err)

15.        return err;

16.    if ((_IOC_TYPE(cmd) != ADC_DEV_IOC_MAGIC) || (_IOC_NR(cmd) > ADC_DEV_IOC_MAXNR)) {

17.        err = -ENOTTY;

18.        goto exit;

19.    }

20.    switch(cmd) {

21.    case ADC_DEV_CON_PBAT:

22.        adc_cmd = CMD_AD_CON_PBAT;

23.        break;

24.    case ADC_DEV_CON_CHX:

25.        if (get_user(channel, p)) {

26.            err = -EFAULT;

27.            goto exit;

28.        }

29.        switch (channel) {

30.        case 0:

31.            adc_cmd = CMD_AD_CON_CH0;

32.            break;

33.        case 1:

34.            adc_cmd = CMD_AD_CON_CH1;

35.            break;

36.        case 2:

37.            adc_cmd = CMD_AD_CON_CH2;

38.            break;

39.        case 3:

40.            adc_cmd = CMD_AD_CON_CH3;

41.            break;

42.        }

43.        break;

44.    default:

45.        err = -ENOTTY;

46.        goto exit;

47.    }

48.    DBG("adc_cmd = 0x%x\n", adc_cmd);

49.    put_user(adc->ops->convert(adc_cmd), p);

50. 

51.exit:

52.    mutex_unlock(&adc->ops_lock);

53.    return err;

54.}

说明:
1、此处共有两条命令。
2、命令控制函数首先对命令有效性进行检查。
3、第一条是测量pbat命令,直接赋命令值。
4、第二条命令是对AD转换通道进行测量,此处包括一个通道值,所以先从应用层获取通道值。
5、最后调用adc-core操作函数集中的转换函数,也就是第一篇文章中的gsc3280AdcCon()进行AD转换。
6、将转换结果发送给应用层。

dev释放函数adc_dev_release()如下:

1.  static int adc_dev_release(struct inode *inode, struct file *file)

2.  {

3.      struct adc_core_dev *adc = file->private_data;

4.   

5.      if (adc->ops->release)

6.          adc->ops->release(adc->dev.parent);

7.      clear_bit_unlock(ADC_DEV_BUSY, &adc->flags);

8.      return 0;

9.  }


说明:

1、判断adc-core的操作函数集是否包括release函数,通过第一篇文章我们知道,adc-core的函数操作集只有一个转换函数,所以此处不执行if语句里面内容。

2、清除忙标志。







1.2、应用层测试程序

应用层测试程序如下:

1.  #include <fcntl.h>

2.  #include <stdio.h>

3.  #include <stdlib.h>

4.  #include <sys/ioctl.h>

5.  #include <linux/ioctl.h>

6.   

7.  #define ADC_DEV_IOC_MAGIC            'a'

8.  #define ADC_DEV_IOC_MAXNR            2

9.  #define ADC_DEV_CON_PBAT            _IOR(ADC_DEV_IOC_MAGIC, 0, int)

10.#define ADC_DEV_CON_CHX            _IOWR(ADC_DEV_IOC_MAGIC, 1, int)

11.int main(int argc, char **argv)

12.{

13.    //unsigned char buff[2] = {0};

14.    unsigned int idCmd = 0;

15.    int fd = 0, ret = 0, data = 0;

16.    

17.    //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK

18.    fd = open("/dev/adc0", 0);

19.    if (fd < 0) {

20.        printf("Open ADC Device Faild!\n");

21.        exit(1);

22.    }

23.    while(1) {

24.        data = 1;

25.        idCmd = ADC_DEV_CON_CHX;

26.        ret = ioctl(fd, idCmd, &data);

27.        if (ret != 0) {

28.            printf("Read ADC Device Faild!\n");

29.            break;

30.        } else {

31.            //data = buff[0] | (buff[1] << 8);

32.            printf("Read ADC value is: %d\n", data);

33.        }

34.        for (ret = 0; ret < 6553600; ret++)

35.            ; 

36.    }

37.    close(fd);

38.    return 0;

39.}

说明:
1、首先打开设备
2、设置通道1和采集通道命令,调用ioctl实现AD采集
3、如果采集正确,打印数据
4、延时后,循环再一次采集数据,具体测试图如下:


应用层测试程序.png







二、sysfs文件系统和测试

2.1、sysfs文件系统

在第一篇文章中的ADC子系统核心的注册函数中,调用了adc_sysfs_init();函数,该函数如下:
1.  void __init adc_sysfs_init(struct class *adc_class)
2.  {
3.      adc_class->dev_attrs = adc_attrs;
4.  }


该函数增加了一个device_attribute属性--adc_attrs,device_attribute属性将在第三篇文章讲述,adc_attrs具体内容如下:

1.  #define to_adc_device(d) container_of(d, struct adc_core_dev, dev)

2.  /* device attributes */

3.  static ssize_t

4.  adc_sysfs_show_name(struct device *dev, struct device_attribute *attr, char *buf)

5.  {

6.      return sprintf(buf, "%s\n", to_adc_device(dev)->name);

7.  }

8.   

9.  static ssize_t

10.adc_sysfs_show_pbat(struct device *dev, struct device_attribute *attr,

11.        char *buf)

12.{

13.    int result = 0;

14.    struct adc_core_dev *adc = to_adc_device(dev);

15. 

16.    result = adc->ops->convert(CMD_AD_CON_PBAT);

17.    printk(KERN_INFO "pbat = %d\n", result);

18.    sprintf(buf, "%d\n", result);

19.    return 0;

20.}

21. 

22.static ssize_t

23.adc_sysfs_show_ch0(struct device *dev, struct device_attribute *attr,

24.        char *buf)

25.{

26.    int result = 0;

27.    struct adc_core_dev *adc = to_adc_device(dev);

28. 

29.    result = adc->ops->convert(CMD_AD_CON_CH0);

30.    printk(KERN_INFO "ch0 = %d\n", result);

31.    sprintf(buf, "%d\n", result);

32.    return 0;

33.}

34. 

35.static ssize_t

36.adc_sysfs_show_ch1(struct device *dev, struct device_attribute *attr,

37.        char *buf)

38.{

39.    int result = 0;

40.    struct adc_core_dev *adc = to_adc_device(dev);

41. 

42.    result = adc->ops->convert(CMD_AD_CON_CH1);

43.    printk(KERN_INFO "ch1 = %d\n", result);

44.    sprintf(buf, "%d\n", result);

45.    return 0;

46.}

47. 

48.static ssize_t

49.adc_sysfs_show_ch2(struct device *dev, struct device_attribute *attr,

50.        char *buf)

51.{

52.    int result = 0;

53.    struct adc_core_dev *adc = to_adc_device(dev);

54. 

55.    result = adc->ops->convert(CMD_AD_CON_CH2);

56.    printk(KERN_INFO "ch2 = %d\n", result);

57.    sprintf(buf, "%d\n", result);

58.    return 0;

59.}

60. 

61.static ssize_t

62.adc_sysfs_show_ch3(struct device *dev, struct device_attribute *attr,

63.        char *buf)

64.{

65.    int result = 0;

66.    struct adc_core_dev *adc = to_adc_device(dev);

67. 

68.    result = adc->ops->convert(CMD_AD_CON_CH3);

69.    printk(KERN_INFO "ch3 = %d\n", result);

70.    sprintf(buf, "%d\n", result);

71.    return 0;

72.}

73. 

74.static struct device_attribute adc_attrs[] = {

75.    __ATTR(name, S_IRUGO, adc_sysfs_show_name, NULL),

76.    __ATTR(pbat, S_IRUGO, adc_sysfs_show_pbat, NULL),

77.    __ATTR(ch0, S_IRUGO, adc_sysfs_show_ch0, NULL),

78.    __ATTR(ch1, S_IRUGO, adc_sysfs_show_ch1, NULL),

79.    __ATTR(ch2, S_IRUGO, adc_sysfs_show_ch2, NULL),

80.    __ATTR(ch3, S_IRUGO, adc_sysfs_show_ch3, NULL),

81.    { },

82.};


由以上程序可知,该属性成员总共有六个成员,通过这种赋值,系统起来后,在根文件目录/sys/class/adc/adc0下就有相应的节点,操作相应的节点,就相当于调相应的函数,比如操作节点pbat,则调用函数adc_sysfs_show_pbat()。其他节点类似。

各个节点函数类似,首先都是获取adc-core结构体,然后根据节点内容不同,赋值不同命令,最后调用adc-core中的函数操作集中的转换函数实现AD转换。







2.2、应用层测试

应用层测试采用命令行的方式,具体如下图:

应用层测试程序2.png







三、proc文件系统和测试

3.1、proc文件系统

在ADC子系统注册函数adc_device_register()中,调用了adc-proc.c中的adc_proc_add_device(adc);

函数,具体内容如下:
1.  static const struct file_operations adc_proc_fops = {

2.      .open    = adc_proc_open,

3.      .read    = seq_read,

4.      .llseek    = seq_lseek,

5.      .release    = adc_proc_release,

6.  };

7.   

8.  void adc_proc_add_device(struct adc_core_dev *adc)

9.  {

10.    if (adc->id == 0)

11.        proc_create_data("driver/adc", 0, NULL, &adc_proc_fops, adc);

12.}

13. 

14.void adc_proc_del_device(struct adc_core_dev *adc)

15.{

16.    if (adc->id == 0)

17.        remove_proc_entry("driver/adc", NULL);

18.}


说明:

1、使用proc_create_data()函数创建了函数操作函数集adc_proc_fops。

2、adc_proc_del_device()函数在ADC子系统注销函数中调用,是adc_proc_add_device()函数的相反过程。

3、操作函数集adc_proc_fops涉及的函数如下:
1.  static int adc_proc_show(struct seq_file *seq, void *offset)

2.  {

3.      int result = 0;

4.      struct adc_core_dev *adc = seq->private;

5.      

6.      result = adc->ops->convert(CMD_AD_CON_PBAT);

7.      seq_printf(seq, "PBAT:%d\n", result);

8.      result = adc->ops->convert(CMD_AD_CON_CH0);

9.      seq_printf(seq, "CH0:%d\n", result);

10.    result = adc->ops->convert(CMD_AD_CON_CH1);

11.    seq_printf(seq, "CH1:%d\n", result);

12.    result = adc->ops->convert(CMD_AD_CON_CH2);

13.    seq_printf(seq, "CH2:%d\n", result);

14.    result = adc->ops->convert(CMD_AD_CON_CH3);

15.    seq_printf(seq, "CH3:%d\n", result);

16.    if (adc->ops->proc)

17.        adc->ops->proc(adc->dev.parent, seq);

18.    return 0;

19.}

20. 

21.static int adc_proc_open(struct inode *inode, struct file *file)

22.{

23.    int ret;

24.    struct adc_core_dev *adc = PDE(inode)->data;

25. 

26.    if (!try_module_get(THIS_MODULE)) {

27.        DBG("!!!!!!try_module_get error!!!!!!\n");

28.        return -ENODEV;

29.    }

30.    ret = single_open(file, adc_proc_show, adc);

31.    if (ret)

32.        module_put(THIS_MODULE);

33.    return ret;

34.}

35. 

36.static int adc_proc_release(struct inode *inode, struct file *file)

37.{

38.    int res = single_release(inode, file);

39.    module_put(THIS_MODULE);

40.    return res;

41.}


说明:       

1、open函数中首先获得adc-core设备结构体,使用single_open()函数调用dc_proc_show()函数。

2、释放函数调用single_release()释放设备。

3、adc_proc_show()函数中,使用adc-core的操作函数集的AD转换函数,实现AD转换,也就是调用第一篇文章中设备驱动的gsc3280AdcCon()函数。

 4、gsc3280AdcCon()分别实现5路AD的转换。







3.2、应用层测试

应用层测试采用命令行的方式,具体如下图:

应用层测试程序3.png







四、总结

在调试过程中出现过内存泄露的问题,经过调试发现是在系统启动过程中,根据模块启动宏的不同,初始化是有顺序的。把先使用的模块先初始化,比如subsys_initcall(gsc_adc_init);。        

ADC核心使底层硬件对用户来说是透明的,并且减少了编写驱动程序的工作量。ADC新的驱动接口提供了更多的功能,使系统可以同时存在多个ADC。/dev,sysfs,proc这三种机制的实现使得应用程序能灵活的使用ADC。

ADC核心代码的组织方式值得学习,不同功能的代码放在不同的文件中,简单明了。

如果系统有另外一个AD转换芯片,那么只需新增加设备驱动程序,在新的设备驱动程序中,使用函数adc_device_register() 注册ADC子系统,通过idr的id即可区分不同的ADC设备。




原文参见:http://blog.chinaunix.net/uid-25445243-id-4011337.html