這一篇主要是在友善的Smart210開發(fā)板上寫一個(gè)符合linux的iic驅(qū)動(dòng)模型的設(shè)備驅(qū)動(dòng)程序,這樣能有一個(gè)更感性的認(rèn)識(shí),。
開發(fā)環(huán)境介紹:
主機(jī)linux版本:fedora14
開發(fā)板:友善的Smart210
嵌入式linux版本:linux-3.0.8(友善光盤自帶的)
交叉編譯器:arm-linux-gcc-4.5.1(友善光盤自帶的)
硬件簡單介紹:
,、
這是從友善的原理圖上截下的圖,這個(gè)圖沒有什么復(fù)雜的,,從圖可以看出來EEPROM是和s5pv210上的第0個(gè)iic適配器連接的,,但我是用第1個(gè)適配器寫的,所以用線直接把適配器1的引腳和EEPROM相連的,,我這邊正好有這個(gè)項(xiàng)目需要,,所以這樣寫了,你可以直接用適配器0就行了,,在寫驅(qū)動(dòng)的時(shí)候我會(huì)說怎么選iic適配器0和iic適配器1,。
我們前面說過iic驅(qū)動(dòng)模型是采用分層思想的,也即總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)是分開的,。那它們?cè)趺聪嗷ヂ?lián)系了,?總得要一個(gè)什么東西來做個(gè)匹配吧,就像以前的地下工作者,,需要接頭暗號(hào),,要不然就亂套了,哈哈,!iic總線和設(shè)備之間是用名字做匹配的,,那好了,那就先得把設(shè)備的名字告訴總線吧,,下面就是如何在總線上注冊(cè)設(shè)備信息了,。
注冊(cè)設(shè)備信息
閱讀linux下的Documentation/i2c/instantiating-devices 文檔可以知道有兩種方式可以注冊(cè),,咱們只說前一種。打開:linux-3.0.8/arch/arm/mach-s5pv210/mach-mini210.c這個(gè).c文件,。就是在這個(gè)文件中填寫咱們?cè)O(shè)備的信息的,,這就是所說的bsp文件。首先添加頭文件#include
<linux/i2c/at24.h> 因?yàn)閘inux專門問iic接口的eeprom提供了相應(yīng)的數(shù)據(jù)結(jié)構(gòu),,要是不加,,肯定要報(bào)錯(cuò)。接下來添加如下信息:
- static struct at24_platform_data at24c08 = {
- .byte_len = SZ_8K / 8, //eeprom的容量大?。ǖ刂返目倲?shù))
- .page_size = 16, //eeprom的一頁中包含的字節(jié)數(shù)
然后添加如下的信息,,主要把eeprom的信息包裝成符合iic模型中的設(shè)備信息的格式
- static struct i2c_board_info i2c_devices[] __initdata = {
- {
- I2C_BOARD_INFO("at24c08b", 0x50), //后邊的0x50是eeprom的地址,可能有人說應(yīng)該是0xa0,,但linux中需要的是7bit的地址,,所以向右移一位,正好是0x50,。當(dāng)然了,,最終會(huì)在linux的iic的讀函數(shù)和寫函數(shù)中變成0xa0和0xa1的格式
- .platform_data = &at24c08,
- },
最后在mini210_machine_init函數(shù)中把上面寫的信息注冊(cè)到iic總線上
- static void __init mini210_machine_init(void)
- {
- ...
- s3c_i2c2_set_platdata(&i2c2_data);
- i2c_register_board_info(0, mini210_i2c_devs0,
- ARRAY_SIZE(mini210_i2c_devs0));
- //i2c_register_board_info(1, mini210_i2c_devs1,
- //ARRAY_SIZE(mini210_i2c_devs1)); //把友善原來帶的屏蔽掉
- <span style="color:#ff0000;">i2c_register_board_info(1, i2c_devices, //仿照上面的添加如下的,主要這里分為0,、1和2,,你可以修改適配器0的,這樣不需要連線
- ARRAY_SIZE(i2c_devices));</span>
- i2c_register_board_info(2, mini210_i2c_devs2,
- ARRAY_SIZE(mini210_i2c_devs2));
這就算把設(shè)備信息注冊(cè)上了,,重新編譯一下你的linux內(nèi)核吧,,然后把編譯好的內(nèi)核燒進(jìn)開發(fā)板,下面開始就是真真的驅(qū)動(dòng)部分了,。
設(shè)備驅(qū)動(dòng)編寫
首先咱們是用eeprom讀寫一些數(shù)據(jù),,數(shù)據(jù)量不會(huì)很大,,所以它應(yīng)該是個(gè)字符設(shè)備,,盡管它從iic驅(qū)動(dòng)模型的角度說,是iic設(shè)備,,起始這并不矛盾,。因?yàn)樽址O(shè)備里包括了一部分的iic設(shè)備,,下面就是整個(gè)驅(qū)動(dòng)了
- #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/kernel.h>
- #include<linux/fs.h>
- #include<asm/uaccess.h>
- #include<linux/i2c.h>
- #include<linux/miscdevice.h>
- #include<linux/slab.h>
- #include<linux/list.h>
- #include<linux/delay.h>
-
- #define DEVICE_NAME "at24c08"
- //#define DEBUG
-
- struct At24c08_dev
- {
- char name[30];
- struct i2c_client *at24c08_client;
- struct miscdevice at24c08_miscdev; //因?yàn)楸旧硎且粋€(gè)字符設(shè)備,所以定義成一個(gè)雜項(xiàng)設(shè)備
- unsigned short current_pointer;
- };
-
- struct At24c08_dev *At24c08_devp; //定義一個(gè)全局的,,因?yàn)榻Y(jié)構(gòu)體力里面的atc08_client需要從probe函數(shù)中獲得
- //open函數(shù)主要是把全局的At24c08_devp賦給file文件的私有數(shù)據(jù),,這樣在其他的函數(shù)中調(diào)用方便
- static int at24c08_open(struct inode *inode,struct file *file){
-
- file->private_data = At24c08_devp;
- return 0;
- }
- //這就是雜項(xiàng)設(shè)備的read方法,跟普通的雜項(xiàng)設(shè)備的read方法沒什么不一樣的,,只是調(diào)用的i2c_read_byte_data用來實(shí)際傳輸數(shù)據(jù)
- static ssize_t
- at24c08_read(struct file *file,char *buf,size_t count,loff_t *ppos)
- {
- int i = 0;
- int transferred = 0;
- char value;
- char my_buff[50];
-
- struct At24c08_dev *dev = (struct At24c08_dev *)file->private_data;
-
- dev->current_pointer = 0;
- if(i2c_check_functionality(dev->at24c08_client->adapter,I2C_FUNC_SMBUS_READ_BYTE_DATA))
- {
- while(transferred < count)
- {
- msleep(10); //這里一定注意,,要不這個(gè)延時(shí)加上,因?yàn)閏pu速度比較快,,eeprom速度比較慢,,所以不加會(huì)出問題,,我調(diào)試時(shí)就出問題了,,后加的
- value = i2c_smbus_read_byte_data(dev->at24c08_client,dev->current_pointer +i);
- my_buff[i++] = value;
- transferred ++;
- }
- if(!copy_to_user(buf,(void *)my_buff,transferred))
- printk("The data copying from kernel to userspace success!\n");
- else
- printk("Mybe some errors has occured\n");
- dev->current_pointer +=transferred;
- }
- return transferred;
- }
- //這就是注冊(cè)的雜項(xiàng)設(shè)備的write方法
- static ssize_t
- at24c08_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
- {
- int i = 0;
- int transferred = 0;
- char my_buff[50];
-
- struct At24c08_dev *dev = (struct At24c08_dev *)file->private_data;
- dev->current_pointer = 0;
- if(i2c_check_functionality(dev->at24c08_client->adapter,I2C_FUNC_SMBUS_BYTE_DATA))
- {
- if(!copy_from_user(my_buff,buf,count))
- {
- printk("The data copying from userspace to kernel success!\n");
- while(transferred < count)
- {
- msleep(10);//與上面的read函數(shù)中的類似
- i2c_smbus_write_byte_data(dev->at24c08_client,dev->current_pointer + i,my_buff[i]); //這個(gè)函數(shù)通過adapter的通信方法把一個(gè)字節(jié)的數(shù)據(jù)發(fā)送 //到iic設(shè)備中去
- i ++;
- transferred ++;
- }
- dev->current_pointer +=transferred;
- }
- else
- printk("Mybe some errors has occured\n");
- }
- return transferred;
- }
-
- static const struct file_operations at24c08_fops ={
- .owner = THIS_MODULE,
- .open = at24c08_open,
- .read = at24c08_read,
- .write = at24c08_write,
- };
- //當(dāng)把設(shè)備掛接到總線上時(shí),,只有當(dāng)at24c08b_id所起的名字和之前注冊(cè)到總線當(dāng)中的名字一樣時(shí),才會(huì)調(diào)用probe函數(shù),。在probe函數(shù)里會(huì)分配i2c_client,,通過這個(gè)//i2c_client,當(dāng)調(diào)用注冊(cè)的字符設(shè)備時(shí),,iic適配器就知道把數(shù)據(jù)跟那個(gè)iic設(shè)備交互,。
- static int __devinit at24c08b_probe(struct i2c_client *client,const struct i2c_device_id *id)
- {
- int ret;
-
- #ifdef DEBUG
- printk("The routine of probe has started(for binding device)\n");
- #endif
-
- At24c08_devp = kmalloc(sizeof(struct At24c08_dev),GFP_KERNEL);
- if(!At24c08_devp)
- {
- return ret = -ENOMEM;
- }
- memset(At24c08_devp,0,sizeof(struct At24c08_dev));
-
- At24c08_devp->at24c08_client = client; //把分配的i2c_client賦給定義的全局變量
-
- At24c08_devp->at24c08_miscdev.minor = MISC_DYNAMIC_MINOR;
- At24c08_devp->at24c08_miscdev.name = DEVICE_NAME;
- At24c08_devp->at24c08_miscdev.fops = &at24c08_fops; //把雜項(xiàng)設(shè)備的一些域用我們具體的方法定義
-
- ret = misc_register(&At24c08_devp->at24c08_miscdev); //注冊(cè)雜項(xiàng)設(shè)備
- #ifdef DEBUG
- printk("The driver of at24c08 has registered!\n");
- #endif
- return ret;
- }
-
- static int __devexit at24c08b_remove(struct i2c_client *client)
- {
- misc_deregister(&At24c08_devp->at24c08_miscdev);
- #ifdef DEBUG
- printk("The routine of remove has implemented!\n");
- #endif
- return 0;
- }
- static const struct i2c_device_id at24c08b_id[]={
- {"at24c08",0},
- {}
- }; //當(dāng)把設(shè)備掛接到總線上時(shí),就調(diào)用這里面的名字和注冊(cè)在總線里的名字比對(duì),,如果一樣就會(huì)調(diào)用probe函數(shù),,同時(shí)給掛接的設(shè)備分配i2c_client結(jié)構(gòu)體
-
- MODULE_DEVICE_TABLE(i2c,at24c08b_id);
-
- static struct i2c_driver at24c08b_driver = {
-
- .driver = {
- .name = "at24c08",
- .owner=THIS_MODULE,
- },
- .probe = at24c08b_probe,
- .remove=__devexit_p(at24c08b_remove),
- .id_table =at24c08b_id,
- };
-
- static int __init at24c08b_init(void)
- {
- #ifdef DEBUG
- printk(KERN_NOTICE"The driver of at24c08 is insmod!\n");
- #endif
- return i2c_add_driver(&at24c08b_driver); //把iic設(shè)備掛接到總線上
- }
-
- void at24c08b_exit(void)
- {
- #ifdef DEBUG
- printk(KERN_NOTICE"at24c0b is rmmod!\n");
- #endif
- i2c_del_driver(&at24c08b_driver); //把iic設(shè)備移除,這時(shí)會(huì)調(diào)用remove函數(shù),,所以在remove函數(shù)中一般會(huì)干一些注銷設(shè)備的工作等
- }
-
-
- MODULE_DESCRIPTION("at24c08b eeprom driver");
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("xie yingdong");
-
- module_init(at24c08b_init);
- module_exit(at24c08b_exit);
上面就是完整的eeprom驅(qū)動(dòng),,當(dāng)然驅(qū)動(dòng)寫完了,需要寫個(gè)簡單的Makefile來編譯這個(gè)驅(qū)動(dòng),,好吧,,下面就是Makefile文件的內(nèi)容
- obj-m:=eeprom-driver.o
- KDIR = /tmp/linux-3.0.8 //這里需要你根據(jù)自己的實(shí)際的linux源碼放的位置來設(shè)置
-
- all:
- $(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd) modules ARCH=arm CROSS_COMPILE=arm-linux-
- clean:
- @rm -rf eeprom-driver*.o
上面的Makefile文件很是簡單,就不做過多的解釋了,。當(dāng)把驅(qū)動(dòng)編譯好了,,用動(dòng)態(tài)的方式掛載到了linux內(nèi)核上后,你還得做個(gè)簡單的測(cè)試程序,,來驗(yàn)證咱們寫的驅(qū)動(dòng)工作是否正常,,下面就直接貼出來吧。
- #include<stdio.h>
- #include<linux/types.h>
- #include<stdlib.h>
- #include<fcntl.h>
- #include<unistd.h>
- #include<sys/types.h>
-
- int main(void)
- {
- int i;
- char value[19] = "eeprom-driver test!";
- char backvalue[19];
-
- int fd;
- fd = open("/dev/at24c08",O_RDWR); //這里的名字一定要和驅(qū)動(dòng)里注冊(cè)的雜項(xiàng)設(shè)備的名字一樣,,但跟iic設(shè)備的名字無關(guān),,這里只是正好取的一樣而已
- if(fd<0){
- printf("Open at24c08 device failed!\n");
- exit(1);
- }
- write(fd,value,19);
- printf("The string writing to eeprom : %s\n",value);
- printf("##################################################\n");
- sleep(1);
- read(fd,backvalue,19);
- printf("The string reading from eeprom : %s\n",backvalue);
- close(fd);
- return 0;}
哈哈,驅(qū)動(dòng)就寫完了,,我自己測(cè)試了,,沒問題,你可以試試,,下一篇我們會(huì)分析iic總線驅(qū)動(dòng),。
|