在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