ADC驱动。。。就是应用程序去读取ADC的值,具体来讲是读取ADC寄存器的值,那就是典型的字符驱动了,当然用作Input子系统的ADC驱动还是与普通的字符驱动有很大差别的。
/*
* S3C-XXXX ADC驱动程序
*
*/
include<linux/kernel.h> /* 提供prink等内核特有属性 */
include<linux/module.h> /* 提供如MODULE_LICENSE()、EXPORT_SYMBOL() */
include<linux/init.h> /* 设置段,如_init、_exit,设置初始化优先级,如__initcall */
include<linux/wait.h> /* 等待队列wait_queue */
include<linux/interrupt.h> /* 中断方式,如IRQF_SHARED */
include<linux/fs.h> /* file_operations操作接口等 */
include<linux/clk.h> /* 时钟控制接口,如struct clk */
include<linux/miscdevice.h> /* 杂项设备 */
include<asm/io.h> /* 提供readl、writel */
include<asm/irq.h> /* 提供中断号,中断类型等,如IRQ_ADC中断号 */
include<asm/arch/regs-adc.h> /* 提供控制器的寄存器操作,如S3C2410_ADCCON */
include<asm/uaccess.h> /* 提供copy_to_user等存储接口 */
/* 定义设备名称,用户访问接口/dev/adc */
#define DEVICE_NAME "adc"
/* 定义adc时钟,通过adc_clock接口获得adc输入时钟,adc转换器需要 */
static struct clk *adc_clock;
/* 定义虚拟地址访问硬件寄存器,__iomem只是用于表示指针将指向I/O内存 */
static void __iomem *base_addr;
/* 定义并初始化一个等待队列adc_waitqueue,对ADC资源进行阻塞访问 */
static wait_queue_head_t adc_waitqueue;
/* 定义并初始化信号量adc_lock,用于控制共享中断IRQ_ADC资源的使用 */
ECLARE_MUTEX(adc_lock);
XPORT_SYMBOL(adc_lock);
/* 定义等待队列的条件,当is_read_ok=1时,ADC转换完毕,数据可读 */
static volatile int is_read_ok = 0;
/* 定义ADC转换的数据内容 */
static volatile int adc_data;
static int adc_open(struct inode *inode, struct file *file);
static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos);
static int adc_close(struct inode *inode, struct file *filp);
/* 实现字符设备操作接口 */
static struct file_operations adc_fops =
.owner = THIS_MODULE,
.open = adc_open,
.read = adc_read,
.release = adc_close,
;
/* 实现misc杂项设备操作接口 */
static struct miscdevice adc_miscdev =
.minor = MISC_DYNAMIC_MINOR, /* 动态获取杂项设备的次设备号 */
.name = DEVICE_NAME, /* 杂项设备的设备名称,这里为adc */
.fops = &adc_fops, /* 杂项设备子系统接口,指向adc_fops操作接口 */
;
/* ADC中断服务程序,获取ADC转换后的数据 */
static irqreturn_t adc_irq(int irq, void *dev_id)
/* 仅当is_read_ok=0时才进行转换,防止多次中断 */
if(!is_read_ok)
{
/* 读取ADCCON[9:0]的值,0x3ff为只获取[9:0]位,ADCCON为转换后的数据 */
adc_data = readl(base_addr +S3C2410_ADCDAT0) & 0x3ff;
/* 设置标识为1,唤醒读等待进程可以拷贝数据给用户空间了 */
is_read_ok = 1;
wake_up_interruptible(&adc_waitqueue);
}
return IRQ_RETVAL(IRQ_HANDLED);
/* ADC设备打开,并注册IRQ_ADC中断处理函数 */
static int adc_open(struct inode *inode, struct file *file)
int ret;
/* 由于IRQ_ADC为共享中断,因此中断类型选择IRQF_SHARED,最后一个参数需要设置NULL以外的值 */
ret = request_irq(IRQ_ADC, adc_irq,IRQF_SHARED, DEVICE_NAME, (void *)1);
if (ret)
{
printk(KERN_ERR "Could not allocate ts IRQ_ADC !\n");
return -EBUSY;
}
return 0;
}
/*设置ADC控制寄存器,开启AD转换*/
static void adc_run(void)
{
volatile unsigned int adccon;
/* ADCCON的位[14]=1为使能A/D预分频器,位[13:6]=32表示设置的分频值,ADC的转换频率需要在2.5MHZ以下
* 我们使用的ADC输入时钟为PCLK=50MHZ,50MHZ/32<2.5MHZ,满足条件
* 位[5:3]=000,表示模拟输入通道选择AIN0
*/
adccon = (1 << 14) | (64 << 6);
writel(adccon, base_addr + S3C2410_ADCCON);
/* 位[0]=1表示使能ADC转换,当转换完毕后此位被ADC控制器自动清0 */
adccon = readl(base_addr + S3C2410_ADCCON)| (1 << 0);
writel(adccon, base_addr + S3C2410_ADCCON);
}
/*ADC设备驱动读函数 */
static ssize_t adc_read(struct file *filp, char *buff, size_t count, loff_t *offp)
{
int err;
/* 获取信号量,如果被占用,睡眠等待持有者调用up唤醒
* 这样做的原因是,有可能其他进程抢占执行或是触摸屏驱动抢占执行
*/
down_interruptible(&adc_lock);
/* 启动adc转换,调用中断处理函数adc_irq*/
adc_run();
/* 如果is_read_ok为假,则睡眠等待条件为真,由中断处理函数唤醒 */
wait_event_interruptible(adc_waitqueue,is_read_ok);
/* 执行到此说明中断处理程序获得了ADC转换后的值,清除为0等待下一次的读 */
is_read_ok = 0;
/* 将转换后的数据adc_data提交给用户 */
err = copy_to_user(buff, (char*)&adc_data, min(sizeof(adc_data),count));
/* 释放信号量,并唤醒因adc_lock而睡眠的进程 */
up(&adc_lock);
return err ? -EFAULT : sizeof(adc_data);
}
/*ADC设备关闭函数 */
static int adc_close(struct inode *inode, struct file *filp)
{
/*释放中断*/
free_irq(IRQ_ADC, (void *)1);
return 0;
}
static int __init adc_init(void)
{
int ret;
/* 获得adc的时钟源,通过arch/arm/mach-s3c2410/clock.c获得提供的时钟源为PCLK */
adc_clock = clk_get(NULL, "adc");
if (!adc_clock)
{
printk(KERN_ERR "failed to get adcclock source\n");
return -ENOENT;
}
/* 在时钟控制器中给adc提供输入时钟,ADC转换需要输入时钟 */
clk_enable(adc_clock);
/* 使用ioremap获得操作ADC控制器的虚拟地址
* S3C2410_PA_ADC=ADCCON,是ADC控制器的基地址,寄存器组的长度=0x1c
*/
base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
if (base_addr == NULL)
{
printk(KERN_ERR "Failed to remapregister block\n");
return -ENOMEM;
goto fail1;
}
/* 初始化等待队列 */
init_waitqueue_head(&adc_waitqueue);
/* 注册杂项设备 */
ret = misc_register(&adc_miscdev);
if (ret)
{
printk(KERN_ERR "Failed toregister miscdev\n");
goto fail2;
}
printk(DEVICE_NAME "initialized!\n");
return 0;
fail2:
iounmap(base_addr);
fail1:
clk_disable(adc_clock);
clk_put(adc_clock);
return ret;
}
static void __exit adc_exit(void)
{
/* 释放虚拟地址 */
iounmap(base_addr);
/* 禁止ADC的时钟源 */
if (adc_clock)
{
clk_disable(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
/*注销misc设备*/
misc_deregister(&adc_miscdev);
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");