和i2c有关的代码都在源码drivers/i2c目录下。内核提供了两种i2c的实现方法:
第二种可以认为是正统的i2c驱动,其本质是:工程师任意选用input子系统、misc框架、普通字符驱动等方式实现i2c驱动,i2c子系统的意义仅仅是为硬件操作提供接口(库)
如图
可以看出,i2c子系统基本机制和platform很类似,都是设备和驱动两者匹配来工作。i2c驱动只需调用核心层提供的接口(相当于核心层提供了库),即可方便地操作i2c
i2c总线核心提供了设备驱动和设备(client)的注册、注销方法, 还提供了一组不依赖于硬件平台的接口函数,I2C 总线驱动和设备驱动之间依赖于 I2C 核心作为纽带
所谓的i2c适配器驱动,就是soc内部的i2c控制器的驱动,由原厂移植内核时提供,一般位于driver/i2c/busses内。而i2c适配器设备的注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论
我们这里用的是i2c-s3c2410.c,该驱动兼容三星大部分的soc,包括210。该驱动由platform总线实现,该驱动probe函数中主要做了:
在新内核下,i2c适配器的驱动倒是没有变化,而i2c适配器设备体的注册,却采用了设备树的方式
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_I2C1>;
status = "disabled";
};
所谓的i2c设备(client),就是挂在i2c上的外设(比如各种传感器),这个需要我们自己注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论
对于老版本的内核,首先应该进入mach-xxx.c完成i2c设备(client)的注册。如何注册?这方面i2c和platform有较大不同,主要是soc上有多个i2c,所以是分开注册的
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
static struct i2c_board_info i2c_devs0[] __initdata = {
{
I2C_BOARD_INFO("wm8580", 0x1b),
/*假如要添加设备,就在这里加*/
},
};
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hummingboard_i2c1>;
status = "okay";
rtc: pcf8523@68 {
compatible = "nxp,pcf8523";
reg = <0x68>;
};
};
首先要明白一点,对于驱动工程师,如果手中是移植过的内核,则i2c总线核心和i2c适配器驱动是不需要动的,我们主要关注点在:提供i2c设备(client)、编写i2c设备驱动
#include
#include
#include
#include
#include
#include
#include
#include
/*mpu6050内部寄存器地址 */
#define MPU6050_RA_PWR_MGMT_1 0x6B
#define MPU6050_RA_ACCEL_XOUT_H 0x3B
#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"
struct i2c_client *mpu6050_client;
/*
* 写mpu6050内部的寄存器。先发寄存器地址再发寄存器的值
*/
static int mpu6050_write_reg(unsigned char addr, unsigned char dat)
{
int ret = -1;
struct i2c_msg msgs[2];
msgs[0].addr = mpu6050_client -> addr;//MPU6050_ADDR
msgs[0].buf = &addr;
msgs[0].len = 1; //长度1 byte
msgs[0].flags = 0; //表示写
msgs[1].addr = mpu6050_client -> addr;//MPU6050_ADDR
msgs[1].buf = &dat;
msgs[1].len = 1; //长度1 byte
msgs[1].flags = 0; //表示写
/*连续发送两帧信息*/
ret = i2c_transfer(mpu6050_client ->adapter, msgs, 2);
if (ret != 2) {
printk(KERN_INFO "i2c_transfer(mpu6050 write) error \n");
return -EIO;
}
return 0;
}
/*
*读mpu6050内部的寄存器。先发寄存器地址再读寄存器的值
*/
static int mpu6050_read_reg(unsigned char addr, unsigned char buf)
{
int ret = -1;
struct i2c_msg msgs[2];
msgs[0].addr = mpu6050_client -> addr;//MPU6050_ADDR
msgs[0].buf = &addr;
msgs[0].len = 1; //长度1 byte
msgs[0].flags = 0; //表示写
msgs[1].addr = mpu6050_client -> addr;//MPU6050_ADDR
msgs[1].buf = &buf;
msgs[1].len = 1; //长度1 byte
msgs[1].flags = I2C_M_RD; //表示读
/*连续发送两帧信息*/
ret = i2c_transfer(mpu6050_client ->adapter, msgs, 2);
if (ret != 2) {
printk(KERN_INFO "i2c_transfer(mpu6050 read) error \n");
return -EIO;
}
return 0;
}
static int mpu6050_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "open mpu6050\n");
msleep(50);
mpu6050_write_reg(MPU6050_RA_PWR_MGMT_1, 0X80);//复位
/*这里仅仅做个例子,一般在这里要做初始化*/
return 0;
}
ssize_t mpu6050_read(struct file *file, char __user *ubuf,
size_t size, loff_t *opp)
{
unsigned char buf [6] = {0};
mpu6050_read_reg(MPU6050_RA_GYRO_XOUT_H, buf[0]);
/*这里仅仅是举个例子,怎么从外设中读数据*/
ret = copy_to_user(ubuf, buf , size);
if (ret) {
printk(KERN_INFO "copy_to_user fail\n");
return -EINVAL;
}
return 0;
}
static int mpu6050_release(struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
static struct cdev *mpu6050_pcdev;
static struct class *mpu6050_pclass;
dev_t mpu6050dev_num = 0;
unsigned int mpu6050dev_major = 0;
int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = -1;
mpu6050_client = client;
/*内核自动分配一个设备号*/
ret = alloc_chrdev_region(&mpu6050dev_num, 0, MPU6050_CNT, MPU6050_NAME);
mpu6050dev_major = MAJOR(mpu6050dev_num);
if (ret < 0) {
printk(KERN_INFO "alloc_chrdev_region fail\n");
goto out_err_0;
}
printk(KERN_INFO "MAJOR %d\n", mpu6050dev_major);
/*实例化一个字符设备体*/
mpu6050_pcdev = cdev_alloc();
/*填充cdev设备体 。最主要是将file_operations填充进去*/
cdev_init(mpu6050_pcdev, &mpu6050_fops);
/* 将设备体与设备号绑定并向内核注册一个字符设备*/
ret = cdev_add(mpu6050_pcdev, mpu6050dev_num, MPU6050_CNT);
if (ret) {
printk(KERN_INFO "cdev_add fail\n");
goto out_err_1;
}
/*创建类、设备*/
mpu6050_pclass = class_create(THIS_MODULE, "mpu6050");
if (IS_ERR(mpu6050_pclass)) { //排错
printk(KERN_ERR "can't register class\n");
goto out_err_2;
}
device_create(mpu6050_pclass, NULL, mpu6050dev_num, NULL, "mpu6050");
return 0;
/* “倒影式”错误处理流程*/
out_err_3:
class_destroy(mpu6050_pclass);
out_err_2:
cdev_del(mpu6050_pcdev);
out_err_1:
unregister_chrdev_region(mpu6050dev_num, MPU6050_CNT);
out_err_0:
return -EINVAL;
}
int mpu6050_remove(struct i2c_client *client)
{
/*倒影式注销流程*/
device_destroy(mpu6050_pclass, mpu6050dev_num);
class_destroy(mpu6050_pclass);
cdev_del(mpu6050_pcdev);
unregister_chrdev_region(mpu6050dev_num, MPU6050_CNT);
return 0;
}
/*
* i2c设备驱动结构体内的id_table。用作匹配功能
*/
static struct i2c_device_id mpu6050_id[] = {
{ "mpu6050", 0},
{ }
};
/*
* 这里开始定义i2c设备驱动结构体
*/
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",//i2c总线和platform不同这个name仅仅是名字。并不用作匹配功能
.owner = THIS_MODULE,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = mpu6050_id,//i2c总线和platform不同。只用id_table来匹配driver和client
};
/*
* 模块加载函数负责注册i2c设备驱动
*/
static int __init mpu6050_init(void)
{
return i2c_add_driver(&mpu6050_driver);
}
static void __exit mpu6050_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");
i2c_client *client
将作为参数传入probe,这个i2c_client里面就包含了设备的私有数据(比如设备的i2c地址、绑定的i2c适配器等),类似plat_data,我们在probe中将i2c_client *client
绑定给全局变量mpu6050_client
,这样就能在read、write等函数中用mpu6050_client -> addr
来得到设备的i2c地址了,用mpu6050_client ->adapter
来得到绑定的i2c适配器i2c_transfer
,这种方法有点老旧;kernel官方强烈推荐smbus族接口来进行i2c收发,smbus是i2c_transfer的子集,很多soc可能不支持i2c_transfer
这个接口,这时就只能使用smbus族接口。这两个接口在内部逻辑上有很大不同,比如我们要写mpu6050内部的RA_PWR_MGMT_1寄存器,根据上面的代码,我们调用了两次i2c_transfer
,第一次发送RA_PWR_MGMT_1寄存器的地址,第二次发送要写的值。而对于smbus族接口来说,只需调用一次就行了,可以认为smbus族接口进行了更好的封装,不仅写操作如此,读操作也如此,具体接口如下/*第一个参数是client,第二个参数是i2c设备内的寄存器地址,第三个参数是要写入的值*/
i2c_smbus_write_byte_data(mpu6050_client, MPU6050_RA_PWR_MGMT_1, data);
/*第一个参数是client,第二个参数是i2c设备内的寄存器地址,返回值是读出来的值*/
read_val = i2c_smbus_read_byte_data(mpu6050_client, MPU6050_RA_ACCEL_XOUT_H);
i2c_smbus_read_word_data();
i2c_smbus_write_word_data();
设备树对i2c设备的注册有比较大的影响,详见前面的章节,这里不再赘述;而对于驱动程序,设备树带来的变化极小,主要是驱动和设备之间的匹配方式变了
/*定义的of_match_table*/
static const struct of_device_id pcf8523_of_match[] = {
{ .compatible = "nxp,pcf8523" },
{ }
};
/*driver 结构体中的of_match_table*/
static struct i2c_driver pcf8523_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(pcf8523_of_match),
},
.probe = pcf8523_probe,
.id_table = pcf8523_id,
};