天嵌 ARM开发社区

 找回密码
 注册
查看: 5554|回复: 15

怎样改ADC的驱动

[复制链接]
回帖奖励 40 回复本帖可获得 10 奖励! 每人限 1 次(中奖概率 50%)
月骅 发表于 2012-8-31 15:18:14 | 显示全部楼层 |阅读模式
/*************************************

NAME:EmbedSky_hello.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"

static void __iomem *base_addr;

typedef struct
{
        wait_queue_head_t wait;
        int channel;
        int prescale;
}ADC_DEV;

DECLARE_MUTEX(ADC_LOCK);
static int ADC_enable = 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 (ADC_enable)
        {
                adc_data = ADCDAT0 & 0x3ff;

                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;
        if (down_trylock(&ADC_LOCK) == 0)
        {
                ADC_enable = 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));

                ADC_enable = 0;
                up(&ADC_LOCK);
        }
        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;
        }
}

static int tq2440_adc_open(struct inode *inode, struct file *filp)
{
        init_waitqueue_head(&(adcdev.wait));

        adcdev.channel=2;        //设置ADC的通道
        //adcdev.prescale=0xff;
          adcdev.prescale=0x31;
        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");改了预分频值,转换速度能达到1Mhz,怎么1s采上来的点还不到1K个?


TQ-ZQL 发表于 2012-8-31 15:45:24 | 显示全部楼层
如果你是如此高速、大量采样的话我建议你在驱动中使用轮询的方式去访问。因为系统调用,甚至中断都是有额外的时间开销的。
 楼主| 月骅 发表于 2012-9-5 12:53:33 | 显示全部楼层
TQ-ZQL 发表于 2012-8-31 15:45
如果你是如此高速、大量采样的话我建议你在驱动中使用轮询的方式去访问。因为系统调用,甚至中断都是有额外 ...

版主大大,我把ADC驱动改成了查询方式为什么还是没有大幅度的提升速度呢。我新开了一贴贴上了我的ADC查询方式驱动,希望能得到关注并给出建议性回复。
 楼主| 月骅 发表于 2012-9-5 12:54:06 | 显示全部楼层
TQ-ZQL 发表于 2012-8-31 15:45
如果你是如此高速、大量采样的话我建议你在驱动中使用轮询的方式去访问。因为系统调用,甚至中断都是有额外 ...

我在这里再贴一遍啊:
/*************************************

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

*************************************/
#include <linux/miscdevice.h>
#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/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/regs-clock.h>
#include <plat/regs-timer.h>
#include <linux/fs.h>
#include <plat/regs-adc.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include "tq2440_adc.h"
#include <linux/interrupt.h>
#undef DEBUG
#define DEBUG
#ifdef DEBUG
#define DPRINTK(x...) {printk(__FUNCTION__);printk("(%d): ",__LINE__);printk(x);}
#else
#define DPRINTK(x...) (void)(0)
#endif
#define DEVICE_NAME        "adc"
static void __iomem *base_addr;
struct adc_dev
{
        struct cdev cdev;
        struct semaphore lock;
        int channel;
        int prescale;
};

struct adc_dev adc_dev;
struct adc_dev *adc_devp;
static struct clk        *adc_clock;
#define START_ADC(ch, prescale) S3C2410_ADCCON_PRSCEN |S3C2410_ADCCON_PRSCVL(prescale) \
| S3C2410_ADCCON_SELMUX(ch) | S3C2410_ADCCON_ENABLE_START

static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
        unsigned long result=0;
        unsigned int data;
        if (down_interruptible(&adc_dev.lock))
        return -ERESTARTSYS;
        iowrite32(START_ADC(adc_dev.channel,adc_dev.prescale),base_addr + S3C2410_ADCCON);
        do{
                result=ioread32(base_addr+S3C2410_ADCCON);
        }while((unsigned int)result&0x01);//ensure the start of conversion
        do{
                result=ioread32(base_addr+S3C2410_ADCCON);
        }while(!(((unsigned int)result)&0x8000));//till the end of conversion       
        data = ioread32(base_addr + S3C2410_ADCDAT0) & 0x3ff;
        copy_to_user(buffer,&data,sizeof(data));
        up(&adc_dev.lock);
        return sizeof(result);
}

