基于S3C2440的Linux-3.6.6移植——ADC的移植

时间:2021-02-17 14:51:28

在linux-3.6.6中,系统已经有了关于s3c2440的ADC通用驱动程序文件——arch/arm/plat-samsung/adc.c,但还没有应用层文件,如果要想使ADC工作,并利用系统已有的驱动文件,那么就必须自己动手写一个应用层文件。

 

本文先介绍adc.c,然后给出一个通用的ADC应用层文件,并移植到开发板上。

 

先看adc.c文件中的初始化函数adc_init:

static int __init adc_init(void)

{

       int ret;

       //注册ADC驱动

       ret = platform_driver_register(&s3c_adc_driver);

       if (ret)

                printk(KERN_ERR "%s:failed to add adc driver\n", __func__);

 

       return ret;

}

 

s3c_adc_driver的定义为:

static struct platform_driver s3c_adc_driver = {

       .id_table       = s3c_adc_driver_ids,

       .driver         = {

                .name   = "s3c-adc",

                .owner  = THIS_MODULE,

                .pm     = &adc_pm_ops,

       },

       .probe          = s3c_adc_probe,

       .remove         = __devexit_p(s3c_adc_remove),

};

 

s3c_adc_driver_ids的定义为:

static struct platform_device_id s3c_adc_driver_ids[] = {

       {

                .name           = "s3c24xx-adc",

                .driver_data    = TYPE_ADCV1,

       }, {

                .name           = "s3c2443-adc",

                .driver_data    = TYPE_ADCV11,

       }, {

                .name           = "s3c2416-adc",

                .driver_data    = TYPE_ADCV12,

       }, {

                .name           = "s3c64xx-adc",

                .driver_data    = TYPE_ADCV2,

       }, {

                .name           = "samsung-adc-v3",

                .driver_data    = TYPE_ADCV3,

       },

       { }

};

 

下面重点介绍probe函数s3c_adc_probe

static int s3c_adc_probe(struct platform_device *pdev)

{

       struct device *dev = &pdev->dev;

       struct adc_device *adc;

       struct resource *regs;

       enum s3c_cpu_typecpu = platform_get_device_id(pdev)->driver_data;

       int ret;

       unsigned tmp;

 

       //在内存中开辟一段空间给ADC设备——adc_device

       adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL);

       if (adc == NULL) {

                dev_err(dev, "failed toallocate adc_device\n");

                return -ENOMEM;

       }

 

       spin_lock_init(&adc->lock);

 

       adc->pdev = pdev;

       //为ADC设置预分频

       adc->prescale = S3C2410_ADCCON_PRSCVL(49);

 

       //得到ADC的调整器

       adc->vdd = regulator_get(dev, "vdd");

       if (IS_ERR(adc->vdd)) {

                dev_err(dev, "operatingwithout regulator \"vdd\" .\n");

                ret = PTR_ERR(adc->vdd);

                goto err_alloc;

       }

 

       //得到ADC的中断号

       adc->irq = platform_get_irq(pdev, 1);

       if (adc->irq <= 0) {

                dev_err(dev, "failed toget adc irq\n");

                ret = -ENOENT;

                goto err_reg;

       }

 

       //申请中断,中断处理函数为s3c_adc_irq

       ret = request_irq(adc->irq, s3c_adc_irq, 0, dev_name(dev), adc);

       if (ret < 0) {

                dev_err(dev, "failed toattach adc irq\n");

                goto err_reg;

       }

 

       //得到ADC时钟

       adc->clk = clk_get(dev, "adc");

       if (IS_ERR(adc->clk)) {

                dev_err(dev, "failed toget adc clock\n");

                ret = PTR_ERR(adc->clk);

                goto err_irq;

       }

 

       //得到ADC的IO内存资源

       regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);

       if (!regs) {

                dev_err(dev, "failed tofind registers\n");

               ret = -ENXIO;

                goto err_clk;

       }

 

       //得到虚拟地址

       adc->regs = ioremap(regs->start, resource_size(regs));

       if (!adc->regs) {

                dev_err(dev, "failed tomap registers\n");

                ret = -ENXIO;

                goto err_clk;

       }

 

       //使能调整器

       ret = regulator_enable(adc->vdd);

       if (ret)

                goto err_ioremap;

 

       //使能时钟

       clk_enable(adc->clk);

 

       //设置预分频

       tmp = adc->prescale | S3C2410_ADCCON_PRSCEN;

 

       /* Enable 12-bit ADC resolution */

       if (cpu == TYPE_ADCV12)

                tmp |= S3C2416_ADCCON_RESSEL;

       if (cpu == TYPE_ADCV2 || cpu == TYPE_ADCV3)

                tmp |= S3C64XX_ADCCON_RESSEL;

 

       writel(tmp, adc->regs + S3C2410_ADCCON);

 

       dev_info(dev, "attached adc driver\n");

 

       platform_set_drvdata(pdev, adc);

       adc_dev = adc;

 

       return 0;

 

 err_ioremap:

       iounmap(adc->regs);

 err_clk:

       clk_put(adc->clk);

 

 err_irq:

       free_irq(adc->irq, adc);

 err_reg:

       regulator_put(adc->vdd);

 err_alloc:

       kfree(adc);

       return ret;

}

 

