/* 定义了一个用来保存经过虚拟映射后的内存地址 */ static void __iomem * adc_base; /* 保存从平台时钟队列中获取 ADC 的时钟 */ static struct clk * adc_clk; /* 引用外部一个锁,这个锁已经在 mini2440 加载的驱动里面,所以只能引 用,不能重新定义,对 ADC 资源进行互斥访问 */ //DECLARE MUTEX(ADC LOCK); extern struct semaphore ADC_LOCK; static int __init adc_init ( void){
int ret;
/* 从平台时钟队列中获取 ADC 的时钟,这里为什么要取得这个时钟,因 为 ADC 的转换频率跟时钟有关。系统的一些时钟定义在 arch/arm/plat-s3c24xx/s3c2410-clock.c中*/ adc_clk = clk_get(NULL , \"adc\"); if (!adc_clk)
{
/*错误处理 */
printk(KERN_ERR \"failed to find adc clock source\\n\"); return -ENOENT ;
}
/* 时钟获取后要使能后才可以使用, clk_enable 定义在 arch/arm/plat-s3c/clock.c
中*/
clk_enable(adc_clk);
/*将ADC的IO端口占用的这段 IO空间映射到内存的虚拟地址, ioremap 定义
在 io.h中。注意: IO空间要映射后才能使用,以后对虚拟地址的操作 就是对 IO 空间的操作 ,
S3C2410_PA_ADC 是 ADC 控制器的基地址,定义在 mach-s3c2410/include/mach/map.h中, 0x20 是虚拟地址长度大小 */ adc_base= ioremap(S3C2410_PA_ADC, 0x20); if (adc_base= = NULL )
{
/*错误处理 */
printk(KERN_ERR \"Failed to remap register block\\n\"); ret = -EINVAL ; goto err_noclk;
}
/*把看 ADC 注册成为 misc设备, misc_register定义在 miscdevice.h中 adc_miscdev结构体定义及内部接口函数在第②步中 讲,MISC_DYNAMIC_MINOR
是次设备号,定义在 miscdevice.h中 */
ret = misc_register(& adc_miscdev); if (ret)
{
/*错误处理 */
printk(KERN_ERR \"cannot register miscdev on minor=%d (%d)\\n\goto err_nomap; } printk(DEVICE_NAME \" initialized!\\n\" ); return 0; //以下是上面错误处理的跳转点 err_noclk: clk_disable(adc_clk); clk_put(adc_clk); err_nomap: iounmap(adc_base); return ret; } static void __exit adc_exit(void) { free_irq(IRQ_ADC , 1); /*释放中断 */ iounmap(adc_base); /* 释放虚拟地址映射空间 */ if (adc_clk) { /* 屏蔽和销毁时钟 */ clk_disable(adc_clk); clk_put(adc_clk); adc_clk = NULL; } misc_deregiste(r& adc_miscdev);/* 注销 misc 设备 */ } /* 因为信号量 ADC_LOCK 在内核已经加载的 ADC 驱动中已经声明了,所 以在自己编写的 AD 中,只要引用它就可以了, ADC_LOCK 在触摸屏驱动 中使用,因为触摸屏驱动和 ADC 驱动公用相关的寄存器, 为了不产生资源 竞态,就用信号量来保证资源的互斥访问 */ //EXPORT_SYMBOL (ADC_LOCK ); module_init(adc_init); module_exit(adc_exit); MODULE_LICENSE (\"Dual BSD/GPL\" ); MODULE_AUTHOR (\"apple\"); MODULE DESCRIPTION (\"My2440 ADC Driver\" ); 注意,这个一定不能自己再从新声明一个 ADC_LOC,K因为这个已经在友善之臂 自己的驱动中声明了,我们只要引用他就可以了。
2、adc_miscdev 结构体定义及内部各接口函数的实现,代码如下: #include /*设备名称 */ #define DEVICE_NAME \"my2440_adc\" /* 定义并初始化一个等待队列 adc_waitq,对 ADC 资源进行阻塞访问 */ static DECLARE_WAIT_QUEUE_HEAD (adc_waitq); /*用于标识 AD 转换后的数据是否可以读取, 0 表示不可读取 */ static volatile int ev_adc = 0; /*用于保存读取的 AD 转换后的值,该值在 ADC 中断中读取 */ static int adc_data; /*misc 设备结构体实现 */ static struct miscdevice adc_miscdev= { .minor = MISC_DYNAMIC_MINOR , /*次设备号,定义在 miscdevice.h 中,为 255*/ .name = DEVICE_NAME , .fops = & adc_fops, }; /* 字符设备的相关操作实现 */ static struct file_operations adc_fops= { /*设备名称 */ /* 对 ADC 设备文件操作 */ .owner = THIS_MODULE , .open = adc_open, .read = adc_read, .release = adc_release, }; /*ADC 设备驱动的打开接口函数 */ static int adc_open(struct inode * inode, struct file * file ) { int ret; /* 申请 ADC 中断服务,这里使用的是共享中断 :IRQF_SHARED,为什么要 使用共享中断,因为在触摸屏驱动中也使用了这个中断号。中断服务程序 为:adc_irq 在下面实现, IRQ_ADC 是 ADC 的中断号, 这里注意: 申请中断 函数的最后一个参数一定不能为 NULL ,否则中断申请会失败,如果中断 服务程序中用不到这个参数,就随便给个值就好了,我这里就给个 1*/ ret = request_irq(IRQ_ADC , adc_irq, IRQF_SHARED, DEVICE_NAME , 1); if (ret){
/*错误处理 */
printk(KERN_ERR \"IRQ%d error %d\\n\
}
return 0;
}
/*ADC 中断服务程序,该服务程序主要是从 ADC 数据寄存器中读取 AD 转 换后的
值 */
static irqreturn_t adc_irq(int irq, void * dev_id)
{
/* 保证了应用程序读取一次这里就读取 AD 转换的值一次,避免应用程序 读取
一次后发生多次中断多次读取 AD 转换值 */
if (! ev_adc)
{
/* 读取 AD 转换后的值保存到全局变量 adc_data中,S3C2410_ADCDAT0 定义
在 regs-adc.h中,这里为什么要与上一个 0x3ff ,很简单,因为 AD 转 换后的数据是保存在 ADCDAT0 的第 0-9 位,所以与上 0x3ff(即: 1111111111)后就得到第 0-9 位的数据,多余的位就都为 0*/
adc_data= readl(adc_base+ S3C2410_ADCDAT0 ) & 0x3ff ;
/* 将可读标识为 1,并唤醒等待队列 */ ev_adc= 1;
wake_up_interruptible(& adc_waitq);
}
return IRQ_HANDLED ;
}
/*ADC 设备驱动的读接口函数 */
static ssize_t adc_read(struct file * filp , char *buffer, size_t count, loff_t * ppos)
{
/* 试着获取信号量 ( 即:加锁 )*/ if (down_trylock(&ADC_LOCK ))
{
return -EBUSY;
} if (! ev_adc)/* 表示还没有 AD 转换后的数据,不可读取 */ { if (filp ->f_flags & O_NONBLOCK ) { /* 应用程序若采用非阻塞方式读取则返回错误 */ return -EAGAIN ; } else/* 以阻塞方式进行读取 */ { /* 设置 ADC 控制寄存器,开启 AD 转换*/ start_adc(); /* 使等待队列进入睡眠 */ wait_event_interruptible(adc_waitq, ev_adc); } } /* 能到这里就表示已有 AD 转换后的数据,则标识清 0,给下一次读做判 断用*/ ev_adc = 0; /* 将读取到的 AD 转换后的值发往到上层应用程序 */ copy_to_user(buffer,(char*)& adc_data, sizeof(adc_data)); /*释放获取的信号量 (即:解锁 )*/ up(& ADC_LOCK ); return sizeof(adc_data); } /* 设置 ADC 控制寄存器,开启 AD 转换*/ static void start_adc(void) { unsignedint tmp; tmp =(1 << 14) |(255 << 6) |(0 << 3);/* 0 1 00000011 000 0 0 0 */ writel(tmp, adc_base+ S3C2410_ADCCON); /*AD 预分频器使能、模拟输 入通道设为 AIN0*/ tmp = readl(adc_base+ S3C2410_ADCCON); tmp = tmp |(1 << 0); /* 0 1 00000011 000 0 0 1 */ writel(tmp, adc_base+ S3C2410_ADCCON); /*AD 转换开始*/ /*ADC 设备驱动的关闭接口函数 */ static int adc_release(struct inode * inode, struct file * filp ) {return 0; } 注意:在上面实现的每步中, 为了让代码逻辑更加有条理和容易理解, 就没有考 虑代码的顺序,比如函数要先定义后调用。如果要编译此代码,请严格按照 C 语言的规范来调整代码的顺序。
3、编写用户应用程序测试 my2440_adc驱动。建立应用程序 adc_test.c ,代码 如
下:
#include #include #include int main(int argc, char ** argv){
int fd;
//以阻塞方式打开设备文件,非阻塞时 flags=O_NONBLOCK fd = open(\"/dev/my2440_adc\if(fd < 0)
{
printf (\"Open ADC Device Faild!\\n\" ); exit(1);
}
while (1) int ret; int data;
{
ret = read(fd, & data, sizeof(data)); //读设备 if (ret != sizeof(data))
{
if (errno != EAGAIN )
{
}
printf (\"Read ADC Device Faild!\\n\" ); continue;
}
else
printf (\"Read ADC value is: %d\\n\} } close(fd); return 0; } 4、将驱动程序下载挂载到内核,下载应用程序到开发板上后,运行应用程序, 扭
动 mini2440 开发板上的定位器,可以观察到 ADC转换值的变化,证明驱动程 序工作正常。