2014-01-18 16:16:54 lwj103862095 阅读数 11285

linux2.6.30.4中,系统已经自带有了ADC通用驱动文件---arch/arm/plat-s3c24xx/adc.c,它是以平台驱动设备模型的架构来编写的,里面是一些比较通用稳定的代码,但是linux2.6.30.4版本的ADC通用驱动文件并不完善,居然没有读函数。后来去看了linux3.8版本的ADC通用文件----arch/arm/plat-samsung/adc.c才是比较完善的。

但是本节并不是分析这个文件,而是以另外一种架构来编写ADC驱动,因为ADC驱动实在是比较简单,就没有使用平台驱动设备模型为架构来编写了,这次我们使用的是混杂(misc)设备驱动。

问:什么是misc设备驱动?

答:miscdevice共享一个主设备号MISC_MAJOR(10),但次设备号不同。所有的miscdevice设备形成一条链表,对设备访问时内核根据设备号来查找对应的miscdevice设备,然后调用其file_operations结构体中注册的文件操作接口进行操作。

struct miscdevice  {
	int minor;				//次设备号,如果设置为MISC_DYNAMIC_MINOR则系统自动分配
	const char *name;		//设备名
	const struct file_operations *fops;		//操作函数
	struct list_head list;
	struct device *parent;
	struct device *this_device;
};
dev_init入口函数分析:

static int __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);
	
	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" initialized\n");
	return ret;
}
首先是映射ADC寄存器地址将其转换为虚拟地址,然后获得ADC时钟并使能ADC时钟,接着申请ADC中断,其中断处理函数为

adcdone_int_handler,而flags为IRQF_SHARED,即共享中断,因为触摸屏里也要申请ADC中断,最后注册一个混杂设备。

当应用程序open ("/dev/adc",...)时,就会调用到驱动里面的open函数,那么我们来看看open函数做了什么?

static int tq2440_adc_open(struct inode *inode, struct file *filp)
{
	/* 初始化等待队列头 */
	init_waitqueue_head(&(adcdev.wait));

	/* 开发板上ADC的通道2连接着一个电位器 */
	adcdev.channel=2;	//设置ADC的通道
	adcdev.prescale=0xff;

	DPRINTK( "ADC opened\n");
	return 0;
}
很简单,先初始化一个等待队列头,因为入口函数里既然有申请ADC中断,那么肯定要使用等待队列,接着设置ADC通道,因为TQ2440的ADC输入通道默认是2,设置预分频值为0xff。

当应用程序read时,就会调用到驱动里面的read函数,那么我们来看看read函数做了些什么?

static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
	char str[20];
	int value;
	size_t len;

	/* 尝试获得ADC_LOCK信号量,如果能够立刻获得,它就获得信号量并返回0 
	 * 否则,返回非零,它不会导致调用者睡眠,可以在中断上下文使用
	 */
	if (down_trylock(&ADC_LOCK) == 0)
	{
		/* 表示A/D转换器资源可用 */
		ADC_enable = 1;

		/* 使能预分频,选择ADC通道,最后启动ADC转换*/
		START_ADC_AIN(adcdev.channel, adcdev.prescale);

		/* 等待事件,当ev_adc = 0时,进程被阻塞,直到ev_adc>0 */
		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));

		/* 将在ADC中断处理函数读取的ADC转换结果赋值给value */
		value = adc_data;
		sprintf(str,"%5d", adc_data);
		copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

		ADC_enable = 0;
		up(&ADC_LOCK);
	}
	else
	{
		/* 如果A/D转换器资源不可用,将value赋值为-1 */
		value = -1;
	}

	/* 将ADC转换结果输出到str数组里,以便传给应用空间 */
	len = sprintf(str, "%d\n", value);
	if (count >= len)
	{
		/* 从str数组里拷贝len字节的数据到buffer,即将ADC转换数据传给应用空间 */
		int r = copy_to_user(buffer, str, len);
		return r ? r : len;
	}
	else
	{
		return -EINVAL;
	}
}
tq2440_adc_read函数首先尝试获得ADC_LOCK信号量,因为触摸屏驱动也有使用ADC资源,两者互有竞争关系,获得ADC资源后,使能预分频,选择ADC通道,最后启动ADC转换,接着就调用wait_event_interruptible 函数进行等待,直到ev_adc>0进程才会继续往下跑,往下跑就会将adc_data数据读出来,调用copy_to_user函数将ADC数据传给应用空间,最后释放ADC_LOCK信号量。

问:什么时候ev_adc>0?默认ev_adc = 0

答:在adcdone_int_handler中断处理函数里,等数据读出后,ev_adc被设置为1。

ADC中断处理函数adcdone_int_handler

/* ADC中断处理函数 */
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
	/* A/D转换器资源可用 */
	if (ADC_enable)
	{
		/* 读ADC转换结果数据 */
		adc_data = ADCDAT0 & 0x3ff;

		/* 唤醒标志位,作为wait_event_interruptible的唤醒条件 */
		ev_adc = 1;
		wake_up_interruptible(&adcdev.wait);
	}
	return IRQ_HANDLED;
}
当AD转换完成后就会触发ADC中断,就会进入adcdone_int_handler,这个函数就会讲AD转换数据读到adc_data,接着将唤醒标志位ev_adc置1,最后调用wake_up_interruptible函数唤醒adcdev.wait等待队列。
总结一下ADC的工作流程:

一、open函数里,设置模拟输入通道,设置预分频值

二、read函数里,启动AD转换,进程休眠

三、adc_irq函数里,AD转换结束后触发ADC中断,在ADC中断处理函数将数据读出,唤醒进程

四、read函数里,进程被唤醒后,将adc转换数据传给应用程序

ADC驱动参考源码:

/*************************************

NAME:EmbedSky_adc.c
COPYRIGHT:www.embedsky.net

*************************************/

#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 <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 "tq2440_adc.h"

#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(x...) {printk(KERN_DEBUG "EmbedSky_adc: " x);}
#else
#define DPRINTK(x...) (void)(0)
#endif

#define DEVICE_NAME	"adc"		/* 设备节点: /dev/adc */

static void __iomem *base_addr;

