本次写一下基于Linux的i2c子系统的简单驱动程序的编写.首先来了解一下i2c子系统的框架(i2c协议相关知识请自行网上找资料),如下图:
上图大概可以反应Linux中i2c子系统的一个框架,包括①用户层, ②内核驱动层, ③物理硬件层.本次主要是写内核驱动层的内容,如上图i2c子系统的内核驱动层包括:(1)i2c设备驱动层, (2)i2c设备总线层, (3)i2c适配器层.
其中(2)i2c设备总线层代码(由Linux内核提供), (3)i2c适配器层代码(由芯片原厂提供),我们需要客制化开发的是(1)i2c设备驱动层,因为i2c协议是一个固定的情况,只要对于对于专门的硬件修改相应的硬件驱动程序即可,是相对比较稳定的代码,而支持i2c的设备是千变万化的,所以Linux内核留出(1)i2c设备驱动层给用户开发时比较好的一个考虑.本例程是基于exynos4412来写的,三星的CPU种类比较多,所以大多数驱动程序都会采用平台总线,以使驱动代码的适用性得到提高.实际上只要包含操作寄存器的驱动都可以采用平台总线的方式,这里的i2c程序也是使用平台总线,我使用的Linux版本是友善之臂提供的Linux-3.5.里面的mach-tiny4412.c就是定义了许多平台总线设备层数据,i2c的设备数据也定义在这里,在这里添加我们自己定义的设备数据,下面我们来看一下开发板的EEPROM资源之后,来确定要添加什么数据,1506的底板的EEPROM电路图如下:
从上图看出设备接在i2c的第0组上面,因为EEPROM的A0, A1, A23个管脚都接地,所以地址是1010000 == 0x50.我们现在已经掌握了关键数据了,现在去把这些信息添加到mach-tiny4412.c里面,添加的位置如下图红框部分:
添加完之后,我们修改menuconfig,有如下选项:
make menuconfig Device Drivers ---> <*> I2C support --->//i2c-core.c <*> I2C device interface//通用i2c从设备驱动--主要用于调试(可选) I2C Hardware Bus support ---> <*> S3C2410 I2C Driver //i2c-s3c2410.c
之后重新编译内核:
make -j4
编译完之后会生成新的zImage,我们这里直接用zImage就好了,因为U-boot默认直接识别为ARM平台,所以可以不用包装成uImage,由于之前移植的U-boot-->uboot-tiny4412-1506好像没有移植网卡驱动,从电路图上看此网卡用的是USB接口,所以U-boot目前不支持,需要将Linux内核里的相关驱动移植过来,现在还没移植,所以现在还用不了tftp服务,无法远程网络下载内核,现在没空移植这个驱动,所以这里先把更新好的内核放到SD卡里面,然后直接从SD卡启动这样的方式来验证本此的驱动程序.
这里还把怎么把U-boot刷进SD卡,把zImage放入SD都说明一下:
我在<<tiny4412新手入门,从零开始一步一步到挂载网络根文件系统>>一节有提供一个U-boot-->uboot-tiny4412-1506,下载文件,然后放到Linux下,执行如下操作:
unzip uboot-tiny4412-1506.zip cd uboot-tiny4412-1506 unzip uboot_tiny4412-master.zip cd uboot_tiny4412-master make tiny4412_config make
编译完成之后,接着执行如下命令:
cd sd_fuse make
编译之后,我们插入SD卡,假设SD卡被Linux识别为/dev/sdb,执行如下命令:
cd sd_fuse/tiny4412 sudo ./sd_fusing.sh /dev/sdb执行上面内容之后就可以把U-boot刷进SD卡了,然后把zImage复制进SD卡里即可.然后把卡插进开发板之后,把boot开关切到SD卡启动模式启动,设置一下基本的u-boot环境,就可以在加载完内核之后通过网络挂载文件系统(此时已有网卡驱动了).至于怎么设置,直接去看<<tiny4412新手入门,从零开始一步一步到挂载网络根文件系统>>就好了.
下面,直接上代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/i2c.h> #include <linux/slab.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/input.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/platform_device.h> #include <asm/io.h> #include <asm/uaccess.h> #define DEVICE_STYLE_ONE TRUE // 注册设备节点风格1 struct eeprom_i2c_driver { int major; #ifndef DEVICE_STYLE_ONE struct class *cls; #endif struct device *dev; struct i2c_client *client; }; struct eeprom_i2c_driver *at24_drv; #if defined (DEVICE_STYLE_ONE) struct class at24_class = { .name = "at24_cls", }; void at24_dev_release(struct device *dev) { // do nothing } #endif int at24_drv_open(struct inode *inode, struct file *filp) { // 硬件初始化,eeprom属于上电即可工作的器件,不需要初始化 return 0; } ssize_t at24_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops) { int ret = -1; if(0 > count || 2048 < count) return -EFAULT; char *temp = kzalloc(count, GFP_KERNEL); if(NULL == temp){ printk("read kzalloc failed !\n"); return -ENOMEM; } ret = i2c_master_recv(at24_drv->client, temp, count); if(0 > ret){ printk("i2c_master_recv failed !\n"); kfree(temp); return ret; } ret = copy_to_user(buf, temp, count); if(0 < ret){ printk("copy to user failed !\n"); ret = -EBUSY; } kfree(temp); return count; } ssize_t at24_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops) { int ret = -1; if(0 > count || 2048 < count) return -EFAULT; char *temp = kzalloc(count, GFP_KERNEL); if(NULL == temp){ printk("read kzalloc failed !\n"); return -ENOMEM; } ret = copy_from_user(temp, buf, count); if(0 < ret){ printk("copy from user failed !\n"); kfree(temp); return -EBUSY; } ret = i2c_master_send(at24_drv->client, temp, count); if(0 > ret){ printk("i2c write failed !\n"); kfree(temp); return ret; } kfree(temp); return count; } /** * 我们可以通过llseek来指定读取时的偏移地址 * 为了代码简洁,这里暂不做处理 */ loff_t at24_drv_llseek(struct file *filp, loff_t fops, int count) { // do nothing return 0; } int at24_drv_release(struct inode *inode, struct file *filp) { // do nothing return 0; } struct file_operations at24_fops = { .open = at24_drv_open, .read = at24_drv_read, .write = at24_drv_write, .llseek = at24_drv_llseek, .release = at24_drv_release, }; int eeprom_i2c_probe(struct i2c_client *client, const struct i2c_device_id *devid) { int ret = -1; printk("-----%s active------\n", __func__); // 0, 申请设备对象空间 at24_drv = kzalloc(sizeof(struct eeprom_i2c_driver), GFP_KERNEL); if(NULL == at24_drv){ printk("kzalloc failed !\n"); return -ENOMEM; } // 1, 申请设备号 at24_drv->major = register_chrdev(0, "at24_chrdev", &at24_fops); if(at24_drv->major < 0){ printk("register char device failed !\n"); ret = -ENODEV; goto err1; } #if defined (DEVICE_STYLE_ONE) // 代码走这里 // 2, 创建设备类 ret = class_register(&at24_class); if(ret){ printk("class register failed !\n"); goto err2; } // 3, 创建设备节点 dev_set_name(&at24_drv->dev, "at24_dev"); at24_drv->dev.devt = MKDEV(at24_drv->major, 0); at24_drv->dev.class = &at24_class, at24_drv->dev.parent = NULL, at24_drv->dev.release = at24_dev_release, device_initialize(&at24_drv->dev); device_add(&at24_drv->dev); #else // 2, 创建设备类 at24_drv->cls = class_create(THIS_MODULE, "at24_cls"); if(IS_ERR(at24_drv->cls)){ printk("class create failed !\n"); ret = PTR_ERR(at24_drv->cls); goto err2; } // 3, 创建设备节点 at24_drv->dev = device_create(at24_drv->cls, NULL, MKDEV(at24_drv->major, 0), NULL, "at24_dev"); if(IS_ERR(at24_drv->dev)){ printk("device create failed !\n"); ret = PTR_ERR(at24_drv->dev); class_destroy(at24_drv->cls); goto err2; } #endif // 4, 记录当前设备对象 at24_drv->client = client; return 0; err2: unregister_chrdev(at24_drv->major, "at24_chrdev"); err1: kfree(at24_drv); return ret; } int eeprom_i2c_remove(struct i2c_client *client) { #if defined(DEVICE_STYLE_ONE) device_del(&at24_drv->dev); class_unregister(&at24_class); #else device_destroy(at24_drv->cls, MKDEV(at24_drv->major, 0)); class_destroy(at24_drv->cls); #endif unregister_chrdev(at24_drv->major, "at24_chrdev"); kfree(at24_drv); return 0; } const struct i2c_device_id eeprom_table[] = { {"at24c02a", 0x2}, {"at24c04a", 0x4}, }; struct i2c_driver eeprom_driver = { .probe = eeprom_i2c_probe, .remove = eeprom_i2c_remove, .driver = { .name = "at24_drv", }, .id_table = eeprom_table, }; static void __exit eeprom_i2c_exit(void) { i2c_del_driver(&eeprom_driver); } static int __init eeprom_i2c_init(void) { return i2c_add_driver(&eeprom_driver); } module_init(eeprom_i2c_init); module_exit(eeprom_i2c_exit); MODULE_LICENSE("GPL");
应用层测试代码是:
#include <linux/i2c-dev.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <sys/ioctl.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define I2C_SLAVE 0x0703 void print_usage(char *str) { printf("%s r : read at24c02 addresss 0\n", str); printf("%s w val : write at24c02 addresss 0\n", str); } int main(int argc,char **argv) { int fd; unsigned char val;//字节 char register_addr = 0x08; /* Device register to access 片内地址*/ int res; char wbuf[10]; char rbuf[10]; if (argc < 2){ print_usage(argv[0]); exit(1); } /*打开设备文件*/ fd = open("/dev/at24_dev", O_RDWR); if (fd < 0) { perror("open failed"); exit(1); } if (strcmp(argv[1], "r") == 0){ if (write(fd, ®ister_addr, 1)!=1) { perror("write failed"); exit(1); } if (read(fd, rbuf, 1) != 1) { perror("read failed"); exit(1); } else { printf("rbuf =0x%x\n",rbuf[0]); } } else if ((strcmp(argv[1], "w") == 0) && (argc == 3)) { // ./test w 0x99 val = strtoul(argv[2], NULL, 0); wbuf[0] = register_addr; // 片内地址0x08 wbuf[1] = val; if (write(fd, wbuf, 2)!=2) { perror("write failed"); exit(1); } printf("write data ok!\n"); } close(fd); return 0; }
Makefile是:
#指定内核源码路径 KERNEL_DIR = /home/george/1702/exynos/linux-3.5 #指定当前路径 CUR_DIR = $(shell pwd) MYAPP = at24_app MODULE = exynos4412_i2c_drv all: make -C $(KERNEL_DIR) M=$(CUR_DIR) modules arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c clean: make -C $(KERNEL_DIR) M=$(CUR_DIR) clean $(RM) $(MYAPP) install: cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702 #指定编译当前目录下哪个源文件 obj-m = $(MODULE).o下面是测试步骤和结果: