分类: 嵌入式
2010-01-09 13:57:37
一、开发平台
主 机:VMWare--Fedora 8
开发板:utu2440--64MB Nand / linux-
编译器:arm-linux-gcc-
二、移植步骤
1.修改driver/char/mini2440_adc.c,
之前移植touchscreen驱动时是参考友善的方法,分析代码发现mini2440_adc.c中没有提供ioctl命令来设置adc输入通道,而utu2440上有两个adc测量通道,因此按具体情况将友善的adc驱动做相应修改, 修改后adc驱动代码如下:
/* mini2440_adc.c */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// #include "s3c24xx-adc.h" // driver/char/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
#define DEVICE_NAME "adc"
#define ADC_CHANNEL 1
#define ADC_PRESCALE 2 // adjust prescale command, reserved for future use
static void __iomem *base_addr;
typedef struct {
wait_queue_head_t wait;
int channel;
int prescale;
}ADC_DEV;
DECLARE_MUTEX(ADC_LOCK);
static int OwnADC = 0;
static ADC_DEV adcdev;
static volatile int ev_adc = 0;
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)
#define START_ADC_AIN(ch, prescale) \
do{ \
ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
ADCCON |= ADC_START; \
}while(0)
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
if (OwnADC) {
adc_data = ADCDAT0 & 0x3ff;
ev_adc = 1;
wake_up_interruptible(&adcdev.wait);
}
return IRQ_HANDLED;
}
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 (down_trylock(&ADC_LOCK) == 0) {
OwnADC = 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;
sprintf(str,"%5d", adc_data);
copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));
OwnADC = 0;
up(&ADC_LOCK);
} else {
value = -1;
}
//len = sprintf(str, "%d\n", value);
len = sprintf(str, "%d", value); // 这个‘\n’在后面的测试程序中会显得多余
if (count >= len) {
int r = copy_to_user(buffer, str, len);
return r ? r : len;
} else {
return -EINVAL;
}
}
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;
}
static int s3c2410_adc_release(struct inode *inode, struct file *filp)
{
DPRINTK( "adc closed\n");
return 0;
}
static int s3c2410_adc_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
switch(cmd) {
case ADC_CHANNEL:
adcdev.channel = (int)arg & 0x3;
break;
case ADC_PRESCALE:
adcdev.prescale = (int)arg & 0xff;
break;
default:
return -EINVAL;
break;
}
return 0;
}
static struct file_operations dev_fops = {
owner: THIS_MODULE,
open: s3c2410_adc_open,
read: s3c2410_adc_read,
release: s3c2410_adc_release,
ioctl: s3c2410_adc_ioctl,
};
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);
/* 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;
}
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("FriendlyARM Inc.");
2. 内核配置选项不变,重新编译内核,并下载到开发板
3. adc测试应用程序如下
/* test_adc.c */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ADC_CHANNEL 1
#define ADC_PRESCALE 2 //reserved for future use
void usage(char *argv0)
{
printf("Usage: %s [channel]\n", argv0);
printf("channel: 0 - 1\n");
}
int main(int argc, char *argv[])
{
int fd;
int args;
int n;
float voltage;
char buf[20];
if (argc != 2)
{
usage(argv[0]);
return -1;
}
args = atoi(argv[1]);
if (args != 0 && args != 1)
{
usage(argv[0]);
return -1;
}
fd = open("/dev/adc", O_RDONLY);
if (fd < 0)
{
printf("can't open adc\n");
return -1;
}
if (ioctl(fd, ADC_CHANNEL, args))
{
printf("ioctl ADC_CHANNEL failed\n");
return -1;
}
while (1)
{
memset(buf, 0, sizeof(buf));
n = read(fd, buf, sizeof(buf));
if (n > 0)
{
voltage = (atoi(buf) * 3.3) / 1024;
printf("channel[%d]: %.3fV\n", args, voltage);
}
sleep(2);
}
close(fd);
return 0;
}
启动开发板后测试adc,调整两个输入通道的电位器输出会在0V到3.3V之间变化:
[root@liuzg]# ./test_adc
Usage: ./test_adc [channel]
channel: 0 - 1
[root@liuzg]# ./test_adc 0
channel[0]: 1.895V
channel[0]: 1.895V
channel[0]: 1.895V
channel[0]: 1.895V
channel[0]: 1.895V
^C
[root@liuzg]# ./test_adc 1
channel[1]: 3.294V
channel[1]: 3.297V
channel[1]: 3.297V