LINUX的IIC驱动从这开始(三)

2019-07-13 01:27发布

转载地址:http://blog.csdn.net/xie0812/article/details/22984527
这一篇主要是在友善的Smart210开发板上写一个符合linux的iic驱动模型的设备驱动程序,这样能有一个更感性的认识。 开发环境介绍: 主机linux版本:fedora14 开发板:友善的Smart210 嵌入式linux版本:linux-3.0.8(友善光盘自带的) 交叉编译器:arm-linux-gcc-4.5.1(友善光盘自带的) 硬件简单介绍: 这是从友善的原理图上截下的图,这个图没有什么复杂的,从图可以看出来EEPROM是和s5pv210上的第0个iic适配器连接的,但我是用第1个适配器写的,所以用线直接把适配器1的引脚和EEPROM相连的,我这边正好有这个项目需要,所以这样写了,你可以直接用适配器0就行了,在写驱动的时候我会说怎么选iic适配器0和iic适配器1。 我们前面说过iic驱动模型是采用分层思想的,也即总线驱动和设备驱动是分开的。那它们怎么相互联系了?总得要一个什么东西来做个匹配吧,就像以前的地下工作者,需要接头暗号,要不然就乱套了,哈哈!iic总线和设备之间是用名字做匹配的,那好了,那就先得把设备的名字告诉总线吧,下面就是如何在总线上注册设备信息了。 注册设备信息 阅读linux下的Documentation/i2c/instantiating-devices 文档可以知道有两种方式可以注册,咱们只说前一种。打开:linux-3.0.8/arch/arm/mach-s5pv210/mach-mini210.c这个.c文件。就是在这个文件中填写咱们设备的信息的,这就是所说的bsp文件。首先添加头文件#include 因为linux专门问iic接口的eeprom提供了相应的数据结构,要是不加,肯定要报错。接下来添加如下信息: [cpp] view plain copy
  1. static struct at24_platform_data at24c08 = {  
  2. .byte_len = SZ_8K / 8,  //eeprom的容量大小(地址的总数)  
  3. .page_size = 16,        //eeprom的一页中包含的字节数  
[cpp] view plain copy
  1. };   
