linux I2C总线的使用

1. I2C总线

I2C总线控制器驱动程序不用我们自己编写,nxp已经出厂写好,放在了linux内核中,我们只需要使用即可。
linux驱动模型是:驱动->总线->设备。 此时I2C控制器驱动在这里相当于总线,驱动通过I2C总线控制器来驱动I2C设备。
但是I2C控制器本质上也是一个 driver->bus->device模型,它是一个platform总线驱动,从设备树中获取I2C控制器资源情况,然后和I2C控制器驱动进行匹配运行probe函数,来初始化I2C控制器,然后向上层提供I2C操作函数供给我们利用设备操作。
下图就是I2C设备大概模型:
I2C设备大概模型

(1)I2C控制器数据结构

struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* I2C控制器操作函数就在这个结构体中 */ 
	void *algo_data;
	......
};

这个就是i2c控制器的所有内容的结构体,其中的struct i2c_alagorthm结构体里面是I2C控制器的操作函数。

(2)从设备树从获取的I2C设备信息结构体

在/include/linux/i2c.h的217行左右有struct i2c_client结构体

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];	/* 设备名字*/
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

这个是linux从设备树中获取的I2C设备信息,将保存到这个结构体中,里面有一个struct i2c_adapter结构体,这个就是这个I2C设备所使用的的I2C驱动器的信息。当我们注册I2C设备驱动时,每当有设备和驱动匹配运行probe函数,这个struct i2c_cilent结构体便会作为参数传进去。
此驱动信息继承了父设备结构体struct device dev;关于struct device信息可点击查看。name是client的名字,他来源于设备树中compatible逗号后面的模块名字。

(3)I2C驱动结构体

在/include/linux/i2c.h的第161行左右有struct i2c_driver结构体

struct i2c_driver {
	......
	/* Stand driver model interface*/
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);
	.....
	struct device_driver driver;
	const struct i2c_device_id *id_table;
	......
};

这里面就是需要自己实现的成员变量,probe函数是驱动和设备匹配时自动运行的函数,将设备信息作为第一个参数传进去,这个函数可能会用到,建议将这个设备信息参数保存起来共后面i2c传输函数使用,其内容一般就是注册相关的东西,比如major、cdev、class、device等等。remove是驱动移除时自动调用的函数,一般是将probe注册的东西移除出去。driver是所有驱动都有的结构体,定义了驱动的基本内容,比如name、compatible等等。id_table一般是以前没有采用设备树时候设备与驱动匹配需要用的,一般为了兼容以前的设备,也一般将此变量实现。

(4) I2C发送数据函数消息结构体

在/include/uapi/linux/i2c.h中的71行左右有定义 struct i2c_msg

struct i2c_msg {	 
	__u16 addr;	/* 从机在i2c总线上的物理地址 */
	__u16 flag; /* 一般是发送后者读取标志	*/
	#define I2C_M_TEN	0x0010 
	#define I2c_M_RD	0x0001	/* i2c发送数据标志	*/
	......	/* 此处省略的是各种不认识的标志	*/
	__u16 len;	/* 发送数据的长度 也就是buf的字节数 */
	__u8 *buf;	/* 要发送数据的地址*/
};

flag表示,一般常用的是发送和接收,接收就是上面I2C_M_RD标志 发送的话直接写一个0即可

(5)I2c控制函数

注册和注销I2C设备驱动一般用的函数是:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
owner: 一般为THIS_MODULE
driver: 一般为要注册的i2c_driver

i2c_add_driver(struct i2c_driver *driver)
这个一般也经常用作i2c驱动注册,他是一个宏定义在i2c.h的头文件有定义,其内容是:
#define I2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)
本质上就是对i2c_register_driver函数做一个简单地封装,可以按照上面那个形式调用。

void i2c_del_driver(struct i2c_driver *driver)
driver: 一般就是要注销的i2c_driver

设备数据收发函数:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num)
此函数可以让主机既可以发送数据也可以读取数据
adap: 是i2c控制器结构体,struct i2c_client会保存其对应的i2c_adapter
msg: 是i2c要发送的消息结构体
bum: 是发送消息结构体的数量,一般读取发送两个,写入发送一个 

2. I2C设备驱动编写框架

(1)先定义一个struct i2c_driver驱动结构体

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .name = "ap3216c_driver",
        .owner = THIS_MODULE,
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,
};

一般内容都是如此:
ap3216c_of_match[]是采用设备树形式的设备兼容性

struct of_device_id ap3216c_of_match[] = {
    { .compatible = "cui,ap3216c",  },  
    {   },
};

ap3216c_id[]是采用传统方式匹配驱动的设备兼容性

struct i2c_device_id ap3216c_id[] = {
    {.name = "cui,ap3216c", 0},
    {   },
};

ap3216c_probe 是驱动和设备匹配时自动运行的函数,ap3216c_remove是设备卸载时候自动运行的函数,.driver->name是此驱动在linux系统中此驱动文件的名字。