要想使用系统自带的ADC驱动,首先要利用s3c_adc_register函数注册ADC客户:

struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,

                                        void (*select)(struct s3c_adc_client *client,

                                                      unsigned int selected),

                                        void(*conv)(struct s3c_adc_client*client,

                                                     unsigned d0,unsigned d1,

                                                    unsigned *samples_left),

                                       unsigned int is_ts)

{

       struct s3c_adc_client*client;

 

       WARN_ON(!pdev);

 

       //如果没有指定select回调函数,则指定一个默认的函数

       if (!select)

                select = s3c_adc_default_select;

 

       if (!pdev)

                return ERR_PTR(-EINVAL);

 

       //在内存中开辟一段空间给s3c_adc_client

       client = kzalloc(sizeof(struct s3c_adc_client), GFP_KERNEL);

       if (!client) {

                dev_err(&pdev->dev,"no memory for adc client\n");

                return ERR_PTR(-ENOMEM);

       }

 

       client->pdev = pdev;

       //指定该ADC客户是否是触摸屏,is_ts为1表示是触摸屏,为0表示不是触摸屏

       client->is_ts = is_ts;             

       client->select_cb = select;

       client->convert_cb = conv;

 

       return client;

}

从上面的介绍可以看出,s3c_adc_register函数的主要目的是为select_cb和convert_cb两个回调函数赋值。在adc.c文件内,分别给出了select_cb的回调函数——s3c_adc_default_select,和convert_cb的回调函数——s3c_convert_done。s3c_adc_default_select是空函数,s3c_convert_done主要作用是唤醒等待队列中的休眠进程。

 

当要读取经ADC转换后的数据时,可以调用s3c_adc_read函数,它的第二个参数表示选择第几个ADC通道:

int s3c_adc_read(structs3c_adc_client *client,unsigned int ch)

{

       //定义等待队列头

       DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);

       int ret;

 

       client->convert_cb = s3c_convert_done;

       client->wait = &wake;

       client->result = -1;

 

       //调用s3c_adc_start函数,第二个参数为ADC通道,第三个参数表示ADC的采样个数

       ret = s3c_adc_start(client,ch, 1);

       if (ret < 0)

                goto err;

 

       //等待休眠,当client->result>= 0或休眠时间大于HZ / 2时,休眠结束

       //前面提到的s3c_convert_done函数可以使client->result >= 0,并且唤醒休眠

       ret = wait_event_timeout(wake, client->result >= 0, HZ / 2);

       if (client->result < 0) {

               ret = -ETIMEDOUT;

                goto err;

       }

 

       client->convert_cb = NULL;

       return client->result;

 

err:

       return ret;

}

 

来看看s3c_adc_start函数:

