一、前言
前面第二篇文章中,我总结了Linux系统下i2c驱动中的适配器驱动,但是一个完整的总线-设备驱动模型应该包含总线驱动和设备驱动,总线驱动也就是前面所总结的i2c适配器驱动,现在再来总结一下i2c设备驱动的具体实现步骤。
二、硬件平台及内核版本
硬件平台:NXP I.MX6Q(四核)
Kernel版本:3.0.35(不支持设备树)
三、代码实现
1.在板级文件中添加i2c设备信息
(1)首先找到板级文件,我的文件是:arch/arm/mach-mx6/board-mx6q_sabresd.c
i2c的设备信息是在板级文件中通过i2c_board_info来描述的,因此找到i2c_board_info并添加我的i2c设备信息
static struct i2c_board_info mxc_i2c1_board_info[] __initdata = { { I2C_BOARD_INFO("mxc_hdmi_i2c", 0x50), }, { //我增加的一个虚拟的I2C设备 I2C_BOARD_INFO("xxx_i2c", 0x30),//设备名字是xxx_i2c,地址是0x30 }, #ifdef CONFIG_SND_SOC_IMX_SGTL5000 { I2C_BOARD_INFO("sgtl5000", 0x0a), }, #endif #ifdef CONFIG_MXC_CAMERA_OV3640 { I2C_BOARD_INFO("ov3640", 0x3c),//0x78 0x3c .platform_data = (void *) &camera_data_ov3640, }, #endif };
(2)在板级初始化函数中初始化设备
找到mx6_sabresd_board_init()函数,这个函数里面调用了很多初始化函数,除了我们自己的i2c设备之外,它还调用很多外设的初始化函数,比如spi、uart等
static void __init mx6_sabresd_board_init(void) { ... ... mx6q_sabresd_init_uart();//初始化串口 ... ... imx6q_add_imx_i2c(0, &mx6q_sabresd_i2c_data); imx6q_add_imx_i2c(1, &mx6q_sabresd_i2c_data); imx6q_add_imx_i2c(2, &mx6q_sabresd_i2c_data); i2c_register_board_info(0, mxc_i2c0_board_info, ARRAY_SIZE(mxc_i2c0_board_info)); //注册刚刚新增加的i2c设备 i2c_register_board_info(1, mxc_i2c1_board_info, ARRAY_SIZE(mxc_i2c1_board_info)); i2c_register_board_info(2, mxc_i2c2_board_info, ARRAY_SIZE(mxc_i2c2_board_info)); ... ... }
通过以上两个步骤,新增加的i2c设备就被添加到系统当中了,此时设备的名字是"xxx_i2c",当我们在编写i2c设备驱动的时候,驱动名字也一定要是"xxx_i2c",只有这样当在加载驱动或者设备的时候,才能互相匹配到。
2.编写设备驱动
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/i2c.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define XXX_I2C_CNT 1 /*设备数*/ #define XXX_I2C_NAME "xxx_i2c" /*设备名字*/ /*设备结构体*/ struct _xxx_i2c_dev { dev_t devid; /*设备号*/ int major; int minor; struct cdev cdev; struct class *class; struct device *device; //struct device_node *nd; /*设备节点*/ void *private_data; /*私有数据*/ }; struct _xxx_i2c_dev xxx_i2c_dev; static int xxx_i2c_open(struct inode *inode, struct file *filp) { filp->private_data = &xxx_i2c_dev; return 0; } static ssize_t xxx_i2c_read(struct file *filp, char __user *buf, size_t size, loff_t *loft) { return 0; } static ssize_t xxx_i2c_write(struct file *filp, const char __user *buf, size_t size, loff_t *loft) { return 0; } static int xxx_i2c_release(struct inode *inode, struct file *filp) { return 0; } const struct file_operations xxx_i2c_fops = { .owner = THIS_MODULE, .open = xxx_i2c_open, .read = xxx_i2c_read, .write = xxx_i2c_write, .release = xxx_i2c_release, }; int xxx_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { printk(KERN_WARNING "xxx_i2c_probe "); /*1.创建设备号*/ if(xxx_i2c_dev.major) { xxx_i2c_dev.devid = MKDEV(xxx_i2c_dev.major, 0); register_chrdev_region(xxx_i2c_dev.devid, XXX_I2C_CNT, XXX_I2C_NAME);/*注册一个字符设备*/ } else { alloc_chrdev_region(&xxx_i2c_dev.devid, 0, XXX_I2C_CNT, XXX_I2C_NAME); xxx_i2c_dev.major = MAJOR(xxx_i2c_dev.devid); xxx_i2c_dev.minor = MINOR(xxx_i2c_dev.devid); } /*2.初始化、注册设备*/ cdev_init(&xxx_i2c_dev.cdev, &xxx_i2c_fops); cdev_add(&xxx_i2c_dev.cdev, xxx_i2c_dev.devid, XXX_I2C_CNT); /*3.创建类*/ xxx_i2c_dev.class = class_create(THIS_MODULE, XXX_I2C_NAME); if(IS_ERR(xxx_i2c_dev.class)) { printk(KERN_WARNING "Create class failed "); return PTR_ERR(xxx_i2c_dev.class); } /*4.创建设备*/ xxx_i2c_dev.device = device_create(xxx_i2c_dev.class, NULL, xxx_i2c_dev.devid, NULL, XXX_I2C_NAME); if(IS_ERR(xxx_i2c_dev.device)) { printk(KERN_WARNING "Create device failed "); return PTR_ERR(xxx_i2c_dev.device); } return 0; } int xxx_i2c_remove(struct i2c_client *client) { /*1.删除设备*/ cdev_del(&xxx_i2c_dev.cdev); /*2.注销设备*/ device_destroy(xxx_i2c_dev.class, xxx_i2c_dev.devid); class_destroy(xxx_i2c_dev.class); return 0; } /*匹配表,用于非设备树的情况下的i2c设备*/ static const struct i2c_device_id xxx_i2c_id[] = { {"xxx_i2c", 0},/*名字要和板级文件里面的设备名字相对应*/ {}, }; MODULE_DEVICE_TABLE(i2c, xxx_i2c_id);//通过 MODULE_DEVICE_TABLE 声明一下 xxx_i2c_id设备匹配表 /*驱动结构体*/ static struct i2c_driver xxx_i2c_drv = { .probe = xxx_i2c_probe, .remove = xxx_i2c_remove, .driver = { .owner = THIS_MODULE, .name = "xxx_i2c", }, .id_table = xxx_i2c_id,//用于设备和驱动匹配,设备属性在板级文件中有初始化,比如设备的名字 }; static int __init xxx_i2c_drv_init(void) { int val = 0; val = i2c_add_driver(&xxx_i2c_drv);/*添加驱动*/ if(val != 0){ printk(KERN_WARNING "Add xxx_i2c_drv failed "); } return 0; } static void __exit xxx_i2c_drv_exit(void) { i2c_del_driver(&xxx_i2c_drv); } module_init(xxx_i2c_drv_init); module_exit(xxx_i2c_drv_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xxx");
编写完成后make一下,把编译数来的ko文件拷贝到文件系统中,运行命令进行驱动的安装
#depmod
#modprobe my_i2c.ko
成功安装后,查看/dev路径下的设备,即可看到我们刚添加的设备xxx_i2c