| 
轉(zhuǎn)自: http://blog.csdn.net/ielife/article/details/7798999 主要講述本人在學(xué)習(xí)Linux內(nèi)核input子系統(tǒng)的全部過程,如有分析不當(dāng),多謝指正。以下交流方式,文章歡迎轉(zhuǎn)載,保留聯(lián)系信息,以便交流。 
郵箱:eabi010@gmail.com 
主頁:www.(愛嵌論壇——嵌入式技術(shù)學(xué)習(xí)交流) 
博客:blog.csdn.net/ielife 
 
 1      mini2440的ADC驅(qū)動實例這節(jié)與輸入子系統(tǒng)無關(guān),出現(xiàn)在這里是因為后面的章節(jié)會講到觸摸屏輸入子系統(tǒng)驅(qū)動,由于觸摸屏也使用ADC,因此本節(jié)是為了說明ADC通過驅(qū)動代碼是如何控制的。 本節(jié)重點: 
 
如何通過原理圖查找ADC硬件使用的資源如何通過芯片手冊查找ADC硬件的操作方法ADC設(shè)備驅(qū)動程序的初始化流程ADC設(shè)備驅(qū)動程序的中斷處理流程 
 本節(jié)難點: 
 
ADC的控制寄存器的操作方法ADC驅(qū)動程序的控制邏輯 
 1.1    模數(shù)轉(zhuǎn)換(ADC)簡介ADC是把模擬信號轉(zhuǎn)化為計算機能夠處理的數(shù)字信號的過程。 模擬信號一般為電壓,或者是電流。有些時候也可以是非電信號,如溫度、濕度、聲音、位移等,它們通過傳感器轉(zhuǎn)換為電壓信號傳遞給A/D轉(zhuǎn)換器才可以進行A/D轉(zhuǎn)換。 1.2    mini2440上的可調(diào)電阻由mini2440的用戶手冊的1.3.8節(jié)A/D輸入測試可知,S3C2440的AIN0引腳接到了開發(fā)板的可調(diào)電阻W1上,原理圖如下圖3所示:  
 
 
 圖3  mini2440可調(diào)電阻原理圖   上圖中,1、2電路的狀態(tài)是能夠確定的,一個接3.3V電壓,一個接地,中間接可變電阻W1(10K)。而引腳3接AIN0,它是什么?可以通過mini2440開發(fā)板原理圖來查找:  
 
 
 圖4  mini2440可調(diào)電阻與S3C2440接口電路   通過上圖可知,開發(fā)板的AIN0引腳與S3C2440 CPU芯片上的AIN0引腳相連接。因此需要進一步查看S3C2440芯片手冊獲得AIN0引腳的作用。 下圖5是S3C2440芯片手冊的第16章對A/D轉(zhuǎn)換器和觸摸屏接口的介紹。S3C2440內(nèi)部共有8個通道的模擬輸入接口,其轉(zhuǎn)換的模擬信號為10位的二進制數(shù)字編碼。 A[3:0]分別代表AIN0、AIN1、AIN2、AIN3,觸摸屏接口可以控制/選擇觸摸屏X、Y方向的引腳(XP,XM,YP,YM)的變換。 
 
  
 圖5  A/D轉(zhuǎn)換器和觸摸屏的功能結(jié)構(gòu)圖   那么ADC如何實現(xiàn)模擬信號到數(shù)字信號的轉(zhuǎn)換呢,由上圖可知,模擬信號通過8個通道的任意一個輸入,然后通過分頻器決定A/D轉(zhuǎn)換器的頻率,最后通過ADC將模擬信號轉(zhuǎn)換為數(shù)字信號保存在ADCDAT0中,ADCDAT0中的數(shù)據(jù)可以通過查詢或者中斷的方式來獲得。 S3C2440模數(shù)轉(zhuǎn)換器的控制邏輯可由以下寄存器來進行操作: 