int s3c_adc_start(structs3c_adc_client *client,

                  unsigned int channel,unsigned int nr_samples)

{

       struct adc_device *adc = adc_dev;

       unsigned long flags;

 

       if (!adc) {

                printk(KERN_ERR "%s:failed to find adc\n", __func__);

                return -EINVAL;

       }

 

       spin_lock_irqsave(&adc->lock, flags);

 

       if (client->is_ts && adc->ts_pend) {

               spin_unlock_irqrestore(&adc->lock, flags);

                return -EAGAIN;

       }

 

       //选择ADC通道

       client->channel = channel;

       //赋值采样次数

       client->nr_samples = nr_samples;

 

       if (client->is_ts)            //如果是触摸屏,保存client

                adc->ts_pend = client;

       else                //如果不是触摸屏,将client插入链表中

                list_add_tail(&client->pend,&adc_pending);

 

       //如果没有正在处理其他客户请求,则调用s3c_adc_try函数处理当前客户请求

       if (!adc->cur)

                s3c_adc_try(adc);

 

       spin_unlock_irqrestore(&adc->lock, flags);

 

       return 0;

}

 

再来看看s3c_adc_try函数:

static void s3c_adc_try(struct adc_device *adc)

{

       struct s3c_adc_client*next = adc->ts_pend;

 

       //检查是否有触摸屏客户和非触摸屏客户在等待ADC转换

       if (!next && !list_empty(&adc_pending)) {

                next =list_first_entry(&adc_pending,

                                        struct s3c_adc_client, pend);

                list_del(&next->pend);

       } else

                adc->ts_pend = NULL;

 

       //对正在等待的ADC客户进行处理

       if (next) {

                adc_dbg(adc, "new clientis %p\n", next);

                adc->cur = next;

                //执行select回调函数,并初始化ADC控制器

                s3c_adc_select(adc, next);

                //启动ADC转换

                s3c_adc_convert(adc);

                //调试打印输出寄存器的值

                s3c_adc_dbgshow(adc);

       }

}

 

从上面的分析可以看出,调用s3c_adc_read函数和s3c_adc_start函数都能够实现读取ADC转换数据的目的,但两者有两点不同:其一是调用s3c_adc_read函数只能实现一次采样,而调用s3c_adc_start函数可以自己设定采样次数;其二是s3c_adc_read函数有休眠等待的机制,在这段等待的时间内,系统可以启动ADC中断,以实现ADC的转换,而s3c_adc_start函数没有该机制。因此,如果单纯的进行ADC转换,则需要调用s3c_adc_read函数,而如果应用触摸屏,则需要调用s3c_adc_start函数,因为s3c2440有自己的触摸屏中断。

 

最后,我们再来分析ADC中断处理函数:

static irqreturn_t s3c_adc_irq(int irq, void *pw)

{

       struct adc_device *adc = pw;

       struct s3c_adc_client*client = adc->cur;

       enum s3c_cpu_typecpu = platform_get_device_id(adc->pdev)->driver_data;

       unsigned data0, data1;

 

       if (!client) {

               dev_warn(&adc->pdev->dev, "%s: no adc pending\n",__func__);

                goto exit;

       }

 

       //分别读取寄存器ADCDAT0和ADCDAT1

       data0 = readl(adc->regs + S3C2410_ADCDAT0);

       data1 = readl(adc->regs + S3C2410_ADCDAT1);

       adc_dbg(adc, "read %d: 0x%04x, 0x%04x\n",client->nr_samples, data0, data1);

 

       //采样数减1

       client->nr_samples--;

 

       //根据不同的CPU类型,对读取到的数据进行有效位数的处理

       if (cpu == TYPE_ADCV1 || cpu == TYPE_ADCV11) {

                data0 &= 0x3ff;

                data1 &= 0x3ff;

       } else {

                /* S3C2416/S3C64XX/S5PADC resolution is 12-bit */

                data0 &= 0xfff;

                data1 &= 0xfff;

       }

 

        //执行convert_cb回调函数

       if (client->convert_cb)

                (client->convert_cb)(client,data0, data1, &client->nr_samples);

 

       if (client->nr_samples > 0) {               //如果还要进行下次采样

                /* fire another conversion forthis */

               //继续选择当前客户,并再次启动ADC转换

                client->select_cb(client,1);

                s3c_adc_convert(adc);

       } else {                 //如果采样次数为0,则结束ADC转换

                spin_lock(&adc->lock);

                (client->select_cb)(client,0);

                adc->cur = NULL;

 

                //检查是否还有正在等待的ADC,如果有,则再次启动ADC转换

                s3c_adc_try(adc);

                spin_unlock(&adc->lock);

       }

 

exit:

       if (cpu == TYPE_ADCV2 || cpu == TYPE_ADCV3) {

                /* Clear ADC interrupt */

                writel(0, adc->regs + S3C64XX_ADCCLRINT);

       }

       return IRQ_HANDLED;

}

 

