转自:http://hi.baidu.com/huicxu/blog/item/d065a20b0eeb7d2db0351d48.html
1、 原理分析
S3c6410的内部adc结构图:
ADC模块总共有8个通道可以进行模拟信号的输入,分别是AIN0、AIN1、AIN2、AIN3、YM、YP、XM、XP。首先模拟信号从任一通道输入,然后设定寄存器中预分频器的值来确定AD转换器频率,最后ADC将模拟信号转换为数字信号保存到ADC数据寄存器0中(ADCDAT0),然后ADCDAT0中的数据可以通过中断或查询的方式来访问。
ADC寄存器要用到的主要有ADCDLY、ADCTSC和ADCCON。ADCDLY用于设置adc转换延迟时间。ADCTSC寄存器主要用于设置ADC的工作模式,包括普通转换模式、独立X/Y转换模式、自动(连续)X/Y转换模式、等待中断模式和待机模式五种。触摸屏驱动的ADC工作于等待中断模式下。而一般的ADC转换如通过ADC测量电池电压等则要工作于普通转换模式下(可设置成ADCTSC=0),还有可以设置上拉使能。ADCCON主要是用于设置ADC的输入通道和开始转换。对于ADC的各寄存器的操作和注意事项请参阅数据手册。
在ADC驱动的初始化函数里要完成以下几步,首先要先使能时钟,然后把IO空间映射到内存的虚拟地址以便后面设置寄存器时使用,最后是读取配置信息并对寄存器进行设置。
在读取AD转换值是要先设置AD转换的输入通道和工作模式并启动AD转换(通过设置ADCTSC和ADCCON),然后等待直到ad转换完成(通过中断或查询ADCCON的ECFLG位),最后读取AD转换后的值(通过读取ADCDAT0的低十位或底十二位得到)。
因为触摸屏驱动和ADC驱动共用相关的寄存器,为了不产生资源竞态,需要用信号量或互斥体来保证资源的互斥访问,并在进程切换时保存和恢复相关寄存器。
ADC设备在Linux中可以看做是简单的字符设备,也可以当做是一混杂设备(misc设备),这里看做是misc设备来实现ADC的驱动。获取AD转换后的数据有两种方式。一种是中断的方式,即当AD转换完成后产生AD中断,在中断服务程序中来读取ADCDAT0的第0-9位的值(即AD 转换后的值)。另一种是查询的方式,即AD转换开始后不断的查询查询ADCCON直到ECFLG位等于1,表示转换完成后在读取AD 转换后的值。这里采用后面一种方式。
无线数码相框中AIN1接外电,AIN3接电池。
s3c6410的驱动位于arch/arm/plat-s3c64xx/adc.c.可通过对static int adc_port 赋值或同过ioctl函数来指定AD输入端口。 ADC_LOCK;用于实现对ADC共享寄存器的互斥访问。在ADC驱动中需要导出ADC_LOCK:使得在触摸屏驱动中能够获得此互斥体”EXPORT_SYMBOL(ADC_LOCK);”
在触摸屏驱动drivers/input/touchscreen/s3c-ts.c:导入adc_mutex: “extern struct semaphore adc_mutex;”并在用到共享ADC寄存器的地方通过”down_trylock(&ADC_LOCK);”和”up(&ADC_LOCK);”实现互斥。
下面是几个要注意的地方:
a、当adc中断发生时要在中断处理程序中执行如下语句否则不断产生中断从而把系统卡死
if(ts->s3c_adc_con==ADC_TYPE_2) {
__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
}
b、触摸屏的触点中断函数stylus_updown函数是个原子操作不能挂起。这时如果用互斥结构体mutex实现互斥则上锁(mutex_lock(&adc_mutex))不成功内核会发生错误。而如果用信号量semaphore实现互斥上锁(down_trylock(&ADC_LOCK))失败只是会返回非零值,而我们可以通过其返回值来确定是否执行下一步操作。所以在这里只能用semaphore实现互斥。
c、触摸屏驱动和ADC驱动共用相关的寄存器和中断,当我们进行adc转换时触摸屏的adc中断函数stylus_action照样会执行
修改arch/arm/plat-s3c64xx/adc.c
首先在文件开头部分引掉mutex结构体用semaphore结构体代替:
//static DEFINE_MUTEX(adc_mutex);
DECLARE_MUTEX(ADC_LOCK);
然后在read函数中修改互斥的实现
s3c_adc_read(struct file *file, char __user * buffer,
size_t size, loff_t * pos)
{
int adc_value = 0;
printk(KERN_INFO " s3c_adc_read() entered\n");
#ifdef ADC_WITH_TOUCHSCREEN
//mutex_lock(&adc_mutex);
if (down_trylock(&ADC_LOCK) != 0)
{
goto down_error;
}
s3c_adc_save_SFR_on_ADC();
#endif
adc_value = s3c_adc_convert();
#ifdef ADC_WITH_TOUCHSCREEN
s3c_adc_restore_SFR_on_ADC();
//mutex_unlock(&adc_mutex);
up(&ADC_LOCK);
……
}
最后在文件结束部分导出信号量结构体ADC_LOCK,目的是让触摸屏驱动可以访问这个全局变量。
EXPORT_SYMBOL(ADC_LOCK);
4、 修改drivers/input/touchscreen/s3c-ts.c
首先声明信号量ADC_LOCK:
extern struct semaphore ADC_LOCK;
然后修改函数touch_timer_fire:
static void touch_timer_fire(unsigned long data)
{
……
writel(WAIT4INT(0), ts_base+S3C_ADCTSC);
#ifdef add_by_cc
if (own_adc)
{
//printk(KERN_INFO "do funtion: mutex_unlock(&adc_mutex);\n");
//mutex_unlock(&adc_mutex);
up(&ADC_LOCK);
own_adc = 0;
}
#endif
}
}
修改函数stylus_updown:
static irqreturn_t stylus_updown(int irqno, void *param)
{
unsigned long data0;
unsigned long data1;
int updown;
#ifdef add_by_cc
// bool f_lock = 0;
//if (!own_adc)
if (down_trylock(&ADC_LOCK) != 0)
{
if(ts->s3c_adc_con==ADC_TYPE_2) {
__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
}
goto down_error;
//mutex_lock(&adc_mutex);
//own_adc = 1;
//f_lock = 1;
}
own_adc = 1;
#endif
……
#ifdef add_by_cc
if (!updown)// && f_lock)
{
//mutex_unlock(&adc_mutex);
up(&ADC_LOCK);
own_adc = 0;
}
down_error:
#endif
return IRQ_HANDLED;
}
修改stylus_action函数:
static irqreturn_t stylus_action(int irqno, void *param)
{
unsigned long data0;
unsigned long data1;
int maskdata0;
int maskdata1;
#ifdef add_by_cc
if (!own_adc) {
goto no_own_adc;
}
#endif
data0 = readl(ts_base+S3C_ADCDAT0);
……
no_own_adc:
if(ts->s3c_adc_con==ADC_TYPE_2) {
__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
}
return IRQ_HANDLED;
}