ADCCON       ADC控制寄存器 ADCTSC        ADC觸摸屏控制寄存器器 ADCDLY ADC啟動初始化延遲寄存器 ADCDAT0      ADC轉(zhuǎn)換數(shù)據(jù)寄存器 ADCDAT1      ADC轉(zhuǎn)換數(shù)據(jù)寄存器 ADCUPDN     筆尖抬起或落下中斷狀態(tài)寄存器 由以上內(nèi)容,開發(fā)板可以通過W1可變電阻的阻值變化產(chǎn)生電壓的變化,由AIN0引腳傳遞給ADC控制器轉(zhuǎn)化為數(shù)字信號,我們通過驅(qū)動來獲得可調(diào)電阻W1硬件的變化。 1.3    可調(diào)電阻的ADC驅(qū)動程序既然需要寫驅(qū)動,首先先確定可調(diào)電阻的ADC驅(qū)動屬于什么設(shè)備。由于是順序讀取寄存器ADCDAT0的過程,所以把它看成一個字符設(shè)備,而且對于這個設(shè)備來說,更簡單的實現(xiàn)方法是通過misc雜項設(shè)備來實現(xiàn)。 代碼實現(xiàn)的非常簡單,通過中斷的方式獲取ADCDAT0的前10位的值就可以了。代碼如下: /*  * mini2440 ADC驅(qū)動程序  *  * Kevin Lee <www.>  */     #include<linux/kernel.h> /* 提供prink等內(nèi)核特有屬性 */  #include<linux/module.h> /* 提供如MODULE_LICENSE()、EXPORT_SYMBOL() */  #include<linux/init.h> /* 設(shè)置段,如_init、_exit,設(shè)置初始化優(yōu)先級,如__initcall */  #include<linux/wait.h> /* 等待隊列wait_queue */  #include<linux/interrupt.h> /* 中斷方式,如IRQF_SHARED */  #include<linux/fs.h> /* file_operations操作接口等 */  #include<linux/clk.h> /* 時鐘控制接口,如struct clk */  #include<linux/miscdevice.h> /* 雜項設(shè)備 */  #include<asm/io.h> /* 提供readl、writel */  #include<asm/irq.h> /* 提供中斷號,中斷類型等,如IRQ_ADC中斷號 */  #include<asm/arch/regs-adc.h> /* 提供控制器的寄存器操作,如S3C2410_ADCCON */  #include<asm/uaccess.h> /* 提供copy_to_user等存儲接口 */     /* 定義設(shè)備名稱,用戶訪問接口/dev/adc */  #defineDEVICE_NAME "adc"     /* 定義adc時鐘,通過adc_clock接口獲得adc輸入時鐘,adc轉(zhuǎn)換器需要 */  staticstruct clk *adc_clock;     /* 定義虛擬地址訪問硬件寄存器,__iomem只是用于表示指針將指向I/O內(nèi)存 */  staticvoid __iomem *base_addr;     /* 定義并初始化一個等待隊列adc_waitqueue,對ADC資源進行阻塞訪問 */  staticwait_queue_head_t adc_waitqueue;     /* 定義并初始化信號量adc_lock,用于控制共享中斷IRQ_ADC資源的使用 */  DECLARE_MUTEX(adc_lock);  EXPORT_SYMBOL(adc_lock);     /* 定義等待隊列的條件,當(dāng)is_read_ok=1時,ADC轉(zhuǎn)換完畢,數(shù)據(jù)可讀 */  staticvolatile int is_read_ok = 0;     /* 定義ADC轉(zhuǎn)換的數(shù)據(jù)內(nèi)容 */  staticvolatile int adc_data;     staticint adc_open(struct inode *inode, struct file *file);  staticssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos);  staticint adc_close(struct inode *inode, struct file *filp);     /* 實現(xiàn)字符設(shè)備操作接口 */  staticstruct file_operations adc_fops =  {      .owner   = THIS_MODULE,      .open    = adc_open,      .read    = adc_read,         .release = adc_close,  };     /* 實現(xiàn)misc雜項設(shè)備操作接口 */  staticstruct miscdevice adc_miscdev =  {      .minor  = MISC_DYNAMIC_MINOR, /* 動態(tài)獲取雜項設(shè)備的次設(shè)備號 */      .name   = DEVICE_NAME,        /* 雜項設(shè)備的設(shè)備名稱,這里為adc */      .fops   = &adc_fops,          /* 雜項設(shè)備子系統(tǒng)接口,指向adc_fops操作接口 */  };     /*ADC中斷服務(wù)程序,獲取ADC轉(zhuǎn)換后的數(shù)據(jù) */  staticirqreturn_t adc_irq(int irq, void *dev_id)  {      /* 僅當(dāng)is_read_ok=0時才進行轉(zhuǎn)換,防止多次中斷 */      if(!is_read_ok)      {          /* 讀取ADCCON[9:0]的值,0x3ff為只獲取[9:0]位,ADCCON為轉(zhuǎn)換后的數(shù)據(jù) */          adc_data = readl(base_addr +S3C2410_ADCDAT0) & 0x3ff;             /* 設(shè)置標(biāo)識為1,喚醒讀等待進程可以拷貝數(shù)據(jù)給用戶空間了 */          is_read_ok = 1;         wake_up_interruptible(&adc_waitqueue);      }         return IRQ_RETVAL(IRQ_HANDLED);  }     /*ADC設(shè)備打開,并注冊IRQ_ADC中斷處理函數(shù) */  staticint adc_open(struct inode *inode, struct file *file)  {      int ret;         /* 由于IRQ_ADC為共享中斷,因此中斷類型選擇IRQF_SHARED,最后一個參數(shù)需要設(shè)置NULL以外的值 */      ret = request_irq(IRQ_ADC, adc_irq,IRQF_SHARED, DEVICE_NAME, (void *)1);      if (ret)      {          printk(KERN_ERR "Could notallocate ts IRQ_ADC !\n");          return -EBUSY;      }         return 0;  }     /*設(shè)置ADC控制寄存器,開啟AD轉(zhuǎn)換*/  staticvoid adc_run(void)  {      volatile unsigned int adccon;           /* ADCCON的位[14]=1為使能A/D預(yù)分頻器,位[13:6]=32表示設(shè)置的分頻值,ADC的轉(zhuǎn)換頻率需要在2.5MHZ以下      * 我們使用的ADC輸入時鐘為PCLK=50MHZ,50MHZ/32<2.5MHZ,滿足條件      * 位[5:3]=000,表示模擬輸入通道選擇AIN0      */      adccon = (1 << 14) | (32 << 6);      writel(adccon, base_addr + S3C2410_ADCCON);           /* 位[0]=1表示使能ADC轉(zhuǎn)換,當(dāng)轉(zhuǎn)換完畢后此位被ADC控制器自動清0 */      adccon = readl(base_addr + S3C2410_ADCCON)| (1 << 0);      writel(adccon, base_addr + S3C2410_ADCCON);  }     /*ADC設(shè)備驅(qū)動讀函數(shù) */  staticssize_t adc_read(struct file *filp, char *buff, size_t count, loff_t *offp)  {      int err;         /* 獲取信號量,如果被占用,睡眠等待持有者調(diào)用up喚醒      * 這樣做的原因是,有可能其他進程搶占執(zhí)行或是觸摸屏驅(qū)動搶占執(zhí)行      */      down_interruptible(&adc_lock);         /* 啟動adc轉(zhuǎn)換,調(diào)用中斷處理函數(shù)adc_irq*/      adc_run();         /* 如果is_read_ok為假,則睡眠等待條件為真,由中斷處理函數(shù)喚醒 */      wait_event_interruptible(adc_waitqueue,is_read_ok);         /* 執(zhí)行到此說明中斷處理程序獲得了ADC轉(zhuǎn)換后的值,清除為0等待下一次的讀 */      is_read_ok = 0;         /* 將轉(zhuǎn)換后的數(shù)據(jù)adc_data提交給用戶 */      err = copy_to_user(buff, (char*)&adc_data, min(sizeof(adc_data),count));         /* 釋放信號量,并喚醒因adc_lock而睡眠的進程 */      up(&adc_lock);         return err ? -EFAULT : sizeof(adc_data);  }     /*ADC設(shè)備關(guān)閉函數(shù) */  staticint adc_close(struct inode *inode, struct file *filp)  {      /*釋放中斷*/      free_irq(IRQ_ADC, (void *)1);         return 0;  }     staticint __init adc_init(void)  {      int ret;         /* 獲得adc的時鐘源,通過arch/arm/mach-s3c2410/clock.c獲得提供的時鐘源為PCLK */      adc_clock = clk_get(NULL, "adc");      if (!adc_clock)      {          printk(KERN_ERR "failed to get adcclock source\n");          return -ENOENT;      }         /* 在時鐘控制器中給adc提供輸入時鐘,ADC轉(zhuǎn)換需要輸入時鐘 */      clk_enable(adc_clock);         /* 使用ioremap獲得操作ADC控制器的虛擬地址      * S3C2410_PA_ADC=ADCCON,是ADC控制器的基地址,寄存器組的長度=0x1c      */      base_addr = ioremap(S3C2410_PA_ADC, 0x1c);      if (base_addr == NULL)      {          printk(KERN_ERR "Failed to remapregister block\n");          return -ENOMEM;          goto fail1;      }           /* 初始化等待隊列 */      init_waitqueue_head(&adc_waitqueue);         /* 注冊雜項設(shè)備 */      ret = misc_register(&adc_miscdev);      if (ret)      {          printk(KERN_ERR "Failed toregister miscdev\n");          goto fail2;      }         printk(DEVICE_NAME "initialized!\n");         return 0;       fail2:      iounmap(base_addr);  fail1:      clk_disable(adc_clock);      clk_put(adc_clock);         return ret;  }     staticvoid __exit adc_exit(void)  {      /* 釋放虛擬地址 */      iounmap(base_addr);         /* 禁止ADC的時鐘源 */      if (adc_clock)                  {          clk_disable(adc_clock);          clk_put(adc_clock);          adc_clock = NULL;      }         /*注銷misc設(shè)備*/      misc_deregister(&adc_miscdev);  }     module_init(adc_init);  module_exit(adc_exit);     MODULE_AUTHOR("KevinLee <www.>");  MODULE_DESCRIPTION("Mini2440ADC Misc Device Driver");  MODULE_VERSION("MINI2440ADC 1.0");  MODULE_LICENSE("GPL");  
 
 
 由于驅(qū)動程序不同于應(yīng)用程序main函數(shù),因此讀者觀看以上程序的順序應(yīng)該如下所示: 首先執(zhí)行的代碼是__init adc_init函數(shù),它會被insmod加載進內(nèi)核,當(dāng)然也可以在內(nèi)核初始化的時候加載,加載成功,應(yīng)用層訪問接口“/dev/adc”被創(chuàng)建; 其次,由于應(yīng)用層會首先打開“/dev/adc”設(shè)備,進而操作ADC設(shè)備,因此需要查看adc_open函數(shù)做了什么。由于打開設(shè)備意味著要使用設(shè)備,所以在adc_open中注冊IRQ_ADC中斷資源; 最后,用戶會調(diào)用read函數(shù)讀取ADC轉(zhuǎn)換的值,會調(diào)用到adc_read。因此,在adc_read函數(shù)中需要設(shè)置好AIN0引腳的模擬輸入,并啟動ADC,把讀取的任務(wù)交給adc_irq函數(shù)去完成,最后由adc_read函數(shù)把數(shù)據(jù)提交給應(yīng)用層。 如果使用insmod的方式加載,需要編寫Makefile函數(shù),如下: MODULENAME:= adc.o     ifneq($(KERNELRELEASE),)  #call from kernel build system  obj-m      := $(MODULENAME)     else  #KERNELDIR?= /lib/modules/$(shell uname -r)/build  KERNELDIR?= /work/system/linux-2.6.22.6  PWD       := $(shell pwd)  default:         $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  endif     clean:         rm -rf *.o *~ core .depend .*.cmd *.ko*.mod.c .tmp_versions module* Module* $(APPNAME)     depend.depend dep:         $(CC) $(CFLAGS) -M *.c > .depend     ifeq(.depend,$(wildcard .depend))  include.depend  endif  
 
 adc.c與Makefile文件放在同一目錄下,執(zhí)行make就可以了。Makefile中使用的編譯器的名稱為arm-linux-gcc,根據(jù)自己的情況修改即可。 編譯成功,在當(dāng)前目錄下得到adc.ko驅(qū)動模塊,使用命令modinfo  adc.ko,獲取信息如下: 