至此,adc.c文件就介绍完了,我们再次梳理一下如何利用系统自带的adc.c文件来操作ADC:首先调用s3c_adc_register函数注册一个ADC客户,然后调用s3c_adc_read函数读取ADC转换的数据,在读取的过程中,系统会启动ADC中断来进行ADC转换。可以看出整个流程是非常简单的。

 

下面我们就编写一段通用的基于s3c2440的ADC应用层文件。首先在drives/misc目录下创建s3c24xx_adc.c文件,内容如下:

#include <linux/module.h>

#include <linux/types.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/platform_device.h>

#include <linux/device.h>

#include <linux/io.h>

#include <plat/adc.h>

#include <linux/miscdevice.h>

#include <linux/uaccess.h>

 

struct adc_dev

{

 struct s3c_adc_client*padc;             //ADC客户

  intchannel;                      //ADC通道

};

static struct adc_dev s3c2410adc;

 

//读取ADC转换数值

static int s3c24xx_adc_read(struct file *filp, char __user *buff,size_t count, loff_t *offp)

{

   unsigned long err;

   int  adc_value;

 

   //调用s3c_adc_read函数

   adc_value = s3c_adc_read(s3c2410adc.padc, s3c2410adc.channel);

   //把ADC转换的数值赋值给用户

   err = copy_to_user(buff, &adc_value, sizeof(adc_value));

 

   return sizeof(adc_value);

}

 

static int s3c24xx_adc_close(struct inode *inode, struct file*file)

{

 return 0;

}

 

static int s3c24xx_adc_open(struct inode *inode, struct file*file)

{

 return 0;

}

 

static struct file_operations s3c2410adc_fops = {

   .owner   =   THIS_MODULE,

   .open    =   s3c24xx_adc_open,

   .release =   s3c24xx_adc_close,

   .read    =   s3c24xx_adc_read,

};

 

//定义杂项设备

static struct miscdevice s3c2410adc_miscdev = {

   .minor = MISC_DYNAMIC_MINOR,

   .name ="adcdev",

    .fops= &s3c2410adc_fops,

};

 

//根据s3c2440的ADC四个通道,定义ADC平台驱动列表

static struct platform_device_id s3c24xx_adc_driver_ids[] = {

       {

              .name           = "s3c24xx-a2d0",

              .driver_data    = 0,

       },{

              .name             = "s3c24xx-a2d1",

              .driver_data    = 0,

       },{

              .name             = "s3c24xx-a2d2",

              .driver_data    = 0,

       },{

              .name           = "s3c24xx-a2d3",

              .driver_data    = 0,

       },

       {}

};

MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);

 

static int s3c24xx_adc_probe(struct platform_device *pdev)

{

 struct platform_device_id *id= s3c24xx_adc_driver_ids;

  s3c2410adc.channel=0;

//根据定义的平台设备,自动确定ADC通道

 while(id->name[0]){

   if(strcmp(pdev->name,id->name)==0){

     break;   

    }

   id++;

    s3c2410adc.channel++;

  }

  //注册s3c2440的ADC

  s3c2410adc.padc = s3c_adc_register(pdev,NULL,NULL,0);

  //注册杂项设备

 misc_register(&s3c2410adc_miscdev);

 

 return 0;

}

 

