s3c2410的A/D驱动

作者:陈刚,华清远见嵌入式学院讲师。

我们要写s3c2410的A/D驱动需要了解用户2410的A/D。我们来看看2410用户手册

The 10-bit CMOS analog to digital converter (ADC) of the S3C2410A is a recycling typed device with 8-channel analog inputs.

2410提供的是10位精度8通道的A/D。

用户手册给出公式:

A/D converter freq. = 50 MHz/(49+1) = 1 MHz
        Conversion time = 1/(1 MHz / 5cycles) = 1/200 kHz = 5 us

并且提示maximum 2.5 MHz clock

This A/D converter is designed to operate at maximum 2.5 MHz clock, so the conversion rate can go up to 500 KSPS.

所以我们为A/D提供的分频值最小为17;

我们主要关心两个寄存器

ADCCON (ADC控制寄存器) 和ADCDAT1(A/D转换后,数据存放在此寄存器)

我们只关心ADCDAT1(获得A/D转换后的数据(数据为10位精度)) 的低10位 。

下面我们在FS2410上实现一个linux下得A/D驱动示例:

首先我们看外围芯片电路图:

选择A/D的AIN0通道.

示例代码如下:

#include <linux/module.h>
        #include <linux/kernel.h>
        #include <linux/init.h>
        #include <linux/fs.h>
        #include <linux/cdev.h>
        #include <linux/device.h>
        #include <linux/io.h>
        #include <linux/irq.h>
        #include <linux/interrupt.h>
        #include <asm/uaccess.h>
        #include <linux/clk.h>
        #include <linux/wait.h>
        #include <linux/delay.h>
        #include <linux/sched.h>

#define DEVICE_NAME "adc_driver"

#define pADCCON 0x58000000
        #define pADCDAT0 0x5800000C

MODULE_LICENSE ("GPL");

unsigned long *vADCCON; /*ADC控制寄存器地址*/
        unsigned long *vADCDAT0;/*ADCDAT0寄存器地址*/

int major = 250;
        int minor = 0;
        struct class *my_class;
        wait_queue_head_t my_queue;

int sleep_flag = 0;
        int number_of_devices = 1;

struct cdev cdev;
        dev_t devno = 0;

/* open 打开设备 */
        static int adc_driver_open(struct inode *inode, struct file *file)
        {
                struct clk *adc_clock = clk_get(NULL, "adc"); /*获得adc时钟*/
                if (adc_clock == NULL) {
                        printk(KERN_WARNING "clk_get failed");
                        return -ENOENT;
                }
                clk_enable(adc_clock); /*时能ADC时钟,打开ADC*/

        writel((1<<14)|(49<<6)|(0x00),vADCCON);
                printk (KERN_INFO "adc_driver is open");

        return 0;
        }

static int adc_driver_release(struct inode *inode, struct file *file)
        {
                struct clk *adc_clock = clk_get(NULL, "adc");
                clk_disable(adc_clock);

        return 0;
        }
        /*中断处理函数(ADC 可以使用中断方式和轮询方式获得转换好的数据)*/
        irqreturn_t interrupt_adc(int irq, void *dev_id)
        {
                sleep_flag = 1;
                wake_up_interruptible(&my_queue);

        return IRQ_HANDLED;
        }
        /*阻塞读*/
        static ssize_t adc_driver_read(struct file *filp, char __user *buf,
        size_t count, loff_t *ppos)
        {
                unsigned int num, ret;

        writel((readl(vADCCON) & ~(0x3)) | (0x1),vADCCON);
                wait_event_interruptible(my_queue, (sleep_flag == 1));
                sleep_flag = 0;

        num = readl(vADCDAT0) & (0x3FF);
                ret = copy_to_user(buf, &num, sizeof(unsigned int));
                if (ret < 0)
                        printk (KERN_WARNING "copy_to_user failed");
                return ret;
        }

struct file_operations adc_fops = {
                .owner = THIS_MODULE,
                .open = adc_driver_open,
                .release = adc_driver_release,
                .read = adc_driver_read,
        };

static void char_reg_setup_cdev (void)
        {
                int error;
                cdev_init (&cdev, &adc_fops);
                cdev.owner = THIS_MODULE;
                cdev.ops = &adc_fops;
                error = cdev_add (&cdev, devno , 1);
                if (error)
                        printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev\n", error);
         }

static int __init adc_driver_init (void)
        {
                int result;

        if (major) {
                        devno = MKDEV(major,0);
                        result = register_chrdev_region(devno,number_of_devices,DEVICE_NAME);
                }
                else
                        result = alloc_chrdev_region(&devno, 0, number_of_devices, DEVICE_NAME);
                        if (result < 0) {
                        printk(KERN_WARNING "adc_driver: can't get major number %d\n", major);
                        return result;
                }

        vADCCON = ioremap(pADCCON, 4);
                if (vADCCON == NULL) {
                        printk("ioremap vADCCON failed");
                        return -1;
                }
                vADCDAT0 = ioremap(pADCDAT0, 4);
                if (vADCDAT0 == NULL) {
                        printk("ioremap vADCDAT0 failed");
                        goto err0;
                }

        init_waitqueue_head(&my_queue);

        result = request_irq(IRQ_ADC, interrupt_adc, IRQF_DISABLED, "adc", NULL);
                if (result < 0) {
                        printk (KERN_WARNING "request irq interrupt_adc failed\n");
                }

                my_class = class_create( THIS_MODULE, "adc_class" );
                if(IS_ERR(my_class)) {
                        printk("Err: failed to create class.\n");
                        return -1;
                }
                device_create( my_class, NULL, devno, NULL, "adc_device");

        char_reg_setup_cdev ();

        printk (KERN_INFO "char device registered\n");
                return 0;

err0:
                iounmap(vADCDAT0);
                return -1;

}

static void __exit adc_driver_exit (void)
        {
                iounmap(vADCCON);
                iounmap(vADCDAT0);

        free_irq(IRQ_ADC, NULL);

        device_destroy(my_class, devno);
                class_destroy(my_class);

        cdev_del (&cdev);

        unregister_chrdev_region (devno, number_of_devices);
                printk (KERN_INFO "adc_driver is clean up\n");

}

module_init (adc_driver_init);
        module_exit (adc_driver_exit);