linux-2.6.32.2内核移植(七)——增加ADC驱动支持
【1】关于S3C2440 的ADC 和触摸屏接口
在 S3C2440 芯片中,AD 输入和触摸屏接口使用共同的A/D 转换器,见2440 芯片手册第16 章节,如图,其中通道7(XP或AIN7)作为触摸屏接口的X坐标输入,通道5(YP或AIN5)作为触摸屏接口的Y坐标输入。

我 们从上面的结构图和数据手册可以知道,该ADC模块总共有8个通道可以进行模拟信号的输入,分别是AIN0、AIN1、AIN2、AIN3、YM、 YP、XM、XP。那么ADC是怎么实现模拟信号到数字信号的转换呢?首先模拟信号从任一通道输入,然后设定寄存器中预分频器的值来确定AD转换器频率, 最后ADC将模拟信号转换为数字信号保存到ADC数据寄存器0中(ADCDAT0),然后ADCDAT0中的数据可以通过中断或查询的方式来访问。对于 ADC的各寄存器的操作和注意事项请参阅数据手册。

上图是mini2440上的ADC应用实例,开发板通过一个10K的电位器(可变电阻)来产生电压模拟信号,然后通过第一个通道(即:AIN0)将模拟信号输入ADC。
ADC 设备在Linux中可以看做是简单的字符设备,也可以当做是一混杂设备(misc设备),这里我们就看做是misc设备来实现ADC的驱动。注意:这里我 们获取AD转换后的数据将采用中断的方式,即当AD转换完成后产生AD中断,在中断服务程序中来读取ADCDAT0的第0-9位的值(即AD转换后的 值)。
【2】在内核中添加ADC 驱动
Linux-2.6.32.2 内核并没有提供支持S3C2440 的ADC 驱动程序,对ADC驱动程序作了下修改s3c24xx-adc.h文件中提供的宏修改通道获取采样数据。
在 S3C2440 芯片中,AD 输入和触摸屏接口使用共同的 A/D 转换器,因此,ADC 驱动和触摸屏驱动若想共存,就必须解决共享"A/D 转换器"资源这个问
题,因此在 ADC 驱动程序中声明了一个全局的"ADC_LOCK"信号量。
(1)将ADC驱动程序(s3c24xx-adc.h文件和mini2440_adc.c)放于drivers/char目录下
s3c24xx-adc.h 内容如下:
/*
* s3c24xx-adc.h wonfee 2012-05-23
*/
#ifndef _S3C2410_ADC_H_
#define _S3C2410_ADC_H_
#define ADC_WRITE_GETCH(data)(((data)>>16)&0x7)
#define ADC_WRITE_GETPRE(data)((data)&0xff)
// ADC_WRITE_GETCH(data) 得到ch
// ADC_WRITE_GETPRE(data) 得到prescale
#define ADC_WRITE(ch,prescale)((ch)<<16|(prescale))
#endif
/* _S3C2410_ADC_H_*/
mini2440_adc.c 内容如下:
/*
* mini2440_adc.c wonfee 2012-05-23
* ADC driver
*/
#include<linux/errno.h>
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/slab.h>
#include<linux/input.h>
#include<linux/init.h>
#include<linux/serio.h>
#include<linux/delay.h>
#include<linux/clk.h>
#include<linux/wait.h>
#include<linux/sched.h>
#include<asm/io.h>
#include<asm/irq.h>
#include<asm/uaccess.h>
#include<mach/regs-clock.h>
#include<plat/regs-timer.h>
#include<plat/regs-adc.h>
#include<mach/regs-gpio.h>
#include<linux/cdev.h>
#include<linux/miscdevice.h>
//自己定义的头文件 因为原生内核并么有包含
#include"s3c24xx-adc.h"
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(x...){printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);}
#else
#define DPRINTK(x...)(void)(0)
#endif
#define DEVICE_NAME "adc"
staticvoid __iomem *base_addr;
// define ADC device struct
typedefstruct{
wait_queue_head_t wait;
int channel;
int prescale;
}ADC_DEV;
// global var for ADC and Touch Screen drivers
DECLARE_MUTEX(ADC_LOCK);
//ADC驱动是否拥有A/D转换器资源的状态变量
staticintOwnADC=0;
static ADC_DEV adcdev;
staticvolatileint ev_adc =0;
staticint adc_data;
staticstruct clk *adc_clock;
//定义ADC相关的寄存器
#define ADCCON (*(volatileunsignedlong*)(base_addr + S3C2410_ADCCON))//ADC control
#define ADCTSC (*(volatileunsignedlong*)(base_addr + S3C2410_ADCTSC))//ADC touch screen control
#define ADCDLY (*(volatileunsignedlong*)(base_addr + S3C2410_ADCDLY))//ADC start or Interval Delay
#define ADCDAT0 (*(volatileunsignedlong*)(base_addr + S3C2410_ADCDAT0))//ADC conversion data 0
#define ADCDAT1 (*(volatileunsignedlong*)(base_addr + S3C2410_ADCDAT1))//ADC conversion data 1
#define ADCUPDN (*(volatileunsignedlong*)(base_addr +0x14))//Stylus Up/Down interrupt status
#define PRESCALE_DIS (0<<14)
#define PRESCALE_EN (1<<14)
#define PRSCVL(x)((x)<<6)
#define ADC_INPUT(x)((x)<<3)
#define ADC_START (1<<0)
#define ADC_ENDCVT (1<<15)
//开启AD输入
#define START_ADC_AIN(ch,prescale) \
do{\
ADCCON = PRESCALE_EN | PRSCVL(prescale)| ADC_INPUT((ch));\
ADCCON |= ADC_START; \
}while(0)
//ADC中断处理函数
staticirqreturn_t adcdone_int_handler(int irq,void*dev_id)
{
//如果ADC驱动拥有A/D转换器资源,则从ADC寄存器读取转换状态
if(OwnADC){
adc_data = ADCDAT0 &0x3ff;
ev_adc =1;
wake_up_interruptible(&adcdev.wait);
}
return IRQ_HANDLED;
}
//ADC读函数,
staticssize_t s3c2410_adc_read(struct file *filp,char*buffer,size_t count,loff_t*ppos)
{
char str[20];
int value;
size_t len;
//判断'A/D转换器'资源是否可用
if(down_trylock(&ADC_LOCK)==0){
OwnADC=1;//标记为'A/D转换器'资源是可用
START_ADC_AIN(adcdev.channel, adcdev.prescale);//开始转换
wait_event_interruptible(adcdev.wait, ev_adc);//等待转换结束
ev_adc =0;
DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ADCCON &0x80?1:0);
//把转换结果赋值,以便传递到用户层
value = adc_data;
//释放A/D转换器资源
OwnADC=0;
up(&ADC_LOCK);
}else{
//没有A/D转换器资源
value =-1;
}
len = sprintf(str,"%d\n", value);
if(count >= len){
//将转换结果传到到用户层
int r = copy_to_user(buffer, str, len);
return r ? r : len;
}else{
return-EINVAL;
}
}
//ADC设备的打开函数
staticint s3c2410_adc_open(struct inode *inode,struct file *filp)
{
//初始化中断队列
init_waitqueue_head(&(adcdev.wait));
//缺省通道为'0'
adcdev.channel=0;
adcdev.prescale=0xff;
DPRINTK("adc opened\n");
return0;
}
staticint s3c2410_adc_release(struct inode *inode,struct file *filp)
{
DPRINTK("adc closed\n");
return0;
}
staticstruct file_operations dev_fops ={
owner: THIS_MODULE,
open: s3c2410_adc_open,
read: s3c2410_adc_read,
release: s3c2410_adc_release,
};
staticstruct miscdevice misc ={
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops =&dev_fops,
};
staticint __init dev_init(void)
{
int ret;
base_addr=ioremap(S3C2410_PA_ADC,0x20);
if(base_addr == NULL){
printk(KERN_ERR "Failed to remap register block\n");
return-ENOMEM;
}
adc_clock = clk_get(NULL,"adc");
if(!adc_clock){
printk(KERN_ERR "failed to get adc clock source\n");
return-ENOENT;
}
clk_enable(adc_clock);
/* normal ADC */
ADCTSC =0;
ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME,&adcdev);
if(ret){
iounmap(base_addr);
return ret;
}
ret = misc_register(&misc);
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
staticvoid __exit dev_exit(void)
{
free_irq(IRQ_ADC,&adcdev);
iounmap(base_addr);
if(adc_clock){
clk_disable(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
misc_deregister(&misc);
}
//导出信号量"ADC_LOCK"以便触摸屏驱动使用
EXPORT_SYMBOL(ADC_LOCK);
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");
(2) 然后打开 drivers/char/Makefile 文件,在大概 114 行加入 ADC 驱动程序目标模块:
obj-$(CONFIG_JS_RTC) += js-rtc.o
js-rtc-y = rtc.o
obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o
# Files generated that shall be removed upon make clean
clean-files := consolemap_deftbl.c defkeymap.c
(3) 再打开 drivers/char/Kconfig 文件,加入 ADC 驱动配置选项:
config DEVKMEM
bool "/dev/kmem virtual device support"
default y
help
Say Y here if you want to support the /dev/kmem device. The
/dev/kmem device is rarely used, but can be used for certain
kind of kernel debugging operations.
When in doubt, say "N".
config MINI2440_ADC
bool "ADC driver for FriendlyARM Mini2440 development boards"
depends on MACH_MINI2440
default y if MACH_MINI2440
help
this is ADC driver for FriendlyARM Mini2440 development boards
Notes: the touch-screen-driver required this option
config BFIN_JTAG_COMM
tristate "Blackfin JTAG Communication"
depends on BLACKFIN
help
(4) 这样,我们就在内核中添加了 ADC 驱动,现在内核源代码目录的命令行执行:make menuconfig,依次选择如下子菜单项,找到刚刚添加的 ADC 驱动配置选项:
Device Drivers --->
Character devices --->
按空格键选中 ADC 配置选项
make zImage ---> uImage; 下载到开发板启动测试。

浙公网安备 33010602011771号