static int s3c24xx_adc_remove(struct platform_device *dev)

{

  s3c_adc_release(s3c2410adc.padc);

 misc_deregister(&s3c2410adc_miscdev);

 return 0;

}

 

//定义ADC平台驱动

static struct platform_driver s3c24xx_adc_driver = {

 .probe  = s3c24xx_adc_probe,

 .remove  = s3c24xx_adc_remove,

 .id_table = s3c24xx_adc_driver_ids,

 .driver  = {

     .owner = THIS_MODULE,

     .name = "s3c2410-adc",

  },

};

 

static int __init s3c24xx_adc_init(void)

{

 platform_driver_register(&s3c24xx_adc_driver);

 return 0;

}

 

static void __exit s3c24xx_adc_exit(void)

{

 platform_driver_unregister(&s3c24xx_adc_driver);

}

 

module_init(s3c24xx_adc_init);

module_exit(s3c24xx_adc_exit);

MODULE_AUTHOR("Zhao Chunjiang");

MODULE_DESCRIPTION("s3c24xx adc Driver");

MODULE_LICENSE("GPL");

 

然后在该目录下的Kconfig文件内添加下列语句:

 

config ZHAOCJ2440_ADC

       tristate "ADCdriver for s3c2440development boards"

       depends on  ARM ||S3C24XX_ADC

       help

       this is ADC driver forZHAOCJ2440 development boards

 

再在该目录下的Makefile文件内添加下列语句:

obj-$(CONFIG_ZHAOCJ2440_ADC)    += s3c24xx_adc.o

 

最后在arch/arm/mach-s3c24xx/mach-zhaocj2440.c文件内的zhaocj2440_devices结构之前,添加下列语句:

static struct resource s3c_a2d_resource[] = {

       [0] =DEFINE_RES_MEM(S3C24XX_PA_ADC,S3C24XX_SZ_ADC),

};

 

struct platform_device s3c_device_a2d2 = {

       .name            ="s3c24xx-a2d2",

       .id          = -1,

       .dev.parent   = &s3c_device_adc.dev,

       .num_resources   = ARRAY_SIZE(s3c_a2d_resource),

       .resource      = s3c_a2d_resource,

};

 

并在zhaocj2440_devices结构内添加:

       &s3c_device_adc,

       &s3c_device_a2d2,

 

在这里,可以通过定义不同的ADC平台设备来使系统自动识别ADC通道,即如果把平台设备定义为s3c_device_a2d0,则表示该设备应用的是0通道;如果把平台设备定义为s3c_device_a2d1,则表示该设备应用的是1通道;以此类推。由于在我的开发板上ADC的通道2连接着一个电位器,因此我把ADC平台设备定义为s3c_device_a2d2。

 

ADC的移植就是这些,另外还需要进行menuconfig的配置,以增加ADC功能:

Device Drivers --->

      Misc devices --->

        <*> ADC driverfor s3c2440 development boards

 

 

下面我们写一段应用程序来验证ADC功能:

/*

* adc.c

*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/select.h>

#include <sys/time.h>

#include <errno.h>

 

int main(void)

{

    int  adc_fd;

    int  adc_value, adc_size;

    int  i;

    i=0;

    adc_value=0;

 

    adc_fd =open("/dev/adcdev", O_RDONLY);

    if (adc_fd < 0) {

        perror("opendevice adcdev");

        exit(1);

    } 

 

    for(i=0;i<3;i++)

    { 

       sleep(1);

       adc_size =read(adc_fd,&adc_value,sizeof(adc_value));

       printf("adc  value : %d\n", adc_value);

    }

 

    close(adc_fd);

    return 0;

}

 

编译并运行之,结果为:

[root@zhaocj/temp]#./adc

adc  value : 531

adc  value : 533

adc  value : 532