Linux驱动:I2C设备驱动(基于Freescale i.MX6ULL平台了解I2C的驱动框架,顺便写个简陋的MPU6050驱动)

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_algorithmmaster_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_driveri2c_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_transferSMBus两种方式来编写驱动程序。

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 # 

猜你喜欢

转载自blog.csdn.net/weixin_44498318/article/details/110622768
今日推荐