I2C总线(五)I2C子系统(3)-— EEPROM实例

2019-07-13 02:18发布

这一篇主要是在友善的Smart210开发板上写一个符合linux的iic驱动模型的设备驱动程序,这样能有一个更感性的认识。
开发环境介绍:
主机linux版本:Ubuntu18.04
开发板:三星的Louis210
嵌入式linux版本:linux-3.10.46
交叉编译器:arm-linux-gcc-4.3.3
硬件简单介绍: 从图可以看出来EEPROM是和Louis210上的第0个iic适配器连接的。 注册设备信息
阅读linux下的Documentation/i2c/instantiating-devices 文档可以知道有两种方式可以注册,咱们只说前一种。打开:linux-3.10.46/arch/arm/mach-Louis210/mach-Louis210.c这个.c文件。就是在这个文件中填写咱们设备的信息的,这就是所说的bsp文件。首先添加头文件#include 因为linux专门问iic接口的eeprom提供了相应的数据结构,要是不加,肯定要报错。接下来添加如下信息: static struct at24_platform_data at24c08 = { .byte_len = SZ_2K / 8, //eeprom的容量大小(地址的总数) .page_size = 8, //eeprom的一页中包含的字节数 }; 然后添加如下的信息,主要把eeprom的信息包装成符合iic模型中的设备信息的格式 static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = { { I2C_BOARD_INFO("24c02", 0x50), *platform_data = at24c08, }, /* add by Louis */ }; 后边的0x50是eeprom的地址,可能有人说应该是0xa0,但linux中需要的是7bit的地址,所以向右移一位, 正好是0x50。当然了,最终会在linux的iic的读函数和写函数中变成0xa0和0xa1的格式 最后在Louis210_machine_init函数中把上面写的信息注册到iic总线上 static void __init Louis210_machine_init(void) { ... s3c_i2c0_set_platdata(NULL); s3c_i2c1_set_platdata(NULL); s3c_i2c2_set_platdata(NULL); i2c_register_board_info(0, smdkv210_i2c_devs0, ARRAY_SIZE(smdkv210_i2c_devs0)); i2c_register_board_info(1, smdkv210_i2c_devs1, ARRAY_SIZE(smdkv210_i2c_devs1)); i2c_register_board_info(2, smdkv210_i2c_devs2, ARRAY_SIZE(smdkv210_i2c_devs2)); ... } 这就算把设备信息注册上了,重新编译一下你的linux内核吧,然后把编译好的内核烧进开发板,下面开始就是真真的驱动部分了。 设备驱动编写
首先咱们是用eeprom读写一些数据,数据量不会很大,所以它应该是个字符设备,尽管它从iic驱动模型的角度说,是iic设备,起始这并不矛盾。因为字符设备里包括了一部分的iic设备,下面就是整个驱动了 /* * at24.c - handle most I2C EEPROMs * * Copyright (C) 2005-2007 David Brownell * Copyright (C) 2008 Wolfram Sang, Pengutronix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "24c02" //#define DEBUG #define MEMDEV_SIZE (2048 / 8) #define AT24_SIZE_BYTELEN 5 #define AT24_SIZE_FLAGS 8 struct At24c02_dev { char name[30]; unsigned write_max; struct mutex lock; //互斥锁 struct i2c_client *at24c02_client; struct miscdevice at24c02_miscdev; //因为本身是一个字符设备,所以定义成一个杂项设备 }; struct At24c02_dev *At24c02_devp; /*open函数将全局的At24c02_devp赋给file文件的私有数据,方便其他函数调用*/ static int at24c02_open(struct inode *inode, struct file *filp) { filp->private_data = At24c02_devp; return 0; } static ssize_t at24_eeprom_read(struct file *filp, char *buf, loff_t offset, size_t count) { int transferred = 0; struct At24c02_dev *dev = (struct At24c02_dev *)filp->private_data; if (!i2c_check_functionality(dev->at24c02_client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) return -ENODEV; while(transferred < count) { msleep(10); //这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的 buf[transferred++] = i2c_smbus_read_byte_data(dev->at24c02_client,offset + transferred); if(transferred >= dev->write_max) break; } return transferred; } static ssize_t at24_eeprom_write(struct file *filp, const char *buf, loff_t offset, size_t count) { int transferred = 0; struct At24c02_dev *dev = (struct At24c02_dev *)filp->private_data; if (!i2c_check_functionality(dev->at24c02_client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; while(transferred < count) { msleep(10); //这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的 i2c_smbus_write_byte_data(dev->at24c02_client,offset + transferred, buf[transferred]); //这个函数通过adapter的通信方法把一个字节的数据发送 到iic设备中去 transferred++; if(transferred >= dev->write_max) break; } return transferred; } static ssize_t at24_bin_read(struct file *filp, char *buf, size_t count, loff_t *ppos) { ssize_t retval = 0; ssize_t status; loff_t off; struct At24c02_dev *dev; if(unlikely(!count)) return count; dev = (struct At24c02_dev *)filp->private_data; off = filp->f_pos; mutex_lock(&dev->lock); /* 访问设备前,加锁*/ while(count){ status = at24_eeprom_read(filp, buf, off, count); if(status <= 0){ if(retval == 0) retval = status; break; } buf += status; off += status; count -= status; retval += status; } mutex_unlock(&dev->lock); return retval; } static ssize_t at24_bin_write(struct file *filp, const char *buf, size_t count, loff_t *ppos) { ssize_t retval = 0; ssize_t status; loff_t off; struct At24c02_dev *dev; if(unlikely(!count)) return count; dev = (struct At24c02_dev *)filp->private_data; off = filp->f_pos; mutex_lock(&dev->lock); /* 访问设备前,加锁*/ while(count){ status = at24_eeprom_write(filp, buf, off, count); if(status <= 0){ if(retval == 0) retval = status; break; } buf += status; off += status; count -= status; retval += status; } mutex_unlock(&dev->lock); return retval; } static loff_t at24_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = offset; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + offset; break; case 2: /* SEEK_END */ newpos = MEMDEV_SIZE - 1 + offset; break; default: /* can't happen */ return -EINVAL; } if ((newpos<0) || (newpos > MEMDEV_SIZE)) return -EINVAL; filp->f_pos = newpos; return newpos; } static struct file_operations at24c02_fops = { .owner = THIS_MODULE, .open = at24c02_open, .write = at24_bin_write, .read = at24_bin_read, .llseek = at24_llseek, .release = NULL, }; static int at24c02_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; #ifdef DEBUG printk(KERN_NOTICE"The routine of at24c02 probe! "); #endif At24c02_devp = kzalloc(sizeof(struct At24c02_dev), GFP_KERNEL); if(!At24c02_devp) return -ENOMEM; memset(At24c02_devp, 0, sizeof(struct At24c02_dev)); At24c02_devp->at24c02_client = client; //把分配的i2c_client赋给定义的全局变量 //杂项设备定义 At24c02_devp->at24c02_miscdev.minor = MISC_DYNAMIC_MINOR; At24c02_devp->at24c02_miscdev.name = DEVICE_NAME; At24c02_devp->at24c02_miscdev.fops = &at24c02_fops; At24c02_devp->write_max = 50; mutex_init(&At24c02_devp->lock); /* 初始化设备锁 */ ret = misc_register(&At24c02_devp->at24c02_miscdev); //注册杂项设备 #ifdef DEBUG printk(KERN_NOTICE"The driver of at24c02 has register! "); #endif return ret; } static int at24c02_remove(struct i2c_client *client) { misc_deregister(&At24c02_devp->at24c02_miscdev); #ifdef DEBUG printk(KERN_NOTICE"The routine of at24c02 remove! "); #endif return 0; } #define AT24_DEVICE_MAGIC(_len, _flags) ((1 << AT24_SIZE_FLAGS | (_flags)) << AT24_SIZE_BYTELEN | ilog2(_len)) static const struct i2c_device_id at24_ids[] = { /* old variants can't be handled with this generic entry! */ { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) }, //1K bit { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) }, //2K bit { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) }, //4K bit /* 24rf08 quirk is handled at i2c-core */ { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) }, //8K bit { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) }, //16K bit { "at24", 0 }, { /* END OF LIST */ } }; //当把设备挂接到总线上时,就调用这里面的名字和注册在总线里的名字比对, //如果一样就会调用probe函数,同时给挂接的设备分配i2c_client结构体 MODULE_DEVICE_TABLE(i2c, at24_ids); struct i2c_driver at24c02_driver = { .driver = { .name = DEVICE_NAME, .owner = THIS_MODULE, }, .probe = at24c02_probe, .remove = at24c02_remove, .id_table = at24_ids, }; static int __init at24c02_init(void) { printk(KERN_NOTICE"The driver of at24c02 is insmod! "); return i2c_add_driver(&at24c02_driver); //把iic设备挂接到总线上 } static void __exit at24c02_exit(void) { printk(KERN_NOTICE"at24c02 is remmod! "); i2c_del_driver(&at24c02_driver); } MODULE_DESCRIPTION("AT24C02 eeprom driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("LouisGou"); module_init(at24c02_init); module_exit(at24c02_exit); 上面就是完整的eeprom驱动,当然驱动写完了,需要写个简单的Makefile来编译这个驱动,好吧,下面就是Makefile文件的内容 obj-m := at24.o KDIR := /root/code/linux-stable-3.10.46 all: @make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm clean: @rm -rf *.o *.ko *.order *.symvers *.bak .*.cmd *.mod.o *.mod.c .tmp_versions cp: @make clean @make cp *.ko /root/code/rootfs/root/at24c02 上面的Makefile文件很是简单,就不做过多的解释了。当把驱动编译好了,用动态的方式挂载到了linux内核上后,你还得做个简单的测试程序,来验证咱们写的驱动工作是否正常,下面就直接贴出来吧。 #include #include #include #include #include #include #include void print_usage(char *file) { printf("%s r addr ", file); printf("%s w addr val ", file); } int main(int argc, char **argv) { int i; int ret; char value[19] = "eeprom-driver test!"; char backvalue[19]; unsigned char addr, data; if ((argc != 3) && (argc != 4)) { print_usage(argv[0]); return -1; } int fd; fd = open("/dev/24c02",O_RDWR); if(fd<0){ printf("Open at24c02 device failed! "); exit(1); } addr = strtoul(argv[2], NULL, 0); ret = lseek(fd, addr, SEEK_SET); if(ret == -1) { perror(""); } if(strcmp(argv[1], "r") == 0) { read(fd, &data, 1); printf("read data:%#x %#x %c ", addr, data, data); } else if((strcmp(argv[1], "w") == 0) && (argc == 4)) { data = strtoul(argv[3], NULL, 0); write(fd, &data, 1); printf("write data:%#x %#x %c ", addr, data, data); } else { print_usage(argv[0]); close(fd); return -1; } close(fd); return 0; } #include #include #include #include #include #include #include void print_usage(char *file) { printf("%s r addr ", file); printf("%s w addr val ", file); } int main(int argc, char **argv) { int i; int ret; char value[19] = "eeprom-driver test!"; char backvalue[19]; unsigned char addr; char data[100] = "0123456789qwertyuiopasdfghjklzxcvbnm,./;'[]/*-+?><"; if ((argc != 3) && (argc != 4)) { print_usage(argv[0]); return -1; } int fd; fd = open("/dev/24c02",O_RDWR); if(fd<0){ printf("Open at24c02 device failed! "); exit(1); } addr = strtoul(argv[2], NULL, 0); ret = lseek(fd, addr, SEEK_SET); if(ret == -1) { perror(""); } if(strcmp(argv[1], "r") == 0) { printf("read data:%#x ", addr); read(fd, &data, 100); printf("read data:%#x %#x %#s ", addr, data, data); } else if((strcmp(argv[1], "w") == 0) && (argc == 4)) { //data = strtoul(argv[3], NULL, 0); write(fd, &data, strlen(data)); printf("write data:%#x %#x %#s ", addr, data, data); } else { print_usage(argv[0]); close(fd); return -1; } close(fd); return 0; } 哈哈,驱动就写完了,我自己测试了,没问题,你可以试试,下一篇我们会分析iic总线驱动。