static int adc_open(struct inode *inode, struct file *filp)
{
        init_MUTEX(&adc_dev.lock);
        filp->private_data = adc_devp;
        adc_dev.channel=2;       
        adc_dev.prescale=0x13;
        base_addr=ioremap(S3C2410_PA_ADC,0x20);
        adc_clock=clk_get(NULL,"adc");
        if (!adc_clock) {
        DPRINTK("failed to get adc clock source\n");
        return -ENOENT;
        }
        clk_enable(adc_clock);               
        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 adc_fops = {
        owner:        THIS_MODULE,
        open:        adc_open,
        read:        adc_read,       
        release:        adc_release,
};

static struct miscdevice misc = {
      .minor = MISC_DYNAMIC_MINOR,
      .name = DEVICE_NAME,
      .fops = &adc_fops,
};
static int __init adc_init(void)
{
        int ret;
        ret=misc_register(&misc);
        if(ret<0)
        return ret;
        adc_devp=kmalloc(sizeof(struct adc_dev),GFP_KERNEL);
        if(!adc_devp)
        ret=-ENOMEM;
        memset(adc_devp,0,sizeof(struct adc_dev));
        return 0;
        return ret;
}

static void __exit adc_exit(void)
{
        iounmap(base_addr);

        if (adc_clock)
        {
                clk_disable(adc_clock);
                clk_put(adc_clock);
                adc_clock = NULL;
        }
        kfree(adc_devp);
        misc_deregister(&misc);
}
module_init(adc_init);
module_exit(adc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jetsu.zju@gmail.com");
MODULE_DESCRIPTION("adc driver for my 10000 points");
cba5119 发表于 2012-9-5 16:50:26 | 显示全部楼层

回帖奖励 +10

可以赚金币   帮你顶一下:loveliness:
TQ-ZQL 发表于 2012-9-6 09:46:00 | 显示全部楼层
不是这样的吧,我的意思是让你在驱动中使用轮询的方法一次读多个(如1000)然后你再一次过将这批数据返回给应用 。你现在这种改法感觉与之前没有 什么不同。
for(i=0; i<1000; ++i)
{
    err = rd_adc_val(&val);
    if(err == 0)
         valbuf[i]=val;
   else
         ......
}
copy_to_user(buffer,valbuf,sizeof(valbuf));
 楼主| 月骅 发表于 2012-9-10 14:05:58 | 显示全部楼层
本帖最后由 月骅 于 2012-9-10 14:14 编辑
TQ-ZQL 发表于 2012-9-6 09:46
不是这样的吧,我的意思是让你在驱动中使用轮询的方法一次读多个(如1000)然后你再一次过将这批数据返回给 ...

static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
        unsigned long result=0;
        unsigned int data;         int valbuf[1000];                                       
        if (down_interruptible(&adc_dev.lock))
        return -ERESTARTSYS;
        iowrite32(START_ADC(adc_dev.channel,adc_dev.prescale),base_addr + S3C2410_ADCCON);
       for(int i=0;i<1000;++i)
        {

             do{
                result=ioread32(base_addr+S3C2410_ADCCON);
                }while((unsigned int)result&0x01);//ensure the start of conversion
            do{
                result=ioread32(base_addr+S3C2410_ADCCON);
                }while(!(((unsigned int)result)&0x8000));//till the end of conversion        
            data = ioread32(base_addr + S3C2410_ADCDAT0) & 0x3ff;                    
            valbuf=data;
        }
        copy_to_user(buffer,&valbuf,sizeof(valbuf));

        up(&adc_dev.lock);
        return sizeof(result);
}
是这个意思么?







 楼主| 月骅 发表于 2012-9-10 14:19:36 | 显示全部楼层
TQ-ZQL 发表于 2012-9-6 09:46
不是这样的吧,我的意思是让你在驱动中使用轮询的方法一次读多个(如1000)然后你再一次过将这批数据返回给 ...

static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
        unsigned long result=0;
        unsigned int data;       int valbuf[1000];
        if (down_interruptible(&adc_dev.lock))
        return -ERESTARTSYS;
        iowrite32(START_ADC(adc_dev.channel,adc_dev.prescale),base_addr + S3C2410_ADCCON);
        for(int i=0;i<1000;++i)
        {
        do{
                result=ioread32(base_addr+S3C2410_ADCCON);
        }while((unsigned int)result&0x01);//ensure the start of conversion
        do{
                result=ioread32(base_addr+S3C2410_ADCCON);
        }while(!(((unsigned int)result)&0x8000));//till the end of conversion        
        data = ioread32(base_addr + S3C2410_ADCDAT0) & 0x3ff;
        valbuf=data;
        }
        copy_to_user(buffer,&valbuf,sizeof(valbuf));

        up(&adc_dev.lock);
        return sizeof(result);
}
刚才那个编辑器抽风,怎么都改不过来,重写一遍,看这个,不看刚才那个
 楼主| 月骅 发表于 2012-9-10 14:21:20 | 显示全部楼层
