文章目录
1、简介
Linux中的I2C驱动框架和platform平台设备驱动类似,都是采用的设备驱动分离分层的概念,不过platform平台总线是内核中虚拟出来的,而I2C总线则是SoC里真实存在的。既然都是采用总线的方式,那么在Linux内核中I2C驱动也是被分为两部分:
- I2C总线驱动:也称I2C适配器驱动或I2C控制器驱动,比如IMX6ULL的I2C控制器驱动,就是控制I2C的SCL、SDA引脚电平传输数据的函数封装,这部分已经由SoC原厂做好了,我们直接可以使用;
- I2C设备驱动:这部分就是针对具体的外接设备的驱动,比如MPU6050的驱动。同时,这部分还被分成I2C设备和I2C驱动,前者偏向于描述硬件的信息,后者偏向于通用的软件逻辑,在编写某个I2C设备的驱动时这部分是重点关注。
接下来就来以Freescale i.MX6ULL(内核版本:4.1.15)为例先探究I2C总线驱动与I2C设备驱动的原理,然后再自己写一个简陋的MPU6050驱动。注意,如果只是想快速了解I2C设备的驱动,则可以直接跳到第4小节开始。
2、I2C总线、设备和驱动的结构体定义
这一小节主要是先了解一下总线、设备和驱动被内核抽象成的结构体定义及注册函数,下一小节“I2C总线、设备和驱动三者是如何联系起来的”再深入理解三者的关系。
2.1 结构体定义–I2C总线
正如前面所说,I2C总线驱动一般都是由SoC厂商在Linux原有框架上编写其特有的部分注册到内核中去,这部分我们不用我们去编写,但是我们还是可以了解它有利于理解驱动的框架等。它是被抽象成一个i2c_adapter
结构体,在include/linux/i2c.h
中定义:
struct i2c_adapter {
struct module *owner;
unsigned int class;
const struct i2c_algorithm *algo; /* 硬件操作相关的“算法” */
void *algo_data;
struct rt_mutex bus_lock;
int timeout;
int retries;
struct device dev;
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
其中,内部还包含了i2c_algorithm
“算法”结构体,它里面定义了I2C总线控制传输的函数指针,也是在同文件中定义:
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num); /* 用来和I2C设备通信的函数 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
其中,I2C总线驱动的主要工作就是初始化i2c_adapter
结构体变量和i2c_algorithm
的master_xfer
传输函数,它就是用于与I2C设备通信的读写函数。
辛辛苦苦构造好的结构体,那么接下来就要把它告诉内核。其中注册进内核和从内核中注销的函数如下:
int i2c_add_adapter(struct i2c_adapter *adapter); // 动态总线号
int i2c_add_numbered_adapter(struct i2c_adapter *adap); // 静态总线号
void i2c_del_adapter(struct i2c_adapter * adap); // 删除I2C适配器
2.2 结构体定义–I2C设备
关于I2C设备比较重要的就是i2c_client
结构体,它也是在include/linux/i2c.h
中定义,它会在内核解析设备树或创建I2C设备时进行填充:
struct i2c_client {
unsigned short flags; /* 标志:I2C_CLIENT_TEN表示使用10位芯片地址,
I2C_CLIENT_PEC表示使用SMBus错包检测 */
unsigned short addr; /* 连接到父适配器的I2C总线上使用的地址 */
char name[I2C_NAME_SIZE]; /* 芯片名字 */
struct i2c_adapter *adapter;/* “绑定”该I2C设备所在的总线适配器 */
struct device dev; /* 设备节点 */
int irq; /* 设备终端号 */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb;
#endif
};
2.3 结构体定义–I2C驱动
与I2C总线、设备一样,I2C驱动也同样在include/linux/i2c.h
被抽象成了i2c_driver
结构体类型:
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*probe)(struct i2c_client *, const struct i2c_device_id *); /* 设备与驱动匹配之后被调用 */
int (*remove)(struct i2c_client *); /* 与probe函数刚好相反 */
void (*shutdown)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; /* 如果使用设备树则设置它的of_match_table成员 */
const struct i2c_device_id *id_table; /* 用于与传统不使用设备树的匹配列表 */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
配置好i2c_driver
结构体之后,就可以通过i2c_register_driver
或i2c_add_driver
将它注册到内核中了,相反地,不再使用的时候可以通过i2c_del_driver
将它注销,相关函数定义如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
void i2c_del_driver(struct i2c_driver *driver);
3、I2C总线、设备、驱动、硬件操作的联系
3.1 I2C总线驱动加载到内核的过程
以Freescale i.MX6ULL,Linux 4.1.15版本内核为例,在设备树文件imx6ull.dtsi
中可以看到i2c节点的定义:
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
...
直接grep命令搜索一下“fsl,imx6ul-i2c
”,不过好像没看到和驱动文件相关的,那就搜索下一个compatible
属性“fsl,imx21-i2c
”,这时可以找到在drivers/i2c/busses/i2c-imx.c
中有定义,查看代码就会发现I2C总线驱动还是通过platform总线驱动的方法来注册进内核:
// ... 省略
static const struct of_device_id i2c_imx_dt_ids[] = {
{
.compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
{
.compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
{
.compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
{
/* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);
// ... 省略
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = i2c_imx_dt_ids,
.pm = IMX_I2C_PM,
},
.id_table = imx_i2c_devtype,
};
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);
static void __exit i2c_adap_imx_exit(void)
{
platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);
3.2 I2C总线驱动如何操作硬件
按照platform总线驱动的惯例,I2C总线驱动probe
函数里面就是做一系列的初始化,我们进去函数看看:
/* probe函数里面用到的结构体定义,其中就包含了I2C控制器的结构体i2c_adapter */
struct imx_i2c_struct {
struct i2c_adapter adapter;
// ... 省略
};
static int i2c_imx_probe(struct platform_device *pdev)
{
// ... 省略
struct imx_i2c_struct *i2c_imx;
// ... 省略
/* 设置i2c_adapter结构体 */
strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
i2c_imx->adapter.owner = THIS_MODULE;
i2c_imx->adapter.algo = &i2c_imx_algo; // 硬件操作相关的“算法” --------------->
i2c_imx->adapter.dev.parent = &pdev->dev;
i2c_imx->adapter.nr = pdev->id;
i2c_imx->adapter.dev.of_node= pdev->dev.of_node;
// ... 省略
/* 这个函数后面小节还会回来看这个它的调用过程 */
ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
// ... 省略
}
从上面probe
函数里面可以知道其中一部分工作就是配置i2c_adapter
结构体,还记得上面说过i2c_adapter
结构体里面比较重要的一个成员是algo
,它就是硬件操作相关的“算法”,继续看i2c_imx_algo
,它就在同个文件中定义:
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer, // --------------->
.functionality = i2c_imx_func,
};
static int i2c_imx_xfer(struct i2c_adapter *adapter,
struct i2c_msg *msgs, int num)
{
unsigned int i, temp;
int result;
bool is_lastmsg = false;
struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
/* Start I2C transfer */
result = i2c_imx_start(i2c_imx);
// ... 省略
/* read/write data */
for (i = 0; i < num; i++) {
if (i == num - 1)
is_lastmsg = true;
if (i) {
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_RSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
result = i2c_imx_bus_busy(i2c_imx, 1);
// ... 省略
}
// ... 省略
if (msgs[i].flags & I2C_M_RD)
result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
else {
if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
else
result = i2c_imx_write(i2c_imx, &msgs[i]);
}
// ... 省略
}
/* Stop I2C transfer */
i2c_imx_stop(i2c_imx);
// ... 省略
}
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
| I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}
可以看到,上面的i2c_imx_xfer
就是用于SoC操作I2C的SCL、SDA的时序来实现I2C的start、stop信号等通信功能。那么它又是什么时候被调用的呢?直接从传输函数入手,其中就可以查看i2c_transfer
的调用关系:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
adap->algo->master_xfer(adap, msgs, num);
很明显,i2c_transfer
函数最终就是调用了adap
适配器的algo
“算法”成员的硬件传输函数master_xfer
。所以,在I2C驱动部分调用该函数就可以通过总线驱动来间接地操作到硬件I2C了。
3.3 I2C设备 <-----> I2C驱动
不管是I2C设备、驱动还是I2C总线驱动,都会配置某个成员来进行I2C设备与I2C驱动的配对。以I2C总线驱动为例,上面probe
函数最后部分说了会查看函数i2c_add_numbered_adapter
的调用过程就是该函数,往里面探究一下函数的调用关系:
/* 函数调用关系 */
i2c_add_numbered_adapter/i2c_add_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
adap->dev.bus = &i2c_bus_type;
看了函数调用关系,最终还是配置在配置adapter
适配器中发现了i2c_bus_type
,它在内核drivers/i2c/i2c-core.c
中定义:
/* i2c_bus_type的定义 */
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
/* i2c_device_match的实现 */
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
最终还是和platform平台总线类似,都是使用match
函数来匹配dev和drv,不过这里的match
函数是I2C而不是platform。
3.4 I2C设备 <-----> I2C总线(控制器)
设备树方式:设备放在哪个节点下就是使用哪个总线。
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
};
传统方式:以i2c_new_device
函数为例去添加设备,通过函数创建时就已经指定I2C总线client->adapter
:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
...
client->adapter = adap;
...
}
3.5 I2C驱动 <-----> I2C总线(控制器)
事实上,I2C驱动并没有直接与I2C总线建立联系(要不然将设备和驱动分离也没什么意义了是吧),它是在I2C设备与I2C驱动成功配对之后调用的probe
函数,在probe
函数的参数里获得I2C设备的结构体i2c_client
,里面就包含了adapter
成员(I2C总线):
int (*probe)(struct i2c_client *, const struct i2c_device_id *); // i2c_client -------->
// I2C设备的定义
struct i2c_client {
struct i2c_adapter *adapter;
...
};
4、I2C驱动传输数据API函数
内核里提供两套传输函数,一套是i2c_transfer
,另一套是SMBus(System Management Bus)
。其中,SMBus是I2C协议的一个子集,而且有些适配器仅支持SMBus,所以推荐使用SMBus,相关文档在内核Documentation/i2c/smbus-protocol
。相关函数定义主要在drivers/i2c/i2c-core.c
中定义,仅列出常用的几个:
/* 传输数据:adap是收发消息所用的适配器、msgs是消息的结构体、num是消息的数量 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* 内部调用i2c_transfer,client是I2C设备、buf是要发送/接收的数据、count是发送/接收的字节数 */
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
/* SMBus相关传输函数 */
s32 i2c_smbus_read_byte(const struct i2c_client *client);
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value);
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command, u16 value);
s32 i2c_smbus_read_block_data(const struct i2c_client *client, u8 command, u8 *values);
s32 i2c_smbus_write_block_data(const struct i2c_client *client, u8 command, u8 length, const u8 *values);
5、I2C设备驱动编写框架
5.1 I2C设备框架
I2C设备编写框架参考内核文档Documentation/i2c/instantiating-devices
,里面覆盖设备树、命令行等多种写法,这里仅列出常见的三种:
5.1.1 不使用设备树的写法
static struct i2c_board_info xxx_info = {
I2C_BOARD_INFO("xxx", 0x12), /* <用于与drv匹配的name> <设备地址> */
};
static struct i2c_client *xxx_client;
static int xxx_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0); /* 参数是I2C控制器的编号 */
xxx_client= i2c_new_device(i2c_adap, &xxx_info);
i2c_put_adapter(i2c_adap); /* 配置完之后要put回去 */
return 0;
}
static void xxx_dev_exit(void)
{
i2c_unregister_device(xxx_client);
}
module_init(xxx_dev_init);
module_exit(xxx_dev_exit);
MODULE_LICENSE("GPL");
5.1.2 设备树写法–添加节点
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
// ... 省略
/* 添加的节点 */
xxx@12 {
compatible = "xxx";
reg = <0x12>;
};
};
5.1.3 用户空间echo命令添加设备
/* 添加设备需要:name addr adpter */
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
/* 删除设备只需要:addr adpter */
echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device
5.2 I2C驱动框架
不管“I2C设备”采用哪一种写法,“I2C驱动”的代码框架都是一样的。
static int xxx_open(struct inode *inode, struct file *filp)
{
// ...
return 0;
}
static int xxx_close(struct inode *inode, struct file *filp)
{
// ...
return 0;
}
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.release = xxx_close,
};
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// ...
cdev_init(&cdev, &xxx_fops);
return 0;
}
static int xxx_remove(struct i2c_client *client)
{
// ...
cdev_del(&cdev);
return 0;
}
/* 传统不使用设备树的匹配列表 */
static const struct i2c_device_id xxx_id[] = {
{
"xxx", 0}, /* <用于匹配dev的名字> <私有数据> */
{
}
};
/* 使用设备树的匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{
.compatible = "xxx"}, /* 与设备树节点匹配的名字 */
{
}
};
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
static int __init xxx_init(void)
{
i2c_add_driver(&xxx_driver);
return 0;
}
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
6、MPU6050驱动示例
6.1 I2C设备程序
6.1.1 不使用设备树的写法
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
static struct i2c_client *mpu6050_client;
static struct i2c_board_info mpu6050_info = {
/* STM32的地址写成0xD0 -> 1101 000_
* Linux的地址写成0x64 -> 110 1000 _
* 否则出现错误 i2c i2c-0: Invalid 7-bit I2C address 0xd0
*/
I2C_BOARD_INFO("test,mpu6050", 0x68), /* <用于与驱动匹配的名字>, <设备地址> */
};
static int mpu6050_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0); /* 放在第0个I2C适配器:I2C1 */
mpu6050_client = i2c_new_device(i2c_adap, &mpu6050_info);
i2c_put_adapter(i2c_adap);
return 0;
}
static void mpu6050_dev_exit(void)
{
i2c_unregister_device(mpu6050_client);
}
module_init(mpu6050_dev_init);
module_exit(mpu6050_dev_exit);
MODULE_LICENSE("GPL");
6.1.2 设备树写法–添加节点
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
// ... 省略
/* 添加mpu6050节点 */
mpu6050@68 {
compatible = "book,mpu6050";
reg = <0x68>;
};
};
6.2 I2C驱动程序
正如前面所说,不管“I2C设备”采用哪一种写法,“I2C驱动”的代码框架都是一样的,下面分别使用i2c_transfer
和SMBus
两种方式来编写驱动程序。
6.2.1 I2C驱动写法1:i2c_transfer函数
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/i2c.h>
#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B
#define DEV_CNT 1
struct mpu6050_drv_info{
int major;
struct class *cls;
struct cdev cdev;
struct i2c_client *client;
};
static struct mpu6050_drv_info mpu6050_drv;
static int mpu6050_read_reg(struct i2c_client *client, u8 addr, u8 *buf, u16 size)
{
struct i2c_msg msg[2];
msg[0].addr = client->addr; /* 设备地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = &addr; /* 要读的设备的内部地址 */
msg[0].len = 1; /* 长度 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = buf; /* 把读到的数据传出去 */
msg[1].len = size;
if(2 != i2c_transfer(client->adapter, msg, 2)){
printk("mpu6050_read_reg error!\n");
return -1;
}
return 0;
}
static int mpu6050_write_reg(struct i2c_client *client, u8 addr, u8 data)
{
u8 write_data[2];
struct i2c_msg msg;
write_data[0] = addr;
write_data[1] = data;
msg.addr = client->addr; /* 设备地址 */
msg.flags = 0; /* 标记为发送数据 */
msg.buf = write_data; /* 要写的设备的内部地址及数据 */
msg.len = 2;
if(1 != i2c_transfer(client->adapter, &msg, 1)){
printk("mpu6050_write_reg error!\n");
return -1;
}
return 0;
}
static int mpu6050_open(struct inode *inode, struct file *filp)
{
int ret = 0;
printk("mpu6050_open!\n");
filp->private_data = &mpu6050_drv;
/* 初始化MPU6050 */
ret |= mpu6050_write_reg(mpu6050_drv.client, PWR_MGMT_1, 0x00);
ret |= mpu6050_write_reg(mpu6050_drv.client, SMPLRT_DIV, 0X07);
ret |= mpu6050_write_reg(mpu6050_drv.client, CONFIG, 0x06);
ret |= mpu6050_write_reg(mpu6050_drv.client, ACCEL_CONFIG, 0x01);
return ret;
}
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t sz, loff_t *loff)
{
int ret;
u8 raw_data[12];
struct mpu6050_drv_info *dev = filp->private_data;
printk("mpu6050_read!\n");
mpu6050_read_reg(dev->client, ACCEL_XOUT_H, &raw_data[0], 1);
mpu6050_read_reg(dev->client, ACCEL_XOUT_L, &raw_data[1], 1);
mpu6050_read_reg(dev->client, ACCEL_YOUT_H, &raw_data[2], 1);
mpu6050_read_reg(dev->client, ACCEL_YOUT_L, &raw_data[3], 1);
mpu6050_read_reg(dev->client, ACCEL_ZOUT_H, &raw_data[4], 1);
mpu6050_read_reg(dev->client, ACCEL_ZOUT_L, &raw_data[5], 1);
mpu6050_read_reg(dev->client, GYRO_XOUT_H, &raw_data[6], 1);
mpu6050_read_reg(dev->client, GYRO_XOUT_L, &raw_data[7], 1);
mpu6050_read_reg(dev->client, GYRO_YOUT_H, &raw_data[8], 1);
mpu6050_read_reg(dev->client, GYRO_YOUT_L, &raw_data[9], 1);
mpu6050_read_reg(dev->client, GYRO_ZOUT_H, &raw_data[10], 1);
mpu6050_read_reg(dev->client, GYRO_ZOUT_L, &raw_data[11], 1);
ret = copy_to_user(buf, raw_data, sz);
if(ret < 0){
printk("copy_to_user error!");
return -1;
}
return 0;
}
static int mpu6050_release(struct inode *inode, struct file *filp)
{
printk("mpu6050_release!\n");
return 0;
}
static struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
dev_t devid;
printk("mpu6050_probe!\n");
/* probe函数记得将I2C设备保存起来 */
mpu6050_drv.client = client;
if(mpu6050_drv.major){
devid = MKDEV(mpu6050_drv.major , 0);
register_chrdev_region(devid, DEV_CNT, "mpu6050");
}
else{
alloc_chrdev_region(&devid, 0, DEV_CNT, "mpu6050");
mpu6050_drv.major = MAJOR(devid);
}
cdev_init(&mpu6050_drv.cdev, &mpu6050_fops);
cdev_add(&mpu6050_drv.cdev, devid, DEV_CNT);
mpu6050_drv.cls = class_create(THIS_MODULE, "mpu6050");
device_create(mpu6050_drv.cls, NULL, MKDEV(mpu6050_drv.major, 0), NULL, "mpu6050");
return 0;
}
static int mpu6050_remove(struct i2c_client *client)
{
printk("mpu6050_remove!\n");
device_destroy(mpu6050_drv.cls, MKDEV(mpu6050_drv.major, 0));
class_destroy(mpu6050_drv.cls);
cdev_del(&mpu6050_drv.cdev);
unregister_chrdev_region(MKDEV(mpu6050_drv.major, 0), DEV_CNT);
return 0;
}
/* 传统不使用设备树的匹配列表 */
static const struct i2c_device_id mpu6050_id[] = {
{
"test,mpu6050", 0},
{
}
};
/* 使用设备树的匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
{
.compatible = "test,mpu6050"},
{
}
};
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.id_table = mpu6050_id,
};
static int __init mpu6050_drv_init(void)
{
printk("mpu6050_init!\n");
i2c_add_driver(&mpu6050_driver);
return 0;
}
static void __exit mpu6050_drv_exit(void)
{
printk("mpu6050_exit!\n");
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");
6.2.2 I2C驱动写法2:SMBus传输函数
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/i2c.h>
#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B
#define DEV_CNT 1
struct mpu6050_drv_info{
int major;
struct class *cls;
struct cdev cdev;
struct i2c_client *client;
};
static struct mpu6050_drv_info mpu6050_drv;
static int mpu6050_open(struct inode *inode, struct file *filp)
{
int ret = 0;
printk("mpu6050_open!\n");
filp->private_data = &mpu6050_drv;
/* 初始化MPU6050 */
ret |= i2c_smbus_write_byte_data(mpu6050_drv.client, PWR_MGMT_1, 0x00);
ret |= i2c_smbus_write_byte_data(mpu6050_drv.client, SMPLRT_DIV, 0X07);
ret |= i2c_smbus_write_byte_data(mpu6050_drv.client, CONFIG, 0x06);
ret |= i2c_smbus_write_byte_data(mpu6050_drv.client, ACCEL_CONFIG, 0x01);
return ret;
}
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t sz, loff_t *loff)
{
int ret;
u8 raw_data[12];
struct mpu6050_drv_info *dev = filp->private_data;
printk("mpu6050_read!\n");
raw_data[0] = i2c_smbus_read_byte_data(dev->client, ACCEL_XOUT_H);
raw_data[1] = i2c_smbus_read_byte_data(dev->client, ACCEL_XOUT_L);
raw_data[2] = i2c_smbus_read_byte_data(dev->client, ACCEL_YOUT_H);
raw_data[3] = i2c_smbus_read_byte_data(dev->client, ACCEL_YOUT_L);
raw_data[4] = i2c_smbus_read_byte_data(dev->client, ACCEL_ZOUT_H);
raw_data[5] = i2c_smbus_read_byte_data(dev->client, ACCEL_ZOUT_L);
raw_data[6] = i2c_smbus_read_byte_data(dev->client, GYRO_XOUT_H);
raw_data[7] = i2c_smbus_read_byte_data(dev->client, GYRO_XOUT_L);
raw_data[8] = i2c_smbus_read_byte_data(dev->client, GYRO_YOUT_H);
raw_data[9] = i2c_smbus_read_byte_data(dev->client, GYRO_YOUT_L);
raw_data[10]= i2c_smbus_read_byte_data(dev->client, GYRO_ZOUT_H);
raw_data[11]= i2c_smbus_read_byte_data(dev->client, GYRO_ZOUT_L);
ret = copy_to_user(buf, raw_data, sz);
if(ret < 0){
printk("copy_to_user error!");
return -1;
}
return 0;
}
static int mpu6050_release(struct inode *inode, struct file *filp)
{
printk("mpu6050_release!\n");
return 0;
}
static struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
dev_t devid;
printk("mpu6050_probe!\n");
/* probe函数记得将I2C设备保存起来 */
mpu6050_drv.client = client;
if(mpu6050_drv.major){
devid = MKDEV(mpu6050_drv.major , 0);
register_chrdev_region(devid, DEV_CNT, "mpu6050");
}
else{
alloc_chrdev_region(&devid, 0, DEV_CNT, "mpu6050");
mpu6050_drv.major = MAJOR(devid);
}
cdev_init(&mpu6050_drv.cdev, &mpu6050_fops);
cdev_add(&mpu6050_drv.cdev, devid, DEV_CNT);
mpu6050_drv.cls = class_create(THIS_MODULE, "mpu6050");
device_create(mpu6050_drv.cls, NULL, MKDEV(mpu6050_drv.major, 0), NULL, "mpu6050");
return 0;
}
static int mpu6050_remove(struct i2c_client *client)
{
printk("mpu6050_remove!\n");
device_destroy(mpu6050_drv.cls, MKDEV(mpu6050_drv.major, 0));
class_destroy(mpu6050_drv.cls);
cdev_del(&mpu6050_drv.cdev);
unregister_chrdev_region(MKDEV(mpu6050_drv.major, 0), DEV_CNT);
return 0;
}
/* 传统不使用设备树的匹配列表 */
static const struct i2c_device_id mpu6050_id[] = {
{
"test,mpu6050", 0},
{
}
};
/* 使用设备树的匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
{
.compatible = "test,mpu6050"},
{
}
};
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.id_table = mpu6050_id,
};
static int __init mpu6050_drv_init(void)
{
printk("mpu6050_init!\n");
i2c_add_driver(&mpu6050_driver);
return 0;
}
static void __exit mpu6050_drv_exit(void)
{
printk("mpu6050_exit!\n");
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");
7、MPU6050测试程序
不管前面驱动程序采用i2c_transfer
还是SMBus
写法,用户空间的应用程序都是一样的:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int ret;
char mpu6050_rawdata[12];
if(argc != 2){
printf("Usage: ./MPU6050APP /dev/xxx\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0){
printf("Open %s error!\n", argv[1]);
return -1;
}
while (1)
{
ret = read(fd, mpu6050_rawdata, 12);
if(ret < 0){
printf("Read %s error!\n", argv[1]);
return -1;
}
printf("Ax: %d Ay: %d Az: %d \n", (mpu6050_rawdata[0] << 8) | mpu6050_rawdata[1],
(mpu6050_rawdata[2] << 8) | mpu6050_rawdata[3],
(mpu6050_rawdata[4] << 8) | mpu6050_rawdata[5]);
printf("Gx: %d Gy: %d Gz: %d \n", (mpu6050_rawdata[6] << 8) | mpu6050_rawdata[7],
(mpu6050_rawdata[8] << 8) | mpu6050_rawdata[9],
(mpu6050_rawdata[10] << 8) | mpu6050_rawdata[11]);
sleep(1);
}
return 0;
}
8、MPU6050测试结果
/drivers # cat /sys/bus/i2c/devices/0-0068/name
mpu6050
/drivers #
/drivers # insmod 19_i2c_mpu6050_dts.ko
mpu6050_init!
mpu6050_probe!
/drivers #
/drivers # ./MPU6050APP /dev/mpu6050
mpu6050_open!
mpu6050_read!
Ax: 0 Ay: 0 Az: 0
Gx: 111 Gy: 3205 Gz: 731
mpu6050_read!
Ax: 49644 Ay: 62874 Az: 456
Gx: 65524 Gy: 65507 Gz: 64
mpu6050_read!
Ax: 49636 Ay: 62876 Az: 438
Gx: 65530 Gy: 65503 Gz: 64
mpu6050_read!
Ax: 49662 Ay: 62874 Az: 436
Gx: 65525 Gy: 65507 Gz: 65
^Cmpu6050_release!
/drivers #