在本文中,我们对S3C6410开发板adc驱动代码的实现过程进行分析,然后通过一个实例对adc功能进行测试。在本文的资源中包含了设备驱动的源码和测试的源码。
一、设备驱动源码分析
adc的设备驱动主要实现了模块的初始化、模块的卸载、设备打开、设备关闭、设备读取的功能。
1、模块初始化
模块的初始化的源码如下所示。
static int __init dev_init(void)
{
int ret;
base_addr = ioremap(SAMSUNG_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;
}
这段代码主要实现的功能是是能adc的始终,并且开启了adc的中断功能,最后使用misc_register()函数向内核注册adc的混杂设备。adc的中断初始化过程将adc的中断号与中断处理函数、中断描述结构体绑定在一起。其中,IRQ_ADC是中断号,adcdone_int_handler是中断处理函数,adcdev是指向中断描述结构体的指针,当启用了adc转换后并转换完成后,中断会把相应的中断描述结构体保持下来,并转到adcdone_int_handler函数去处理中断。
adcdone_int_handler()函数的源码如下所示。
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
if (__ADC_locked) {
adc_data = ADCDAT0 & 0x3ff;
ev_adc = 1;
wake_up_interruptible(&adcdev.wait);
/* clear interrupt */
__raw_writel(0x0, base_addr + S3C_ADCCLRINT);
}
return IRQ_HANDLED;
}
这段代码的功能是将adc的转换结果保存到adc_data变量中,并将阻塞的等待队里唤醒。
2、模块卸载
模块的卸载代码如下所示
static void __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的中断功能,禁能adc的始终,并且将adc的混杂设备从内核中取消注册。
3、设备打开
adc的设备打开代码如下所示。
static int s3c2410_adc_open(struct inode *inode, struct file *filp)
{
init_waitqueue_head(&(adcdev.wait));
adcdev.channel=0;
adcdev.prescale=0xff;
DPRINTK("adc opened\n");
return 0;
}
设备打开代码实现了内核阻塞等待队列的初始化,还有adc通道号的初始化。
4、设备关闭
设备关闭代码比较简单,如下所示。
static int s3c2410_adc_release(struct inode *inode, struct file *filp)
{
DPRINTK("adc closed\n");
return 0;
}
代码没有做任何操作,只是打印了一串字符串。
5、设备读取
设备读取的代码如下所示。
static ssize_t s3c2410_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
char str[20];
int value;
size_t len;
if (mini6410_adc_acquire_io() == 0) {
__ADC_locked = 1;
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;
__ADC_locked = 0;
mini6410_adc_release_io();
} else {
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转换功能,然后阻塞等待转换完成,转换完成后会进入中断进行数据读取,在中断中会将读取函数中的阻塞唤醒。唤醒后读取函数读取adc返回的数据,并将ad值转换成字符串str,最后将str复制给用户程序中的buf。
二、测试代码分析
编写了一个简单的测试代码对adc的驱动进行测试,代码如下。
int main()
{
int fd;
char buf[10];
fd = open("/dev/adc",O_RDONLY);
while(1)
{
read(fd,buf,10);
printf("%s\n",buf);
sleep(1);
}
close(fd);
return 0;
}
代码中,首先打开adc端口,然后进入while(1)循环,在循环中,每隔1秒读取一次ad值,并将ad值打印到终端中。