然后添加如下的信息,主要把eeprom的信息包装成符合iic模型中的设备信息的格式 [cpp] view plain copy
  1. static struct i2c_board_info i2c_devices[] __initdata = {  
  2.     {  
  1.     I2C_BOARD_INFO("at24c08b", 0x50),  //后边的0x50是eeprom的地址,可能有人说应该是0xa0,但linux中需要的是7bit的地址,所以向右移一位,正好是0x50。当然了,最终会在linux的iic的读函数和写函数中变成0xa0和0xa1的格式  
  2.     .platform_data = &at24c08,   
  3. },  
最后在mini210_machine_init函数中把上面写的信息注册到iic总线上 [cpp] view plain copy
  1. static void __init mini210_machine_init(void)  
  2. {  
  3.         ...  
  4.     s3c_i2c2_set_platdata(&i2c2_data);  
  5.     i2c_register_board_info(0, mini210_i2c_devs0,  
  6.             ARRAY_SIZE(mini210_i2c_devs0));  
  7.     //i2c_register_board_info(1, mini210_i2c_devs1,  
  8.             //ARRAY_SIZE(mini210_i2c_devs1));  //把友善原来带的屏蔽掉  
  9.         "color:#ff0000;">i2c_register_board_info(1, i2c_devices,            //仿照上面的添加如下的,主要这里分为0、1和2,你可以修改适配器0的,这样不需要连线  
  10.                         ARRAY_SIZE(i2c_devices));  
  11.     i2c_register_board_info(2, mini210_i2c_devs2,  
  12.             ARRAY_SIZE(mini210_i2c_devs2)); 
  1. }  
这就算把设备信息注册上了,重新编译一下你的linux内核吧,然后把编译好的内核烧进开发板,下面开始就是真真的驱动部分了。 设备驱动编写 首先咱们是用eeprom读写一些数据,数据量不会很大,所以它应该是个字符设备,尽管它从iic驱动模型的角度说,是iic设备,起始这并不矛盾。因为字符设备里包括了一部分的iic设备,下面就是整个驱动了 [cpp] view plain copy
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. #include  
  6. #include  
  7. #include  
  8. #include  
  9. #include  
  10. #include  
  11.   
  12. #define DEVICE_NAME  "at24c08"  
  13. //#define DEBUG  
  14.   
  15. struct At24c08_dev  
  16. {  
  17.   char name[30];  
  18.   struct i2c_client *at24c08_client;  
  19.   struct miscdevice  at24c08_miscdev;  //因为本身是一个字符设备,所以定义成一个杂项设备  
  20.   unsigned short current_pointer;  
  21. };  
  22.   
  23. struct At24c08_dev *At24c08_devp;  //定义一个全局的,因为结构体力里面的atc08_client需要从probe函数中获得  
[cpp] view plain copy
  1. //open函数主要是把全局的At24c08_devp赋给file文件的私有数据,这样在其他的函数中调用方便  
  2. static int at24c08_open(struct inode *inode,struct file *file){  
  3.     
  4.   file->private_data = At24c08_devp;  
  5.   return 0;  
  6. }  
[cpp] view plain copy
  1. //这就是杂项设备的read方法,跟普通的杂项设备的read方法没什么不一样的,只是调用的i2c_read_byte_data用来实际传输数据  
  2. static ssize_t  
  3. at24c08_read(struct file *file,char *buf,size_t count,loff_t *ppos)  
  4. {  
  5.   int i = 0;  
  6.   int transferred = 0;  
  7.   char value;  
  8.   char my_buff[50];  
  9.   
  10.   struct At24c08_dev *dev = (struct At24c08_dev *)file->private_data;  
  11.   
  12.   dev->current_pointer = 0;  
  13.   if(i2c_check_functionality(dev->at24c08_client->adapter,I2C_FUNC_SMBUS_READ_BYTE_DATA))  
  14.   {  
  15.     while(transferred < count)  
  16.     {  
  17.       msleep(10);    //这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的  
  18.       value = i2c_smbus_read_byte_data(dev->at24c08_client,dev->current_pointer +i);  
  19.       my_buff[i++] = value;  
  20.       transferred ++;  
  21.     }  
  22.     if(!copy_to_user(buf,(void *)my_buff,transferred))  
  23.       printk("The data copying from kernel to userspace success! ");  
  24.     else  
  25.       printk("Mybe some errors has occured ");  
  26.     dev->current_pointer +=transferred;  
  27.   }  
  28.   return transferred;  
  29. }  
[cpp] view plain copy
  1. //这就是注册的杂项设备的write方法  
  2. static ssize_t  
  3. at24c08_write(struct file *file,const char *buf,size_t count,loff_t *ppos)  
  4. {  
  5.   int i = 0;  
  6.   int transferred = 0;  
  7.   char  my_buff[50];  
  8.   
  9.   struct At24c08_dev *dev = (struct At24c08_dev *)file->private_data;  
  10.   dev->current_pointer = 0;  
  11.   if(i2c_check_functionality(dev->at24c08_client->adapter,I2C_FUNC_SMBUS_BYTE_DATA))  
  12.   {  
  13.     if(!copy_from_user(my_buff,buf,count))  
  14.     {  
  15.       printk("The data copying from userspace to kernel success! ");  
  16.       while(transferred < count)  
  17.       {  
  18.          msleep(10);//与上面的read函数中的类似  
  19.          i2c_smbus_write_byte_data(dev->at24c08_client,dev->current_pointer + i,my_buff[i]); //这个函数通过adapter的通信方法把一个字节的数据发送          //到iic设备中去  
  20.          i ++;  
  21.          transferred ++;  
  22.       }  
  23.       dev->current_pointer +=transferred;  
  24.     }  
  25.     else  
  26.       printk("Mybe some errors has occured ");  
  27.   }  
  28.   return transferred;  
  29. }  
  30.   
  31. static const struct file_operations at24c08_fops ={  
  32.   .owner = THIS_MODULE,  
  33.   .open = at24c08_open,  
  34.   .read = at24c08_read,  
  35.   .write = at24c08_write,  
  36. };  
[cpp] view plain copy
  1. //当把设备挂接到总线上时,只有当at24c08b_id所起的名字和之前注册到总线当中的名字一样时,才会调用probe函数。在probe函数里会分配i2c_client,通过这个//i2c_client,当调用注册的字符设备时,iic适配器就知道把数据跟那个iic设备交互。  
  2. static int __devinit at24c08b_probe(struct i2c_client *client,const struct i2c_device_id *id)  
  3. {  
  4.   int ret;  
  5.     
  6.   #ifdef DEBUG  
  7.     printk("The routine of probe has started(for binding device) ");  
  8.   #endif  
  9.     
  10.   At24c08_devp = kmalloc(sizeof(struct At24c08_dev),GFP_KERNEL);  
  11.   if(!At24c08_devp)  
  12.   {  
  13.     return ret = -ENOMEM;  
  14.   }  
  15.   memset(At24c08_devp,0,sizeof(struct At24c08_dev));  
  16.   
  17.   At24c08_devp->at24c08_client = client; //把分配的i2c_client赋给定义的全局变量  
  18.   
  19.   At24c08_devp->at24c08_miscdev.minor = MISC_DYNAMIC_MINOR;   
  20.   At24c08_devp->at24c08_miscdev.name = DEVICE_NAME;  
  21.   At24c08_devp->at24c08_miscdev.fops = &at24c08_fops; //把杂项设备的一些域用我们具体的方法定义  
  22.     
  23.   ret = misc_register(&At24c08_devp->at24c08_miscdev);  //注册杂项设备  
  24.   #ifdef DEBUG  
  25.     printk("The driver of at24c08 has registered! ");  
  26.   #endif  
  27.   return ret;  
  28. }  
  29.   
  30. static int __devexit at24c08b_remove(struct i2c_client *client)  
  31. {  
  32.   misc_deregister(&At24c08_devp->at24c08_miscdev);  
  33.   #ifdef DEBUG  
  34.     printk("The routine of remove has implemented! ");  
  35.   #endif  
  36.   return 0;  
  37. }  
  38. static const struct i2c_device_id at24c08b_id[]={  
  39.   {"at24c08",0},  
  40.   {}  
  41. };   //当把设备挂接到总线上时,就调用这里面的名字和注册在总线里的名字比对,如果一样就会调用probe函数,同时给挂接的设备分配i2c_client结构体  
  42.   
  43. MODULE_DEVICE_TABLE(i2c,at24c08b_id);  
  44.   
  45. static struct i2c_driver at24c08b_driver = {  
  46.   
  47.   .driver = {  
  48.      .name = "at24c08",  
  49.      .owner=THIS_MODULE,  
  50.   },  
  51.   .probe = at24c08b_probe,  
  52.   .remove=__devexit_p(at24c08b_remove),  
  53.   .id_table =at24c08b_id,  
  54. };  
  55.   
  56. static int __init at24c08b_init(void)  
  57. {  
  58.   #ifdef DEBUG  
  59.     printk(KERN_NOTICE"The driver of at24c08 is insmod! ");  
  60.   #endif  
  61.   return i2c_add_driver(&at24c08b_driver);  //把iic设备挂接到总线上  
  62. }  
  63.   
  64. void at24c08b_exit(void)  
  65. {  
  66.   #ifdef DEBUG  
  67.     printk(KERN_NOTICE"at24c0b is rmmod! ");  
  68.   #endif  
  69.   i2c_del_driver(&at24c08b_driver); //把iic设备移除,这时会调用remove函数,所以在remove函数中一般会干一些注销设备的工作等  
  70. }  
  71.   
  72.   
  73. MODULE_DESCRIPTION("at24c08b eeprom driver");  
  74. MODULE_LICENSE("Dual BSD/GPL");  
  75. MODULE_AUTHOR("xie yingdong");  
  76.   
  77. module_init(at24c08b_init);  
  78. module_exit(at24c08b_exit);  
上面就是完整的eeprom驱动,当然驱动写完了,需要写个简单的Makefile来编译这个驱动,好吧,下面就是Makefile文件的内容 [cpp] view plain copy
  1. obj-m:=eeprom-driver.o  
  2. KDIR = /tmp/linux-3.0.8  //这里需要你根据自己的实际的linux源码放的位置来设置  
  3.   
  4. all:  
  5.     $(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd) modules ARCH=arm CROSS_COMPILE=arm-linux-  
  6. clean:  
  7.     @rm -rf eeprom-driver*.o  
上面的Makefile文件很是简单,就不做过多的解释了。当把驱动编译好了,用动态的方式挂载到了linux内核上后,你还得做个简单的测试程序,来验证咱们写的驱动工作是否正常,下面就直接贴出来吧。 [cpp] view plain copy
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. #include  
  6. #include  
  7.   
  8. int main(void)  
  9. {  
  10.   int i;  
  11.   char value[19] = "eeprom-driver test!";  
  12.   char backvalue[19];  
  13.   
  14.   int fd;  
  15.   fd = open("/dev/at24c08",O_RDWR);  //这里的名字一定要和驱动里注册的杂项设备的名字一样,但跟iic设备的名字无关,这里只是正好取的一样而已  
  16.   if(fd<0){  
  17.     printf("Open at24c08 device failed! ");  
  18.     exit(1);  
  19.   }  
  20.   write(fd,value,19);  
  21.   printf("The string writing to eeprom : %s ",value);  
  22.   printf("################################################## ");  
  23.   sleep(1);  
  24.   read(fd,backvalue,19);  
  25.   printf("The string reading from eeprom : %s ",backvalue);  
  26.   close(fd);  
  27.   return 0;}  
哈哈,驱动就写完了,我自己测试了,没问题,你可以试试,下一篇我们会分析iic总线驱动。