(2)注册和注销驱动

就是普通的驱动注册函数,调用i2c_add_driver()和i2c_del_driver()函数

static int __init ap3216c_init(void)
{
	return i2c_add_driver(&ad3126c_driver);
}
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);

也可以使用module_i2c_driver()此宏定义来注册

module_i2c_driver(ap3216c_driver);
这是一个宏定义,在i2c.h中628行定义
#define module_i2c_driver(__i2c_driver) \
	module_driver(__i2c_driver, i2c_add_driver, \
			i2c_del_driver)
其中module_driver也是一个宏定义在device.h的1260行中定义
#define module_driver(__driver, __register,__ubregister,...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver), ##__VA_ARGS__); \
}\
module_init(__driver##_init); \
static void __exit __driver##exit(void) \
{ \
	__unregister(&(__driver), ##__VA_ARGS__); \
}\
module_exit(__driver##exit);
将第一行展开就是:
static int __init ap3216c_driver_init(void)
{
	return i2c_add_driver(&ap3216c_driver);
}
module_init(ap3216c_driver_init);
static void __exit ap3216c_driver_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}
module_exit(ap3216c_driver_exit);
功能和上面的手动注册一样,所以手动还是使用宏定义都可以

(3)probe函数和remove函数

在本实验中自定义个一个设备结构体

struct ap3216c_dev {
	dev_t devid;
	int major;
	int minor;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	struct device_node *nd;
	void *private_data;	/* 用于存放client	*/
	unsigned short ps,als,ir;	/* 三个设备值	*/
	spinlock_t spinlock;	/* 自旋锁,用于修改上面三个设备值	*/
};

struct ap3216c_dev *ap3216cdev;	/* 在probe函数中订台分配内存	*/

probe函数:

static int ap3216c_probe(sruct i2c_client *client,
							const struct i2c_device_id *id)
{
	....../* 此处省略的是一般驱动注册入口函数devid cdev class device等等和一系列驱动初始化*/
	ap3216cdev->privete_data = client;	/* 此处将设备信息保存到设备结构体的私有变量中以备后面数据发送接收函数使用*/
}

remove函数:

static int ap3216c_remove(struct i2c_client *client)
{
	....../* 此处省略的是一般probe函数注册后,注销驱动需要注销的东西,比如devid cdev class device等等*/
}

(4)i2c读写函数的使用

在此驱动中的读寄存器函数:

static int ap3216c_read_regs(struct ap3216c_dev *dev,u8 reg,
								void *buf, int len)
{
	int ret = 0;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	struct i2c_msg msg[2];
	
	/* 第一个msg是写要读取的寄存器地址	*/
	msg[0].addr = cliebt->addr;	/* i2c设备在i2c总线上的物理地址	*/
	msg[0].flags = 0;	/* 表示发送数据	*/
	msg[0].buf = ®	/* 发送的淑女内容,也就是寄存器地址	*/
	msg[0].len = 1;	/* 发送的数据大小 单位Byte*/

	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;	/* 表示向从机写数据	*/
	msg[1].buf = buf;
	msg[1].len = len;

	ret = i2c_transfer(client->adapter, msg, 2);
	if (ret == 2) {
		return 0;
	}
	else {
		dev_dbg(&client->dev,"i2c read failed = %d reg = %#6x len = %d\r\n",
			ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

在此驱动中写寄存器数据:

static int ap3216c_write_regs(struct ap3216c_dev *dev,u8 reg,
								u8 *buf, int len)
{
	int ret = 0;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	struct i2c_msg msg;
	u8 *b;	/* 用来接收buf中的数据	*/

	b = kmalloc(len + 1, GFP_KERNEL);	/* 申请len+1长度是因为寄存器地址在最前面占一个字节	*/
	if (b == NULL) {
		ret = -ENOMEM;
		dev_dbg(&cilent->dev, 
					"ap3216c_write_regs: kmalloc sendbuf failed!\r\n");
		goto fail_nomem;
	}
	else {
		b[0] = reg;	/* 将寄存器地址存放到第一个字节	*/
		memset(&b[1], 0, len);
		memcpy(&b[1], buf, len);
	}

	msg.addr = client->addr;
	msg.flags = 0;
	msg.buf = b;
	msg.len = len + 1;
	ret = i2c_transfer(client->adapter, &msg, 1);
	if  (ret != 1) {
		dev_dbg(&client->dev,
			"ap3216c_write_flags: i2c_transfer failed :%d\r\n", ret);
		goto fail_transder; 
	}

	kfree(b);
	return 0;

fail_transfer:
	kfree(b);
fail_nomem:
	return ret;
}

(5)编写具体的i2c设备驱动

根据对上面的i2c操作函数的封装,进行调用,编写具体的驱动函数

发布了15 篇原创文章 · 获赞 7 · 访问量 273

猜你喜欢

转载自blog.csdn.net/weixin_42397613/article/details/104836626