typedef struct
{
	wait_queue_head_t wait;		/* 定义等待队列头 */
	int channel;
	int prescale;
}ADC_DEV;

DECLARE_MUTEX(ADC_LOCK);	/* 定义并初始化信号量,并初始化为1 */
static int ADC_enable = 0;			/* A/D转换器资是否可用标志位 */

static ADC_DEV adcdev;				/* 用于表示ADC设备 */
static volatile int ev_adc = 0;		/* 作为wait_event_interruptible的唤醒条件 */
static int adc_data;

static struct clk	*adc_clock;

#define ADCCON		(*(volatile unsigned long *)(base_addr + S3C2410_ADCCON))	//ADC control
#define ADCTSC		(*(volatile unsigned long *)(base_addr + S3C2410_ADCTSC))	//ADC touch screen control
#define ADCDLY		(*(volatile unsigned long *)(base_addr + S3C2410_ADCDLY))	//ADC start or Interval Delay
#define ADCDAT0		(*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT0))	//ADC conversion data 0
#define ADCDAT1		(*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT1))	//ADC conversion data 1
#define ADCUPDN		(*(volatile unsigned long *)(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)


/* 使能预分频,选择ADC通道,最后启动ADC转换*/
#define START_ADC_AIN(ch, prescale) \
	do{ 	ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
		ADCCON |= ADC_START; \
	}while(0)


/* ADC中断处理函数 */
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
	/* A/D转换器资源可用 */
	if (ADC_enable)
	{
		/* 读ADC转换结果数据 */
		adc_data = ADCDAT0 & 0x3ff;

		/* 唤醒标志位,作为wait_event_interruptible的唤醒条件 */
		ev_adc = 1;
		wake_up_interruptible(&adcdev.wait);
	}
	return IRQ_HANDLED;
}

static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
	char str[20];
	int value;
	size_t len;

	/* 尝试获得ADC_LOCK信号量,如果能够立刻获得,它就获得信号量并返回0 
	 * 否则,返回非零,它不会导致调用者睡眠,可以在中断上下文使用
	 */
	if (down_trylock(&ADC_LOCK) == 0)
	{
		/* 表示A/D转换器资源可用 */
		ADC_enable = 1;

		/* 使能预分频,选择ADC通道,最后启动ADC转换*/
		START_ADC_AIN(adcdev.channel, adcdev.prescale);

		/* 等待事件,当ev_adc = 0时,进程被阻塞,直到ev_adc>0 */
		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));

		/* 将在ADC中断处理函数读取的ADC转换结果赋值给value */
		value = adc_data;
		sprintf(str,"%5d", adc_data);
		copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

		ADC_enable = 0;
		up(&ADC_LOCK);
	}
	else
	{
		/* 如果A/D转换器资源不可用,将value赋值为-1 */
		value = -1;
	}

	/* 将ADC转换结果输出到str数组里,以便传给应用空间 */
	len = sprintf(str, "%d\n", value);
	if (count >= len)
	{
		/* 从str数组里拷贝len字节的数据到buffer,即将ADC转换数据传给应用空间 */
		int r = copy_to_user(buffer, str, len);
		return r ? r : len;
	}
	else
	{
		return -EINVAL;
	}
}

static int tq2440_adc_open(struct inode *inode, struct file *filp)
{
	/* 初始化等待队列头 */
	init_waitqueue_head(&(adcdev.wait));

	/* 开发板上ADC的通道2连接着一个电位器 */
	adcdev.channel=2;	//设置ADC的通道
	adcdev.prescale=0xff;

	DPRINTK( "ADC opened\n");
	return 0;
}

static int tq2440_adc_release(struct inode *inode, struct file *filp)
{
	DPRINTK( "ADC closed\n");
	return 0;
}


static struct file_operations dev_fops = {
	owner:	THIS_MODULE,
	open:	tq2440_adc_open,
	read:	tq2440_adc_read,	
	release:	tq2440_adc_release,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &dev_fops,
};

static int __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);
	
	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" initialized\n");
	return ret;
}

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);
}

