天天看點

Linux-4.9.2核心在mini2440上的移植(十)——ADC驅動移植

本篇目的:移植ADC驅動,并測試。

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

說明:

1、本文源碼參考連結的源碼修改,在linux-4.9.2下移植驅動請務必使用本源碼

2、源碼為了友善粘貼,部分注釋已經去掉,需要注釋請檢視參考網址

10.1 添加ADC驅動源碼檔案

(1)添加頭檔案

[email protected]:~/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檔案

[email protected]:~/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

[email protected]:~/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

[email protected]:~/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驅動支援

[email protected]:~/linux-4.9.2# make menuconfig

Device Drivers --->

      [*] Misc devices  ---> 

Linux-4.9.2核心在mini2440上的移植(十)——ADC驅動移植

10.2 編譯、測試

(1)編譯源碼

[email protected]:~/linux-4.9.2# make

[email protected]:~/linux-4.9.2# ./mkuImage.sh

(2)重新開機測試

重新開機開發闆

(3)編寫測試檔案

[email protected]:~/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)編譯測試源碼

[email protected]:~/linux-4.9.2# cd ../NFS/rootfs/home/plg/

[email protected]:~/NFS/rootfs/home/plg# arm-linux-gcc -o adc_testadc_test.c

(5)測試源碼

打開secrueCRT,進入console

[[email protected] /]# cd /home/plg/

[[email protected] 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驅動成功。