TQ-ZQL 发表于 2012-9-6 09:46
不是这样的吧,我的意思是让你在驱动中使用轮询的方法一次读多个(如1000)然后你再一次过将这批数据返回给 ...

是valbuf=data;
编辑器怎么就自动省略了
 楼主| 月骅 发表于 2012-9-10 14:22:01 | 显示全部楼层
TQ-ZQL 发表于 2012-9-6 09:46
不是这样的吧,我的意思是让你在驱动中使用轮询的方法一次读多个(如1000)然后你再一次过将这批数据返回给 ...

打不出来么。。。
亚瑟王 发表于 2012-9-10 17:30:11 | 显示全部楼层
月骅 发表于 2012-9-10 14:22
打不出来么。。。

亲,你要在Linux下用1MHz的频率读取ADC的值,首先你要解决一件事情:让系统调度和其它任务尽量少的占用CPU的机时。这里就涉及到一件事情,如果你采集1个数据就上报给应用程序,系统时间是不够用。前面郑工也给你说了,建议你一次性采集你要的数量的点,然后集中上报给应用程序。这样在采集这些点的频率上可以达到你的要求。系统要求又不会那么高。
 楼主| 月骅 发表于 2012-9-10 20:02:09 | 显示全部楼层
亚瑟王 发表于 2012-9-10 17:30
亲,你要在Linux下用1MHz的频率读取ADC的值,首先你要解决一件事情:让系统调度和其它任务尽量少的占用CP ...

感谢亚瑟王。我明白那个意思。不知道如何改写,囧。应用程序read函数调用的是驱动的adc_read函数,adc_read里面定义了valbuf[1000],并使用for循环为这个数组赋值,valbuf=adc_data;这样貌似数组所有的元素都是同一个转换结果值,这样是不对的吧。
如何让valbuf[1000]的每个元素的值是不同时刻的转换结果值。求教。
 楼主| 月骅 发表于 2012-9-10 20:03:14 | 显示全部楼层
亚瑟王 发表于 2012-9-10 17:30
亲,你要在Linux下用1MHz的频率读取ADC的值,首先你要解决一件事情:让系统调度和其它任务尽量少的占用CP ...

论坛凡是有方括号中间有i的自动被省略啊,打字打不出来,汗
TQ-ZQL 发表于 2012-9-11 10:50:34 | 显示全部楼层
你可以在你进入for时打印一个系统时间,离开for时再打印一个系统时间,看它的们时间间隔时多少,如果只间隔很短的时间那取得同一值也可能是正常的,因为在很短时间内电压可能变化不大。
 楼主| 月骅 发表于 2012-9-16 11:03:19 | 显示全部楼层
TQ-ZQL 发表于 2012-9-11 10:50
你可以在你进入for时打印一个系统时间,离开for时再打印一个系统时间,看它的们时间间隔时多少,如果只间隔 ...

在for循环前面clock_t t;
t=clock();
for循环结束时DPRINTK("the total elapsed time is %d s\n",clock()-t);
看到了让我心碎的数字,一个1000点的for循环过了5秒钟!!!
郑哥,求助啊。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

i.MX8系列ARM cortex A53 M4 工控板上一条 /1 下一条

Archiver|手机版|小黑屋|天嵌 嵌入式开发社区 ( 粤ICP备11094220号 )

GMT+8, 2024-5-18 06:57 , Processed in 1.031250 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表