EXPORT_SYMBOL(ADC_LOCK);
module_init(dev_init);
module_exit(dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.embedsky.net");
MODULE_DESCRIPTION("ADC Drivers for EmbedSky SKY2440/TQ2440 Board and support touch");
ADC应用测试参考源码:

/*************************************

NAME:EmbedSky_adc.c
COPYRIGHT:www.embedsky.net

*************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <errno.h>
#include <string.h>

int main(void)
{
	int fd ;
	char temp = 1;

	fd = open("/dev/adc", 0);
	if (fd < 0)
	{
		perror("open ADC device !");
		exit(1);
	}
	
	for( ; ; )
	{
		char buffer[30];
		int len ;

		len = read(fd, buffer, sizeof buffer -1);
		if (len > 0)
		{
			buffer[len] = '\0';
			int value;
			sscanf(buffer, "%d", &value);
			printf("ADC Value: %d\n", value);
		}
		else
		{
			perror("read ADC device !");
			exit(1);
		}
		sleep(1);
	}
adcstop:	
	close(fd);
}
测试结果:

[WJ2440]# ./adc_test 
ADC Value: 693
ADC Value: 695
ADC Value: 694
ADC Value: 695
ADC Value: 702
ADC Value: 740
ADC Value: 768
ADC Value: 775
ADC Value: 820
ADC Value: 844
ADC Value: 887
ADC Value: 937
ADC Value: 978
ADC Value: 1000
ADC Value: 1023
ADC Value: 1023
ADC Value: 1023

2017-07-24 16:49:18 a1299600490 阅读数 1293


1.1ADC硬件 模拟信号:声音,电压,电流,温度,湿度,压力,速度:

数字信号:0.1这些数字量组成的信号 

数字系统不能直接处理模拟信号,需要将模拟信号转换成对应的数字信号;

不管是数字信号还是描述的同一个事物,只是这个信号在不同形式下的表现实现!

 

1.2信号之间的转换:AD:模拟信号转数字信号的过程;DA:数字信号转成模拟信号的过程

ADC:将模拟信号转成数字信号的硬件单元 DAC: 将数字信号转成模拟信号的硬件单元

 

案例:手机的录音和放音 录音:就是将声音模拟信号转成数字信号的过程--ADC硬件单元-音频文件,二进制文件 放音:数字信号转成声音模拟信号的过程-DAC硬件单元

 

1.3 S5PV210自带的adc的硬件特性,芯片手册1978

模拟输入通道有10路:ain0-ain9。同一时刻只能转化一路模拟信号;这10个io不能复用,只能做input,工作频率:最大5mhz,时钟源为pclk=66mhz,注意降频工作。adc转换器一旦开启信号的转换,转换过程需要一定的时间,一旦转换结束,adc硬件给cpu产生一个中断信号,通知cpu来进行处理。衡量adc工作参数指标:分辨率,自带adc的分辨率为12为或者10位;如果分辨率采用10位,就是代表将模拟信号转成数字信号以后,有效的数字量为10位。如果分辨率采用12位,就是代表将模拟信号转成数字信号以后,有效的数字量为12 

模拟输入电压的范围0-3.3v: 注意:最大的模拟输入电压为3.3v,

如果分辨率为10位:那么每一个bit对应的模拟电压为3.3/(1<<10)=0.0032=3.32mv

如果分辨率为12,那么每一个bit对应的模拟电压为3.3/(1<<12)=0.81mv

问:如果现在已知一个10bit的转换以后的数字量为0011000000,请问这个数字量对应的模拟量为? 模拟量的值=001100000 * 3.32mv; 

1.3.1ADC操作设计寄存器:

寄存器组的基地址:0xe1700000 adc控制寄存器:

bit16-》配置分辨率,0:12 1:12位

bit15-》判断是否转换结束,0转换进行中:1:转换结束 判断是否转换结束:中断判断bit15或者轮询方式进行判断

bit14:是否使能分频 0 :no 1:yes

bit6-bit13:设置分频系统,如果adc的工作频率为3.3MHZ,这个寄存器设置为66/3.3-1=19使能方法:读使能,直接使能

bit0:启动adc转换,1:表示启动adc,一旦启动adc此位进行清0  0:表示结束 

1.3.2 ADC数据寄存器:保存adc转换的结果

bit0-bit11:共12bit x位置转换的数据值或者正常的adc转换的值

范围0-0xfff 注意:普通的adc转换的有效值

分辨率为10位:data=数据寄存器的值&0x3ff

分辨率为12位:data=数据寄存器的值&0xfff 

1.3.3 adc中断清除寄存器:清adc的中断,中断处理函数中

0xe170 1018 【0】:写0或者1清楚中断 

1.3.4adc模拟通道选择寄存器:选择模拟输入通道 bit3-bit0:

1.4adc操作步骤: 1.设置正常的工作频率 3.3MHZ 2.设置分辨率,12位 3.设置模拟输入通道,AIN1

4.启动硬件adc,硬件adc开始对模拟电压信号进行转换 5.adc转换结束产生中断信号

6.中断处理函数中清中断,唤醒休眠的进程 7.进程读取转换以后的数字量,进行输出

 

1.5软件实现案例()

1.用户需求 在qt图形界面上,每隔5s动态刷新显示电压值,配置adc的分辨率和模拟输入通道

2.驱动的设计1.对用户提供的接口:read:读转换以后的电压值;启动adc;判断是否转换结束,如果没有,进入休眠;读取转换以后的数字量上报到用户空间ioctl:配置分辨率和模拟输入通道

 2.采用混杂设备驱动实现方法3.寄存器地址要进行ioremap+ platform4.注册中断处理函数:irq_adc中断号5.由于adc的处理速度慢于cpu(应用程序的读取速度),所以在adc没有转换结束时,进行进入休眠状态(等待队列机制)6.adc正式工作之前,adc的默认工作参数。

3.linux内核对虚拟地址的访问读写,提供相关的函数: 读readb:读一字节readw:读2字节

readl:读4字节函数参数都是内核虚拟地址.

写:writeb:写1字节writew:写2字节writel:写4字节 writel(1(值),地址);

2016-09-27 10:21:40 feng85016578 阅读数 4131

ADC按键驱动

Adc键盘原理图如下,将串联电阻之间分别用按键引出来与地相连,当按键按下时端电压会发生改变。基本思想是在ADC驱动基础上,对采样电压进行判断,检测是哪一个按键按下。

1.      ADC驱动分析

在init()函数中,首先获取adc的时钟,并用clk_enable进行使能,然后使用ioremap将ADC寄存器地址映射到kernel中(内核中对ADC只能使用虚拟地址进行访问),之后调用probe()函数完成定时器的初始化和ADC寄存器的初始化。

/////////////////////////////////////////////////////////////////////////////////////////////////

内核定时器使用步骤:

<1>使用static struct timer_list adc_key_timer定义一个名字为adc_key_time的定时器。

<2>adc_key_time.function =adc_key_timer_fun 定义一个定时器中断服务函数。

<3>add_time(&adc_key_timer)将定时器在kernel 中注册

<4>mod_timer(&adc_key_timer,jiffies+HZ)最后激活定时器开始计时,时间由jiffies+HZ设定。

 

调试方法:

<1>寄存器初始化一般放在probe函数中,可以使用prink来反映寄存器(特别是中断)是否配置成功

<2>在单板下使用如下命令:

           cd  /proc/gcore      

           echo  RD  fe005400  7 > regs  ///fe005400为寄存器地址  7为读取寄存器个数

来读取寄存器内是否被设置成功

/////////////////////////////////////////////////////////////////////////////////////////////////

接下来使用request_irq函数完成ADC中断的注册,需要注意的是在该函数中,ADC的中断号需要加一,并且中断标志位设置成为IRQF_SHARED。在初始化最后部分使用misc_register将ADC当做misc设备完成注册。

 

2.      在file_operations结构体中主要定义了三个函数,其中adc_key_open是完成设备打开、adc_key_release是设备关闭,adc_key_read()是完成设备的读取。在adc_key_read()函数中,首先定义一个全局变量ev_adc作为按键按下的标志位,当按键没有按下时,使用wait_enent_interruptible进入等待队列,当按键按下触发中断后,使用wake_up_interruptible唤醒等待队列,然后在read函数中将按键标志位ev_adc清零,使用copy_to_user()函数将kernel中的数据传递到应用层,供用户使用。然后使用mod_timer()触发定时器,跳入定时器中断服务程序中去。

///////////////////////////////////////////////////////////////////////////////////////////////

Linux驱动程序中,可以使用等待队列(waitqueue)来实现阻塞进程的唤醒

等待队列的使用步骤:

<1>DECLARE_WAIT_QUEUE_HEAD(my_queue)定义并初始化列头"

<2>  DECLARE_WAITQUEUE(name,tsk);义并初始化一个名为name的等待队列(在本程序中并没有使用到这部分)

<3>添加/移除等待队列(也没有用到这部分)

 add_wait_queue()用于将等待队列wait添加到等待队列头q指向的等待队列链表中,而remove_wait_queue()用于将等待队列wait从附属的等待队列头q指向的等待队列链表中移除。

<4>等待事件wait_event_interruptible(queue, condition);将进程挂起。

<5>wake_up_interruptible(&queue),queue队列中的进程唤醒,前提条件是必须保证

cndition为真,才能使用wake_up_interruptible

///////////////////////////////////////////////////////////////////////////////////////////////

3. ADC中断处理函数

该部分是adc按键与一般adc驱动程序区别最大的部分,在该部分中断中,主要使用ADC高阈值中断和低阈值中断来检查按键的松开和按下,此外对采集的电压会进行一个判断,确定是哪一个按键被按下。

高阈值中断和低阈值中断的切换:在ADC中断中只有一个中断号,因此只存在一个中断服务程序,因此对于高阈值中断和低阈值中断需要进行软件的处理。首先将ADC设定为低阈值中断,在按键按下后,ADC采样电压低于设定电压,进入中断服务程序,在中断程序中首先判断ADC中断寄存器值,若为低阈值则在执行完按键按下处理过程后将ADC寄存器设置成为高阈值中断。在松开按键后,当ADC采样电压高于设定的电压,则进入中断服务程序,在程序中显示按键松开信息后将ADC中断寄存器设置为低阈值触发,等待下一次按键的按下。

        

        

        

4.      源码如下:

/////////////////////////////////////////////////////////////////////////////////////////////////////

#include<linux/errno.h>

#include<linux/kernel.h>

#include<linux/init.h>

#include<linux/module.h>

#include<linux/init.h>

#include<linux/clk.h>

#include<linux/input.h>

#include<linux/miscdevice.h>

#include<linux/timer.h>

#include<linux/sched.h>

#include<linux/interrupt.h>

#include<asm/io.h>

#include<asm/irq.h>

#include<asm/uaccess.h>

#include<linux/sched.h>

#include<linux/wait.h>

#include<linux/delay.h>

#define ADC_BASE_ADDR  (0xfe005400)

#define DEVICE_NAME   "adc_key"

#define REG_ADC_INTV 0x04

#define REG_ADC_CTRL 0x1c

#define REG_ADC_DIV    0x00

#define REG_ADC_DATA1        0x0c

#define REG_ADC_INTR1        0x24

#define IRQ_ADC         24

#define REG_ADC_STATUS  0x28

 

static void __iomem *adc_base;    

static struct clk *adc_clk;

static int adc_data;

static volatile int ev_adc = 0;

static int adc_data;

static DECLARE_WAIT_QUEUE_HEAD(adc_key_waitq);

static struct timer_list adc_key_timer;

 

 

static struct botton{

         int  name;

         intdata;

};

struct botton adc_button;

 

static int gcsoc030_adc_key_select(int val)

{

         adc_button.data= val;

         if(val>=0&&val<0x5){adc_button.name= 1;}

          else if(val<0xff){adc_button.name = 2;}

           else if(val<0x1ef){adc_button.name = 3;}

             else if(val<0x3e0){adc_button.name = 4;}

 

         return0;

        

}

 

 

static int gcsoc030_adc_key_release(structinode *inode , struct file *file)

{

         return0;

}

 

static irqreturn_t adc_irq(int irq,void*dev_id)

{

         u32val = readl(adc_base+REG_ADC_INTR1);

         if(val== 0x83e00000)

         {

                   mdelay(80);

                   iowrite32(1<<3,adc_base+REG_ADC_STATUS);

                   iowrite32(0,adc_base+REG_ADC_INTR1);    

                   adc_data= readl(adc_base+REG_ADC_DATA1);

                   adc_button.data= adc_data;

                   iowrite32(0,adc_base+REG_ADC_CTRL);

                   gcsoc030_adc_key_select(adc_data);

                   ev_adc= 1;

                   wake_up_interruptible(&adc_key_waitq);    

//               printk("thebutton%d has been pressed ,the value is%d\n",adc_button.name,adc_button.data);

                   iowrite32(0x000083e0,adc_base+REG_ADC_INTR1);

 

         }

         elseif(val == 0x000083e0)

         {       

 

                   ev_adc= 0;

                   iowrite32(1<<2,adc_base+REG_ADC_STATUS);

                   iowrite32(0,adc_base+REG_ADC_INTR1);

                   iowrite32(0,adc_base+REG_ADC_CTRL);

                   printk("thebutton is released\n");

                   mod_timer(&adc_key_timer,jiffies+8*(HZ/100));

                   iowrite32(0x83e00000,adc_base+REG_ADC_INTR1);

         }

         returnIRQ_HANDLED;

}

 

 static ssize_t gcsoc030_adc_key_read(structfile *file,char *buffer,size_t count,loff_t *ppos)

{

         if(!ev_adc)

         {

                   wait_event_interruptible(adc_key_waitq,ev_adc);

         }

         ev_adc= 0;

         copy_to_user(buffer,(char*)&adc_button,sizeof(adc_button));

         memset((char*)&adc_button,0,sizeof(adc_button));

         mod_timer(&adc_key_timer,jiffies+HZ);

         returnsizeof(adc_button);

}

 

static int gcsoc030_adc_key_open(structinode *inode,struct file *file)

{

 

         return0;

}

 

static void adc_key_timer_fun(void)

{

         iowrite32(0x61,adc_base+REG_ADC_CTRL);

}

 

 static struct file_operations gcsoc030_adc_key_fops = {

          .owner = THIS_MODULE,

          .open  = gcsoc030_adc_key_open,

          .read  = gcsoc030_adc_key_read,

          .release = gcsoc030_adc_key_release,

  };

 

 

 static struct miscdevice adc_key_miscdev=

{

         .minor= MISC_DYNAMIC_MINOR,        

         .name= DEVICE_NAME,         

         .fops=&gcsoc030_adc_key_fops,

};

 

static int adc_key_probe(void)

{       

 

         init_timer(&adc_key_timer);

         adc_key_timer.function= adc_key_timer_fun;

         add_timer(&adc_key_timer);

        

         iowrite32(0x83e00000,adc_base+REG_ADC_INTR1);

         iowrite32(0x100,adc_base+REG_ADC_DIV);

         iowrite32(0x61,adc_base+REG_ADC_CTRL);

         iowrite32(0x20,adc_base+REG_ADC_INTV);

 

         printk("///////////////////////////////\n");

 

         return0;

}

 

static int __initgcsoc030_adc_key_init(void)

{

        int ret_irq,ret,flag;

         adc_clk = clk_get(NULL,"adc");

         if(!adc_clk)

         {

                   printk(KERN_ERR"failedto find adc clock source\n");

                   return-ENOENT;

         }

         clk_enable(adc_clk);

 

 

         adc_base= ioremap(ADC_BASE_ADDR, 0x30);

         if(adc_base== NULL)

         {

                   printk(KERN_ERR"failedto remap register block\n");

                   ret= -EINVAL;

                   gotoerr_noclk;

         }

         printk("ioremapsuccess base = %x\n", adc_base);

 

 

         flag= adc_key_probe();

         if(flag==0)

         {

                   printk("interrupthas initied\n");

         }

         ret_irq= request_irq(IRQ_ADC + 1,adc_irq,IRQF_SHARED,DEVICE_NAME,1);

         printk("///////////////////////////////\n");

         printk("ret_irqis:%d\n",ret_irq);

         if(ret_irq< 0)

         {

                  printk(KERN_ERR"IRQ%d error%d\n",IRQ_ADC,ret);

                   return-EINVAL;

         }

 

 

 

        ret = misc_register(&adc_key_miscdev);

        if(ret < 0){

                 printk(DEVICE_NAME "can'tregister major number\n");

                    goto err_nomap;

        }

        

         printk(DEVICE_NAME"initialiazed");

         return0;

err_noclk:

         clk_disable(adc_clk);

         clk_put(adc_clk);

err_nomap:

         iounmap(adc_base);

         returnret;

}

 

 

static void __exitgcsoc030_adc_key_exit(void)

{

         iounmap(adc_base);

         if(adc_clk)

         {

                   clk_disable(adc_clk);

                   clk_put(adc_clk);

                   adc_clk= NULL;

         }

         misc_deregister(&adc_key_miscdev);

}

 

module_init(gcsoc030_adc_key_init);

module_exit(gcsoc030_adc_key_exit)

MODULE_LICENSE("GPL");

2017-10-01 18:15:56 zl199203 阅读数 510

本篇目的:移植ADC驱动,并测试。

本篇参考:http://singleboy.blog.163.com/blog/static/54900194201152284152419/

 

说明:

1、本文源码参考链接的源码修改,在linux-4.9.2下移植驱动请务必使用本源码

2、源码为了方便粘贴,部分注释已经去掉,需要注释请查看参考网址


10.1 添加ADC驱动源码文件

(1)添加头文件

root@ubuntu:~/linux-4.9.2# vim drivers/misc/s3c24xx-adc.h

 

添加如下源码

#ifndef _S3C2410_ADC_H_
#define _S3C2410_ADC_H_
 
#define ADC_WRITE(ch, prescale) ((ch)<<16|(prescale))
#define ADC_WRITE_GETCH(data) (((data)>>16)&0x7)
#define ADC_WRITE_GETPRE(data) ((data)&0xff)
 
#endif /* _S3C2410_ADC_H_ */

 

保存退出

 

(2)添加mini2440_adc.c文件

root@ubuntu:~/linux-4.9.2# vim drivers/misc/mini2440_adc.c

 

添加如下源码

 

#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-adc.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
 
#include "s3c24xx-adc.h"
 
#undef DEBUG
#ifdef DEBUG
#define DPRINTK(x...) {printk("%s%d\n",__FUNCTION__,__LINE__);}
#else
#define DPRINTK(x...) (void)(0)
#endif
 
#define DEVICE_NAME "adc"
 
static void __iomem *adc_base; /*定义了一个用来保存经过虚拟映射后的内存地址*/
 
typedef struct {
          wait_queue_head_t wait;
           int channel;
            int prescale;
}ADC_DEV;
static ADC_DEV adcdev;
 
DEFINE_SEMAPHORE(ADC_LOCK);
 
/*用于标识AD转换后的数据是否可以读取,0表示不可读取*/
static volatile int ev_adc = 0;
 
/*用于保存读取的AD转换后的值,该值在ADC中断中读取*/
static int adc_data;
 
/*保存从平台时钟队列中获取ADC的时钟*/
static struct clk *adc_clk;
 
#define ADCCON (*(volatile unsigned long *)(adc_base +S3C2410_ADCCON))
#define ADCTSC (*(volatile unsigned long *)(adc_base +S3C2410_ADCTSC))
#define ADCDLY (*(volatile unsigned long *)(adc_base +S3C2410_ADCDLY))
#define ADCDAT0 (*(volatile unsigned long *)(adc_base +S3C2410_ADCDAT0))
#define ADCDAT1 (*(volatile unsigned long *)(adc_base +S3C2410_ADCDAT1))
#define ADCUPDN (*(volatile unsigned long *)(adc_base + 0x14))
#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)
 
#define start_adc(ch, prescale) \
         do{ \
                    ADCCON = PRESCALE_EN | PRSCVL(prescale) |ADC_INPUT((ch)) ; \
                    ADCCON |= ADC_START; \
         }while(0)
 
static irqreturn_t adc_irq(int irq, void *dev_id)
{
         if (!ev_adc)
         {
                   /*读取AD转换后的值保存到全局变量adc_data中,S3C2410_ADCDAT0定义在regs-adc.h中,
              *            这里为什么要与上一个0x3ff,很简单,因为AD转换后的数据是保存在ADCDAT0的第0-9位,
     *                       所以与上0x3ff(即:1111111111)后就得到第0-9位的数据,多余的位就都为0*/
             adc_data = ADCDAT0 & 0x3ff;
               /*将可读标识为1,并唤醒等待队列*/                         
             ev_adc = 1;
            
             wake_up_interruptible(&adcdev.wait);
         }
         return IRQ_HANDLED;
}
 
static ssize_t adc_read(struct file *filp, char *buffer, size_tcount, 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(adcdev.channel, adcdev.prescale);
                             /*使等待队列进入睡眠*/
                             wait_event_interruptible(adcdev.wait, 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);
          
}
 
static int adc_open(struct inode *inode, struct file *filp)
{
         int ret;
         /* normal ADC */
         ADCTSC = 0;
 
         init_waitqueue_head(&(adcdev.wait));
         adcdev.channel=0;
         adcdev.prescale=0xff;
         /* 申请ADC中断服务,这里使用的是共享中断:IRQF_SHARED,为什么要使用共享中断,因为在触摸屏驱动中
          *          *      也使用了这个中断号。中断服务程序为:adc_irq在下面实现,IRQ_ADC是ADC的中断号,这里注意:
          *                   *             申请中断函数的最后一个参数一定不能为NULL,否则中断申请会失败,这里传入的是ADC_DEV类型的变量*/
         ret = request_irq(IRQ_ADC,adc_irq, IRQF_SHARED, DEVICE_NAME, &adcdev);
         if (ret)
         {
                   /*错误处理*/
                   printk(KERN_ERR"IRQ%d error %d\n", IRQ_ADC, ret);
                   return-EINVAL;
         }
        
         DPRINTK( "adcopened\n");
         return 0;
        
}
 
static int adc_release(struct inode *inode, struct file *filp)
{
          DPRINTK( "adc closed\n");
           return 0;
}
 
static struct file_operations dev_fops = {
         owner: THIS_MODULE,
          open: adc_open,
            read: adc_read,
      release: adc_release,
};
 
static struct miscdevice adc_miscdev = {
          .minor = MISC_DYNAMIC_MINOR,
          .name = DEVICE_NAME,
          .fops = &dev_fops,
};
 
static int __init dev_init(void)
{
         int ret;
         /*  1,从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,因为ADC的转换频率跟时钟有关。
            *      系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
         adc_clk = clk_get(NULL,"adc");
        
         clk_prepare_enable(adc_clk);
        
         if (!adc_clk) {
                   printk(KERN_ERR"failed to get adc clock source\n");
                   return-ENOENT;
         }
        
 
           /*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/
         clk_enable(adc_clk);
 
 
         /*  2,将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;
                   gotoerr_noclk;
         }
 
 
         /*   3,把看ADC注册成为misc设备,misc_register定义在miscdevice.h中
          adc_miscdev结构体定义及内部接口函数在第2步中讲,MISC_DYNAMIC_MINOR是次设备号,定义在miscdevice.h中*/
         ret =misc_register(&adc_miscdev);
         if (ret)                                
         {
                   /*错误处理*/
                   printk(KERN_ERR"Cannot register miscdev on minor=%d (%d)\n", MISC_DYNAMIC_MINOR,ret);
                   gotoerr_nomap;
         }
        
         printk(DEVICE_NAME"\tinitialized!\n");
        
         return 0;
        
err_noclk:
         clk_disable(adc_clk);
         clk_put(adc_clk);
 
err_nomap:
         iounmap(adc_base);
        
         return ret;
}
 
static void __exit dev_exit(void)
{       
         free_irq(IRQ_ADC,&adcdev);
         iounmap(adc_base); /*释放虚拟地址映射空间*/
         if (adc_clk)  /*屏蔽和销毁时钟*/
         {
                   clk_disable(adc_clk);
                   clk_put(adc_clk);
                   adc_clk =NULL;
         }
         misc_deregister(&adc_miscdev);
}
 
EXPORT_SYMBOL(ADC_LOCK);
 
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("singleboy");
MODULE_DESCRIPTION("Mini2440 ADC Driver");

 

(3)编辑Makefile

root@ubuntu:~/linux-4.9.2# vim drivers/misc/Makefile

 

在第35行添加红色部分

 

obj-$(CONFIG_TI_DAC7512)       += ti_dac7512.o

obj-$(CONFIG_C2PORT)           += c2port/

obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o

obj-$(CONFIG_HMC6352)          += hmc6352.o

obj-y                          += eeprom/

 

(4)编辑Kconfig

root@ubuntu:~/linux-4.9.2# vim drivers/misc/Kconfig

 

在第87行添加红色部分

 

        help

          Some chips providemore than one TC block, so you have the

          choice of which oneto use for the clock framework.  Theother

          TC can be used forother purposes, such as PWM generation and

          interval timing.

 

config MINI2440_ADC

 bool"ADC driver for FriendlyARM Mini2440 development boards"

 depends onMACH_MINI2440

 default yif MACH_MINI2440

 help

  this isADC driver for FriendlyARM Mini2440 development boards

  Notes: thetouch-screen-driver required this option

 

config DUMMY_IRQ

        tristate "DummyIRQ handler"

        default n

        ---help---

 

(5)在menuconfig中添加ADC驱动支持

root@ubuntu:~/linux-4.9.2# make menuconfig

 

Device Drivers --->

      [*] Misc devices  ---> 

 

10.2 编译、测试

(1)编译源码

root@ubuntu:~/linux-4.9.2# make

root@ubuntu:~/linux-4.9.2# ./mkuImage.sh

 

(2)重启测试

重启开发板

 

(3)编写测试文件

root@ubuntu:~/linux-4.9.2# vim ../NFS/rootfs/home/plg/adc_test.c

 

加入如下代码

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
 
int main(int argc, char **argv)
{
        int fd;
 
        fprintf(stderr,"press Ctrl-C to stop\n");
 
        fd =open("/dev/adc", 0);
 
        if(fd < 0)
        {
               printf("Open ADC Device Faild!\n");
                exit(1);
        }
 
        while(1)
        {
                int ret;
                int data;
 
               fprintf(stderr, "read adc\n");
                ret = read(fd, &data, sizeof(data));
 
               fprintf(stderr, "read adc1\n");
 
                if(ret !=sizeof(data))
                {
                       fprintf(stderr, "read adc2\n");
 
                       if(errno != EAGAIN)
                        {
                               printf("Read ADC Device Faild!\n");
                        }
 
                       continue;
                }
                else
                {
                       printf("Read ADC value is: %d\n", data);
}
               fprintf(stderr, "read adc3\n");
 
                 sleep(1);
 
        }
 
        close(fd);
 
        return 0;
}


(4)编译测试源码

root@ubuntu:~/linux-4.9.2# cd ../NFS/rootfs/home/plg/

root@ubuntu:~/NFS/rootfs/home/plg# arm-linux-gcc -o adc_testadc_test.c

 

(5)测试源码

打开secrueCRT,进入console

[root@FriendlyARM /]# cd /home/plg/

[root@FriendlyARM plg]# ./adc_test

可以看到:

read adc1

Read ADC value is: 838

read adc3

read adc

read adc1

Read ADC value is: 833

read adc3

read adc

read adc1

Read ADC value is: 884

read adc3

read adc

read adc1

Read ADC value is: 884

read adc3

read adc

read adc1

Read ADC value is: 959

read adc3

read adc

read adc1

Read ADC value is: 967

read adc3

read adc

read adc1

Read ADC value is: 968

read adc3

read adc

read adc1

Read ADC value is: 968

read adc3

read adc

read adc1

Read ADC value is: 967

 

说明ADC驱动成功。

2011-11-16 21:20:13 HL5654 阅读数 0

Linux-2.6.32.2内核在mini2440上的移植(七)---添加ADC驱动  

【2】在内核中添加ADC 驱动

Linux-2.6.32.2 内核并没有提供支持S3C2440 的ADC 驱动程序,由于《移植开发实战指南》中ADC部分代码在实际测试中始终输出-1,而无法通过测试,于是结合博主黄刚嵌入式Linux之我行——S3C2440上ADC驱动实例开发讲解的ADC驱动程序作了下修改,经过修改后有一个好处是方便地通过s3c24xx-adc.h文件中提供的宏修改通道获取采样数据,该头文件的代码也在drivers/char目录下内容为:

#ifndef _S3C2410_ADC_H_
#define _S3C2410_ADC_H_

#define ADC_WRITE(ch, prescale) ((ch)<<16|(prescale))
#define ADC_WRITE_GETCH(data) (((data)>>16)&0x7)
#define ADC_WRITE_GETPRE(data) ((data)&0xff)

#endif /* _S3C2410_ADC_H_ */

驱动程序的文件名为:mini2440_adc.c位于drivers/char 目录下。由上述内容可知,ADC 驱动和触摸屏驱动若想共存,就必须解决共享“A/D 转换器”资源这个问题,因此在ADC 驱动程序中声明了一个全局的“ADC_LOCK”信号量,ADC 驱动程序的内容和注解如下:

#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

//;定义ADC 转换设备名称,将出现在/dev/adc
#define DEVICE_NAME "adc"

static void __iomem *adc_base;/*定义了一个用来保存经过虚拟映射后的内存地址*/

//;定义ADC 设备结构
typedef struct {
 wait_queue_head_t wait;
 int channel;
 int prescale;
}ADC_DEV;
static ADC_DEV adcdev;

//;声明全局信号量,以便和触摸屏驱动程序共享A/D 转换器
DECLARE_MUTEX(ADC_LOCK);

//;ADC驱动是否拥有A/D 转换器资源的状态变量
//static volatile int OwnADC = 0;

/*用于标识AD转换后的数据是否可以读取,0表示不可读取*/
static volatile int ev_adc = 0;

/*用于保存读取的AD转换后的值,该值在ADC中断中读取*/
static int adc_data;

/*保存从平台时钟队列中获取ADC的时钟*/
static struct clk *adc_clk;

//;定义ADC 相关的寄存器
#define ADCCON (*(volatile unsigned long *)(adc_base + S3C2410_ADCCON)) //ADC control
#define ADCTSC (*(volatile unsigned long *)(adc_base + S3C2410_ADCTSC)) //ADC touch screen control
#define ADCDLY (*(volatile unsigned long *)(adc_base + S3C2410_ADCDLY)) //ADC start or IntervalDelay
#define ADCDAT0 (*(volatile unsigned long *)(adc_base + S3C2410_ADCDAT0)) //ADC conversion data 0
#define ADCDAT1 (*(volatile unsigned long *)(adc_base + S3C2410_ADCDAT1)) //ADC conversion data 1
#define ADCUPDN (*(volatile unsigned long *)(adc_base + 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)
#define start_adc(ch, prescale) \
do{ \
 ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
 ADCCON |= ADC_START; \
}while(0)
/*设置ADC控制寄存器,开启AD转换*/
/*static void start_adc(int ch,int prescale)
{
    unsigned int tmp;

    tmp = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT(ch);//(1 << 14)|(255 << 6)|(0 << 3);// 0 1 00000011 000 0 0 0
 
//此处writl()的原型是void writel(u32 b, volatile void __iomem *addr),addr是经过地址重映射后的地址
    writel(tmp, ADCCON); //AD预分频器使能、模拟输入通道设为AIN0

    tmp = readl(ADCCON);
    tmp = tmp | ADC_START;
//(1 << 0);   // 0 1 00000011 000 0 0 1
    writel(tmp, ADCCON);  
//AD转换开始
}
问题:此函数被调用时为什么地址映射错误?答案应该需要使用专用的函数iowrite32操作。
*/

//;ADC 中断处理函数
static irqreturn_t adc_irq(int irq, void *dev_id)
{
 //;如果ADC 驱动拥有“A/D 转换器”资源,则从ADC 寄存器读取转换结果
 if (!ev_adc)
 {
  /*读取AD转换后的值保存到全局变量adc_data中,S3C2410_ADCDAT0定义在regs-adc.h中,
           这里为什么要与上一个0x3ff,很简单,因为AD转换后的数据是保存在ADCDAT0的第0-9位,
           所以与上0x3ff(即:1111111111)后就得到第0-9位的数据,多余的位就都为0*/
  adc_data = ADCDAT0 & 0x3ff;

  /*将可读标识为1,并唤醒等待队列*/
  ev_adc = 1;
  wake_up_interruptible(&adcdev.wait);
 }
 return IRQ_HANDLED;
}

//;ADC 读函数,一般对应于用户层/应用层的设备读函数(read)
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(adcdev.channel, adcdev.prescale);

              /*使等待队列进入睡眠*/
         wait_event_interruptible(adcdev.wait, 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设备的函数,一般对应于用户态程序的open
static int adc_open(struct inode *inode, struct file *filp)
{
 int ret; 
 /* normal ADC */
 ADCTSC = 0;
 //;初始化中断队列
 init_waitqueue_head(&(adcdev.wait));
 adcdev.channel=0;//;缺省通道为“0”
 adcdev.prescale=0xff;
 /* 申请ADC中断服务,这里使用的是共享中断:IRQF_SHARED,为什么要使用共享中断,因为在触摸屏驱动中
      也使用了这个中断号。中断服务程序为:adc_irq在下面实现,IRQ_ADC是ADC的中断号,这里注意:
      申请中断函数的最后一个参数一定不能为NULL,否则中断申请会失败,这里传入的是ADC_DEV类型的变量*/
 ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, &adcdev);
 if (ret)
     {
        
   /*错误处理*/
        printk(KERN_ERR "IRQ%d error %d\n", IRQ_ADC, ret);
        return -EINVAL;
     }

 DPRINTK( "adc opened\n");
   return 0;

}
static int adc_release(struct inode *inode, struct file *filp)
{
 DPRINTK( "adc closed\n");
 return 0;
}
static struct file_operations dev_fops = {
 owner: THIS_MODULE,
 open: adc_open,
 read: adc_read,
 release: adc_release,
};
static struct miscdevice adc_miscdev = {
 .minor = MISC_DYNAMIC_MINOR,
 .name = DEVICE_NAME,
 .fops = &dev_fops,
};
static int __init dev_init(void)
{
 int ret;
 /*  1,从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,因为ADC的转换频率跟时钟有关。
     系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
 adc_clk = clk_get(NULL, "adc");
 if (!adc_clk) {
  printk(KERN_ERR "failed to get adc clock source\n");
  return -ENOENT;
 }
 /*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/
 clk_enable(adc_clk);

 /*  2,将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;
 }

 /*   3,把看ADC注册成为misc设备,misc_register定义在miscdevice.h中
   adc_miscdev结构体定义及内部接口函数在第2步中讲,MISC_DYNAMIC_MINOR是次设备号,定义在miscdevice.h中*/
   ret = misc_register(&adc_miscdev);
   if (ret)
     {
      
   /*错误处理*/
      printk(KERN_ERR "Cannot register miscdev on minor=%d (%d)\n", MISC_DYNAMIC_MINOR, ret);
      goto err_nomap;
     }
 
   printk(DEVICE_NAME "\tinitialized!\n");
 
 return 0;
 
//以下是上面错误处理的跳转点
err_noclk:
   clk_disable(adc_clk);
   clk_put(adc_clk);

err_nomap:
   iounmap(adc_base);

   return ret;

}

static void __exit dev_exit(void)
{
 
 free_irq(IRQ_ADC, &adcdev);
//;释放中断
 iounmap(adc_base); 
/*释放虚拟地址映射空间*/
 if (adc_clk) 
/*屏蔽和销毁时钟*/
 {
  clk_disable(adc_clk);
  clk_put(adc_clk);
  adc_clk = NULL;
 }
 misc_deregister(&adc_miscdev);
}
//;导出信号量“ADC_LOCK”,以便触摸屏驱动使用
EXPORT_SYMBOL(ADC_LOCK);
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("singleboy");
MODULE_DESCRIPTION("Mini2440 ADC Driver");

说明:杂项设备(misc device)
杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include/linux 目录下有Miscdevice.h 文件,要把自己定义的misc device 从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10,一起归于misc device,其实misc_register 就是用主标号10 调用register_chrdev()的。也就是说,misc设备其实也就是特殊的字符设备。

然后打开drivers/char/Makefile 文件,在大概24 行加入ADC 驱动程序目标模块

obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o
obj-$(CONFIG_C2PORT)  += c2port/
obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o
obj-y    += eeprom/
obj-y    += cb710/

再打开drivers/char/Kconfig 文件,定位到16行附近,加入ADC 驱动配置选项:

menuconfig MISC_DEVICES
 bool "Misc devices"
 default y
 ---help---
   Say Y here to get to see options for device drivers from various
   different categories. This option alone does not add any kernel code.

   If you say N, all options in this submenu will be skipped and disabled.

if MISC_DEVICES

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 ATMEL_PWM
 tristate "Atmel AT32/AT91 PWM support"
 depends on AVR32 || ARCH_AT91SAM9263 || ARCH_AT91SAM9RL || ARCH_AT91CAP9
 help
   This option enables device driver support for the PWM channels
   on certain Atmel processors.  Pulse Width Modulation is used for
   purposes including software controlled power-efficient backlights
   on LCD displays, motor control, and waveform generation.

这样,我们就在内核中添加了ADC 驱动。

【3】确认配置选项

现在内核源代码目录的命令行执行:make menuconfig,依次选择如下子菜单项,找到刚刚添加的ADC 驱动配置选项:
Device Drivers --->
      [*] Misc devices  --->  

如图所示,按空格键选中 ADC 配置选项

Linux-2.6.32.2内核在mini2440上的移植(八)---添加ADC驱动 - singleboy - singleboy的博客

 

 然后退出保存所选配置, 在命令行执行: make zImage , 将会生成arch/arm/boot/zImage,

brd: module loaded
adc     initialized!
S3C24XX NAND Driver, (c) 2004 Simtec Electronics

... ...

说明ADC设备加载成功。

 “adc-test” 测试程序已经集成到我们的文件系统中, 因此在开发板的命令行终端输入:adc-test,旋转开发板上的 W1可调电阻,可以看到 ADC 转换的结果也在变动,按下触摸屏时,会输出“-1” ,这和我们在驱动程序中设置的结果是一样的,如图:

 

接下来,将进行触摸屏驱动移植

 

Linux内核配置文件

阅读数 2292