stu@stu-desktop:adc$modinfo adc.ko filename:       adc.ko license:        GPL version:        MINI2440 ADC 1.0 description:    Mini2440 ADC Misc Device Driver author:         Kevin Lee <www.> srcversion:     901D02B007F9D53D9C54EA3 depends:       built-in,built-in,built-in,built-in,built-in vermagic:       2.6.22.6mod_unload ARMv4 以上信息也是我們在adc.c代碼中添加的,還有的是在編譯過程中得到的。 把adc.ko文件放到開發(fā)板中,執(zhí)行insmod  adc.ko,看到如下信息則說明啟動正常: 并且可以查看/dev目錄下,已經(jīng)有adc設(shè)備文件 
# ls  -l  /dev/adc crw-rw----    1 0       0         10,  61 Jul 27 23:17 /dev/adc 1.4    可調(diào)電阻的測試程序編寫測試程序adc_test.c文件,源代碼如下: #include<stdio.h>  #include<stdlib.h>  #include<fcntl.h>  #include<unistd.h>  #include<sys/types.h>  #include<errno.h>     #defineDEVICE_NAME       "/dev/adc"     intmain()  {      int fd,ret,value;         fd = open(DEVICE_NAME, O_RDONLY);      if(fd < 0) {          perror("open ADC : ");          exit(EXIT_FAILURE);      }         ret = read(fd, &value, sizeof(value));      if(ret < 0) {              perror("read ADC:");              close(fd);              exit(EXIT_FAILURE);      }           printf("read from ADC : %d\n",value);      close(fd);         return 0;  }  
 
 
 源代碼簡單不做說明,編譯源代碼的命令: 
arm-linux-gcc  -Wall -O2  adc_test.c  -o adc_test arm-linux-strip  adc_test 拷貝adc_test文件到開發(fā)板,執(zhí)行命令./adc_test,顯示如下: 
#./adc_test  readfrom ADC : 736 調(diào)節(jié)(旋轉(zhuǎn))電位器即轉(zhuǎn)動變阻器,再次執(zhí)行./adc_test,顯示如下: 
#./adc_test  readfrom ADC : 886 讀到的數(shù)值隨電阻值的變化而變化,由此說明驅(qū)動及硬件工作正常。 
 |