IIC介绍及驱动编写

1、IIC介绍

IIC是通信协议中的一种,为一主多从的结构,对于主从,所有的数据都是从主机这边发起,从机只能接受,不能主动引起数据传输。它只有两条总线线路:一条串行数据线(SDA),一条串行时钟线(SCL),IIC有硬件IIC和软件IIC,这里简单解释,硬件IIC为硬件构成的IIC,一般只需要操作相关寄存器即可,对于软件IIC,可以由IO口来模拟IIC总线进行对IIC设备的通信。

对于多个IIC设备来说,每个连接到总线的器件都可以使用软件根据它的唯一地址来识别,当我们要使用一个IIC设备时,主机通过SDA线向各IIC设备寻址,等待地址对应的IIC设备的回应即ACK信号。

IIC是半双工的,即SDA总线是双向传输的。两条线在硬件上都使用了开极电路,且都接有上拉电阻(即空闲时,SDA和SCL都为高电平)。

image-20210812203900555

  • 传输过程:

当主机A要发送数据时,A拉低SDA总线,稍后向SCL输送时钟信号,开始传输数据(在每个时钟周期中,当SCL为高电平时,从机从SDA线上采样(获得)数据;当SCL为低电平时,主机向SDA传送(更新)数据)。主机每传完8个比特位,就将SDA释放(SDA恢复到高电平),若从机正常接收完8个比特位,就将SDA线拉低,以表示向主机回送一个ACK信号,若没有接收到8个比特位,就不会拉低SDA线,主机就收不到ACK信号,所以主机会发送一个终止或开始(重传)信号给从机。当主机已经完成数据传输时,会先释放SCL线,然后再释放SDA线。

image-20210812211915033

并非每传输8位数据之后,都会有ACK信号,有以下3种例外:

  1. 当从机不能响应从机地址时(例如它正忙于其他事而无法响应I²总线的操作,或者这个地址没有对应的从机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。这时,主机发出一个Р信号终止传输或者重新发出一个S信号开始新的传输.
  2. 如果从机接收器在传输过程中不能接收更多的数据时,它也不会发出ACK信号。这样,主机就可以意识到这点,从而发出一个Р信号终止传输或者重新发出一个S信号开始新的传输.
  3. 主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出Р信号结束传输
  • 使用IIC协议的芯片介绍—AT24C02/04/08/16

AT24Cxx系列芯片是采用IIC协议的EEPROM芯片,其读写过程如下:

写过程:主机先向芯片发出设备地址,再发出写地址后就可以开始传输数据;

读过程:主机先向芯片发出设备地址,再发出读地址,然后需要再发出一次设备地址,才能读数据,是因为AT24Cxx容量各有不同,有1K,2K,4K,8K,16K等大小,对于4K芯片来说其架构为512*8bit,而512为2的9次方,因此只传输一次读地址是不够的。

image-20210812213009051

2、IIC驱动框架

I²C驱动框架:

App:open()、 read() 、write()
IIC设备驱动程序:drv_open()、 drv_read() 、drv_write() 知道数据含义
IIC总线驱动程序:1.识别设备; 2.提供读写函数 。 知道怎么收发数据
IIC硬件:如AT24C02/AT24C08
  • I 2C 体系结构:i2c核心 + i2总线驱动(适配器) + i2c设备驱动

2.1 I2C核心

I 2C 核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,这 个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为 I 2C 总线 驱动和设备驱动之间依赖于 I 2C 核心作为纽带。I 2C 核心中的主要函数如下:

//1. 增加/删除 i2c_adapter。
int i2c_add_adapter(struct i2c_adapter *adap); 
int i2c_del_adapter(struct i2c_adapter *adap); 

//2. 增加/删除 i2c_driver。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver); 
int i2c_del_driver(struct i2c_driver *driver); 
inline int i2c_add_driver(struct i2c_driver *driver); 

//3. i2c_client 依附/脱离。当一个client被侦测到并被关联的时候,设备和sysfs文件将被注册
int i2c_attach_client(struct i2c_client *client); //
int i2c_detach_client(struct i2c_client *client); 

/* 4. I2C 传输、发送和接收。i2c_master_send()函数和 i2c_master_recv()函数
 *    内部会调用 i2c_transfer()函数分别完成一条写消息和一条读消息;
 *    但i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻
 *    找到i2c_adapter对应的i2c_algorithm,并使用它的master_xfer()函数真正驱动硬件流程。
 */
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num); 
int i2c_master_send(struct i2c_client *client,const char *buf ,int count); 
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

/* 5. I2C 控制命令分配.将发给I2C适配器设备文件ioctl的命令分配给对应适配器的
 * algorithm的algo_control()函数或i2c_driver的command()函数.
 */
int i2c_control(struct i2c_client *client, unsigned int cmd, unsigned long arg); 
void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg);

2.2 I2C总线(适配器)驱动

I2C 总线驱动模块的加载函数要完成两个工作:

  1. 初始化 I2C 适配器所使用的硬件资源,如申请 I/O 地址、中断号等。
  2. 通过 i2c_add_adapter()添加 i2c_adapter 的数据结构,当然这个 i2c_adapter 数 据结构的成员已经被 xxx 适配器的相应函数指针所初始化。

I2C 总线驱动模块的卸载函数要完成的工作:

  1. 释放 I2C 适配器所使用的硬件资源,如释放 I/O 地址、中断号等。
  2. 通过 i2c_del_adapter()删除 i2c_adapter 的数据结构。

I2C 总线驱动的模块加载和卸载函数模板:

static int _ _init i2c_adapter_xxx_init(void) 
{ 
	xxx_adpater_hw_init(); 
	i2c_add_adapter(&xxx_adapter); 
} 
 
static void _ _exit i2c_adapter_xxx_exit(void) 
{ 
	xxx_adpater_hw_free(); 
	i2c_del_adapter(&xxx_adapter); 
} 

I2C 总线通信方法:

我们需要为特定的 I 2C 适配器实现 i2c_algorithm 的 master_xfer()函数和 functionality()函数。functionality()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、 I2C_FUNC_10BIT_ADDR 、 I2C_FUNC_SMBUS_READ_BYTE 、 I2C_FUNC_SMBUS_WRITE_BYTE 等。而master_xfer()函数在I 2C适配器上完成传递给它的i2c_msg数组中的每个I 2C消息。

master_xfer()函数的消息处理时序流程及代码如下图所示(master_xfer()函数的实现在形式上会很多样,有顺序的也有中断的,但不管具体怎么实施,流程的本质都是不变的):

image-20220116201520162

static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 
{ 
	... 
	for (i = 0; i < num; i++) 
	{ 
		i2c_adapter_xxx_start(); /*产生开始位*/ 
		/*如果是读消息*/ 
		if (msgs[i]->flags&I2C_M_RD) 
		{ 
			i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*发送从设备读地址*/ 
			i2c_adapter_xxx_wait_ack(); /*获得从设备的 ack*/ 
            /*读取msgs[i]->len 长的数据到msgs[i]->buf */ 
			i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); 
        } 
		else/*如果是写消息*/ 
		{ 
			i2c_adapter_xxx_setaddr(msg->addr << 1); /*发送从设备写地址*/ 
			i2c_adapter_xxx_wait_ack(); /*获得从设备的 ack*/ 
            /*读取 msgs[i] ->len长的数据到 msgs[i]->buf*/ 
			i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); 
		} 
	} 
	i2c_adapter_xxx_stop(); /*产生停止位*/ 
} 

master_xfer()函数模板中的 i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、 i2c_adapter_ xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes() 和 i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与 I 2C 适配器和 CPU 的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。

多数 I 2C 总线驱动会定义一个 xxx_i2c 结构体,作为 i2c_adapter 的 algo_data (类似“私有数据”):

struct xxx_i2c 
{ 
	spinlock_t lock; 
	wait_queue_head_t wait; 
	struct i2c_msg *msg; 
	unsigned int msg_num; 
	unsigned int msg_idx; 
	unsigned int msg_ptr; 
	... 
	struct i2c_adapter adap; 
}; 

2.3 I2C设备驱动

I2C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数。 i2c_client 一般被包含在设备的私有信息结构体 yyy_data 中,而 i2c_driver 则适合被定义为全局变量并初始化:

 static struct i2c_driver yyy_driver = 
{ 
	.driver = 
	{ 
		.name = "yyy", 
	} , 
	.attach_adapter = yyy_attach_adapter, 
	.detach_client = yyy_detach_client, 
	.command = yyy_command, 
}; 

I2C 设备驱动的模块加载流程图:

image-20220116214119277

I2C 设备驱动的模块 卸载流程图:

image-20220116214311894

i2c_driver成员函数介绍:

  • yyy_attach_adapter():

i2c_add_driver(&yyy_driver)的执行会引发i2c_driver 结构体中yyy_attach_adapter() 函数的执行,我们可以在 yyy_attach_adapter()函数里探测物理设备。为了实现探测, yyy_attach_adapter()函数里面也只需简单地调用 I2C 核心的 i2c_probe()函数:

static int yyy_attach_adapter(struct i2c_adapter *adapter) 
{ 
	return i2c_probe(adapter, &addr_data, yyy_detect); 
} 

第 1 个参数是 i2c_adapter 指针,第 2 个参数 是要探测的地址数据,第 3 个参数是具体的探测函数(即发现设备后要调用的函数)。要探测的地址实际列表在一个 16 位无符号整型数组中,这个数组以 I2C_CLIENT_END 为最后一个元素。i2c_probe()函数会引发 yyy_detect()函数的调用,可以在 yyy_detect()函数中初始化 i2c_client,其模板如下:

static int yyy_detect(struct i2c_adapter *adapter, int address, int kind) 
{ 
	struct i2c_client *new_client; 
	struct yyy_data *data; 
	int err = 0; 
 
	if (!i2c_check_functionality(adapter, I2C_FUNC_XXX) 
		goto exit; 
 
	if (!(data = kzalloc(sizeof(struct yyy_data), GFP_KERNEL))) 
	{ 
		err = - ENOMEM; 
		goto exit; 
	} 
 
	new_client = &data->client; 
	new_client->addr = address; 
	new_client->adapter = adapter; 
	new_client->driver = &yyy_driver; 
	new_client->flags = 0; 
 
	/* 新的 client 将依附于 adapter */ 
	if ((err = i2c_attach_client(new_client))) 
		goto exit_kfree; 
 
	yyy_init_client(new_client); //初始化 i2c_client 对应的I2C设备
	return 0; 
exit_kfree: kfree(data); 
exit: return err; 
} 
  • I2C 设备驱动的 yyy_detach_client 函数模板如下:
static int yyy_detach_client(struct i2c_client *client) 
{ 
	int err; 
	struct yyy_data *data = i2c_get_clientdata(client); 
 
	if ((err = i2c_detach_client(client))) 
		return err; 
 
	kfree(data); 
	return 0; 
} 
  • yyy_command():

yyy_command()的实现是具体设备特异的。如对于实时钟而言,命令将是设置时间和获 取时间,而对于视频 AD 设备而言,命令会是设置采样方式、选择通道等。具体命令的实现是通过组件 i2c_msg 消息数组,并调用 I 2C 核心的传输、发送和 接收函数,由 I 2C 核心的传输、发送和接收函数调用 I 2C 适配器对应的 algorithm 相关 函数来完成的。

static int yyy_command(struct i2c_client *client, unsigned int cmd, void *arg) 
{ 
	switch (cmd) 
	{ 
		case YYY_CMD1: 
			return yyy_cmd1(client, arg); 
		case YYY_CMD2: 	
            return yyy_cmd2(client, arg); 
	default: 	
            return - EINVAL; 
	} 
} 

static int yyy_cmd1(struct i2c_client *client, struct rtc_time *dt) 
{ 
	struct i2c_msg msg[2]; 
	/*第一条消息是写消息*/ 
	msg[0].addr = client->addr; 
	msg[0].flags = 0; 
	msg[0].len = 1; 
	msg[0].buf = &offs; 
	/*第二条消息是读消息*/ 
	msg[1].addr = client->addr; 
	msg[1].flags = I2C_M_RD; 
	msg[1].len = sizeof(buf); 
	msg[1].buf = &buf[0]; 
 
	i2c_transfer(client->adapter, msg, 2); 
	... 
} 

设备驱动的文件操作接口:

作为一种字符类设备,Linux I2C 设备驱动的文件操作接口与普通的设备驱动是完 全一致的,但是在其中要使用 i2c_client、i2c_driver、i2c_adapter 和 i2c_algorithm 结构 体和 I 2C 核心,并且对设备的读写和控制需要借助体系结构中各组成部分的协同合作。通常,如果 I2C 设备不是一个输入/输出 设备或存储设备,就并不需要给 I 2C 设备提 供读写函数。许多 I 2C 设备只是需要被设置以某种方式工作,而不是被读写。另外,I 2C 设备驱动的文件操作接口也不是必需的, 甚至极少被需要。大多数情况下,我们只需要通过 i2c-dev.c 文件提供的 I 2C 适配器设 备文件接口(i2cdev_read()和i2cdev_write())就可完成对 I 2C 设备的读写。

I2C 设备的写操作经历 了如下几个步骤:

  1. 从用户空间到字符设备驱动写函数接口,写函数构造 I 2C 消息数组;
  2. 写函数把构造的 I 2C 消息数组传递给 I 2C 核心的传输函数 i2c_transfer();
  3. I2C 核心的传输函数 i2c_transfer()找到对应适配器的 algorithm 通信方法函数 master_xfer()去最终完成 I2C 消息 的处理。
  • I2C 设备读写完整流程:用户空间的read()/write() –> I2C设备驱动文件操作接口xxx_read()/xxx_write() –> IIC核心的i2c_transfer() –> I2C总线驱动master_xfer()。

**但遗憾的是:**大多数稍微复杂一点 I 2C 设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次 读写周期(如下图 所示的重复开始位 RepStart 模式),这种情况下,在应用层仍 然调用 read()、write()文件 API 来读写 I 2C 设备,将不能正确地读写。

image-20220116223016006

鉴于上述原因,i2cdev_read()和 i2cdev_write()函数只能适用于非 RepStart 模式的情况。对于两条以上消息组 成的读写,在用户空间需要组织 i2c_msg 消息数组并调用 I2C_RDWR IOCTL 命令。常用的 IOCTL 包括 I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设 备 ACK 情况下的重试次数,默认为 1)、I2C_TIMEOU(超时)以及 I2C_RDWR。 应用程序需要通过 i2c_rdwr_ioctl_data 结构体来给内核传递I2C 消息,这个结构体定义如下所示,i2c_msg 数组指针及消息数量 就被包含在 i2c_rdwr_ ioctl_data 结构体中。

struct i2c_rdwr_ioctl_data { 
	struct i2c_msg _ _user *msgs; 	/* I2C 消息指针 */ 
	_ _u32 nmsgs; 					/* I2C 消息数量 */ 
}; 

通过 O_RDWR IOCTL 读写 I 2C 设备:

#include <stdio.h> 
#include <linux/types.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/ioctl.h> 
#include <errno.h> 
#include <assert.h> 
 #include <string.h> 
 
#define MAX_I2C_MSG 2 
 
#define I2C_RETRIES 0x0701 
#define I2C_TIMEOUT 0x0702 
#define I2C_RDWR    0x0707 

struct i2c_msg 
{ 
	_ _u16 addr; /* 从地址 */ 
	_ _u16 flags; 
	#define I2C_M_RD 0x01 
	_ _u8 *buf; /* 消息数据指针 */ 
}; 

struct i2c_rdwr_ioctl_data 
{ 
	struct i2c_msg *msgs; 	/* i2c_msg[]指针 */ 
	int nmsgs; 				/* i2c_msg 数量 */ 
}; 
 
int main(int argc, char **argv) 
{ 
	struct i2c_rdwr_ioctl_data work_queue; 
	unsigned int idx; 
	unsigned int fd; 
	unsigned short start_address; 
	int ret; 

	if (argc < 4) 
	{ 
		printf("Usage:\n%s /dev/i2c-x start_addr\n", argv[0]); 
		return 0; 
	} 
 
	fd = open(argv[1], O_RDWR); 
 
	if (!fd) 
	{ 
		printf("Error on opening the device file\n"); 
		return 0; 
	} 
	sscanf(argv[2], "%x", &start_address); 
	work_queue.nmsgs = MAX_I2C_MSG; /* 消息数量 */ 
 
	work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct i2c_msg)); 
	if (!work_queue.msgs) 
    { 
		printf("Memory alloc error\n"); 
		close(fd); 
		return 0; 
	} 
 
	for (idx = 0; idx < work_queue.nmsgs; ++idx) 
	{ 
		(work_queue.msgs[idx]).len = 0; 
		(work_queue.msgs[idx]).addr = start_address + idx; 
		(work_queue.msgs[idx]).buf = NULL; 
	} 
 
	ioctl(fd, I2C_TIMEOUT, 2); /* 设置超时 */ 
	ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */ 
 
	ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue); 
 
	if (ret < 0)
	{ 
		printf("Error during I2C_RDWR ioctl with error code: %d\n", ret); 
	} 
 
	close(fd); 
	return ; 
} 

设备驱动的模块加载函数:

  1. 通过 register_chrdev()函数将 I 2C 设备注册为一个字符设备;
  2. 通过 I 2C 核心的 i2c_add_driver()函数添加 i2c_driver。

设备驱动的模块卸载函数:

  1. 通过 I 2C 核心的 i2c_del_driver()函数删除 i2c_driver;
  2. 通过 unregister_chrdev()函数注销字符设备。

示例代码:

static int _ _init yyy_init(void) 
{ 
	int ret; 
	/*注册字符设备*/ 
	ret = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老内核接口
	if (ret) 
		goto out; 
	/*添加 i2c_driver*/ 
	ret = i2c_add_driver(&yyy_driver); 
	if (ret) 
		goto out_unreg_class; 
	return 0; 
 
out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c"); 
out: printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__); 
	return ret; 
	} 
 
static void _ _exit yyy_exit(void) 
{ 
	i2c_del_driver(&i2cdev_driver); 
	unregister_chrdev(YYY_MAJOR, "yyy"); 
} 

3、自己编写IIC驱动程序(Linux2.6)

当工程师拿到实际的电路板,面对复杂的 Linux I2C 子系统, 应该如何下手写驱动呢?一方面,适配器驱动可能是 Linux 内核本身还不包含的;另一方面,挂接在适配 器上的具体设备驱动可能也是 Linux 内核还不包含的。即便上述设备驱动都存在于 Linux 内核中,其基于的平台也可能与我们的电路板不一样。因此,工程师要实现的 主要工作如下:

  1. 提供 I 2C 适配器的硬件驱动,探测、初始化 I 2C 适配器(如申请 I 2C 的 I/O 地 址和中断号)、驱动 CPU 控制的 I 2C 适配器从硬件上产生各种信号以及处理 I 2C 中断等。
  2. 提供 I 2C 适配器的 algorithm,用 具体适配器 的 xxx_xfer() 函数填充 i2c_algorithm 的 master_xfer 指针,并把 i2c_algorithm 指针赋值给 i2c_adapter 的 algo 指针。
  3. 实现 I 2C 设备驱动与 i2c_driver 接口,用具体设备 yyy 的 yyy_attach_adapter() 函数指针、yyy_detach_client()函数指针和 yyy_command()函数指针的赋值给 i2c_driver 的 attach_ adapter、detach_adapter 和 detach_client 指针。
  4. 实现 I2C 设备驱动的文件操作接口,即实现具体设备 yyy 的 yyy_read()、 yyy_write()和 yyy_ioctl()函数等。

## 3.1基本框架

以I2C设备(EEPROM芯片)AT24CXX为例,编写其驱动程序的基本步骤如下:

  • 1 分配一个i2c_driver结构体
  • 2 设置
    • attach_adapter // 它直接调用 i2c_probe(adap, 设备地址, 发现这个设备后要调用的函数);
    • detach_client // 卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理
  • 3 注册:i2c_add_driver

参考设备m41t00.c中设备地址怎么写(由AT24CXX的数据手册查得其地址为0xA0),在normal_i2c参数中地址值只需要7位,若识别设备先加入打印语句测试:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
 
static unsigned short ignore[]      = { I2C_CLIENT_END };
/* 查看I2C设备的datasheet知道设备地址是0xA0,取地址值的高7位,即为(0xA0 >> 1) = 0x50 */
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; 
 

static struct i2c_client_address_data addr_data = {
	.normal_i2c	= normal_addr, 
	.probe		= ignore,
	.ignore		= ignore,
};
 
 /* 要发出S信号和设备地址并得到ACK信号,才会调用该函数,否则认为地址非法。此处为调试方便先加打印语句 */
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
	printk("at24cxx_detected.\n");
	return 0;
}
 
static int at24cxx_attach(struct i2c_adapter *adapter)
{
    //调用at24cxx_detect函数查找地址数组addr_data中的I2C设备
	return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
 
static int at24cxx_detach(struct i2c_client *client)
{
	printk("at24cxx_detached.\n");
	return 0;
}
 
 
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
	.driver = {
		.name	= "at24cxx",
	},
	.attach_adapter = at24cxx_attach,
	.detach_client  = at24cxx_detach,
};
 
static int at24cxx_init(void)
{
	i2c_add_driver(&at24cxx_driver);
	return 0;
}
 
static void at24cxx_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");

编译加载驱动,若出现以下打印语句,则表示框架正确!

# insmod at24cxx.ko

at24cxx_detected.

3.2 强制发现IIC设备

当设备还未发现后面才发现,我们可以强制发现设备,添加forces属性,参考i2c_probe函数,ANY_I2C_BUS为任何i2c总线,0x60是还没有接入到开发板的I2C设备的地址,修改addr_data:

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
										
static struct i2c_client_address_data addr_data = {
	.normal_i2c	= ignore,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
	.probe		= ignore,
	.ignore		= ignore,
	.forces     = forces, /* 强制认为存在这个设备 */
};

编译测试,得到一样的情况:

# insmod at24cxx.ko
at24cxx_detected.

# rmmod at24cxx

3.3 添加client(设备)

上面在卸载IIC驱动的时候,没有打印出”at24cxx_detached.”。参考eeprom.c得知,需要发现设备后,在detect函数中分配/设置/注册client结构体,收发数据时会用到。

所以现在修改调用函数at24cxx_detect,并且在at24cxx_detach中卸载client,设备地址重新改回0x50:

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };
static struct i2c_driver at24cxx_driver; 	//声明一下

static struct i2c_client_address_data addr_data = {
    .normal_i2c    = normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
    .probe        = ignore,
    .ignore        = ignore,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
    struct i2c_client *new_client;
    
    printk("at24cxx_detected.\n");

    /* 构造一个i2c_client结构体: 以后收发数据时会用到它 */
    new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
    new_client->addr    = address;
    new_client->adapter = adapter;
    new_client->driver  = &at24cxx_driver;
    strcpy(new_client->name, "at24cxx");
    
    i2c_attach_client(new_client); 
    return 0;
}

static int at24cxx_detach(struct i2c_client *client)
{
    printk("at24cxx_detached.\n");
    i2c_detach_client(client);
    kfree(i2c_get_clientdata(client));

    return 0;
}

编译测试,卸载驱动打印出信息

# insmod at24cxx.ko
at24cxx_detect
# rmmod at24cxx
at24cxx_detach

3.4 完善驱动程序

  • 在调用函数at24cxx_detect()中注册字符设备,修改at24cxx_detect函数,在at24cxx_detach函数中卸载,并添加相应头文件:
#include <linux/fs.h>

static int major;

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
    return 0;
}

static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    return 0;
}


static struct file_operations at24cxx_fops = {
    .owner = THIS_MODULE,
    .read  = at24cxx_read,
    .write = at24cxx_write,
};

static struct class *cls;
struct i2c_client *at24cxx_client;  //修改为全局变量

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{    
    printk("at24cxx_detected.\n");

    /* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
    at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
    at24cxx_client->addr    = address;
    at24cxx_client->adapter = adapter;
    at24cxx_client->driver  = &at24cxx_driver;
    strcpy(at24cxx_client->name, "at24cxx");    
    i2c_attach_client(at24cxx_client);
    
    major = register_chrdev(0, "at24cxx", &at24cxx_fops);
    cls = class_create(THIS_MODULE, "at24cxx");
    class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
    return 0;
}

static int at24cxx_detach(struct i2c_client *client)
{
    printk("at24cxx_detached.\n");
    class_device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, "at24cxx");

    i2c_detach_client(client);
    kfree(i2c_get_clientdata(client));

    return 0;
}
  • 利用i2c_transfer进行传输,i2c_transfer参数一位client中的适配器,以i2c_msg结构体传输,在i2c_msg结构体中含有数据传输三要素,其中addr为设备地址,完整代码如下:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
 
static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
                                        /* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */
 
//static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
//static unsigned short * forces[] = {force_addr, NULL};
										
static struct i2c_client_address_data addr_data = {
	.normal_i2c	= normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
	.probe		= ignore,
	.ignore		= ignore,
	//.forces     = forces, /* 强制认为存在这个设备 */
};
 
static struct i2c_driver at24cxx_driver;
 
static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;
 
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
	unsigned char address;
	unsigned char data;
	struct i2c_msg msg[2];
	int ret;
	
	/* address = buf[0] 
	 * data    = buf[1]
	 */
	if (size != 1)
		return -EINVAL;
	
	copy_from_user(&address, buf, 1);
 
	/* 数据传输三要素: 源,目的,长度 */
 
	/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
	msg[0].addr  = at24cxx_client->addr;  /* 目的 */
	msg[0].buf   = &address;              /* 源 */
	msg[0].len   = 1;                     /* 地址=1 byte */
	msg[0].flags = 0;                     /* 表示写 */
 
	/* 然后启动读操作 */
	msg[1].addr  = at24cxx_client->addr;  /* 源 */
	msg[1].buf   = &data;                 /* 目的 */
	msg[1].len   = 1;                     /* 数据=1 byte */
	msg[1].flags = I2C_M_RD;              /* 表示读 */
 
 
	ret = i2c_transfer(at24cxx_client->adapter, msg, 2);/* 2代表2个数组项 */
	if (ret == 2)
	{
		copy_to_user(buf, &data, 1);
		return 1;
	}
	else
		return -EIO;
}
 
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned char val[2];
	struct i2c_msg msg[1];
	int ret;
	
	/* address = buf[0] 
	 * data    = buf[1]
	 */
	if (size != 2)
		return -EINVAL;
	
	copy_from_user(val, buf, 2);
 
	/* 数据传输三要素: 源,目的,长度 */
	msg[0].addr  = at24cxx_client->addr;  /* 目的(从设备地址) */
	msg[0].buf   = val;                   /* 源 */
	msg[0].len   = 2;                     /* 地址+数据=2 byte */
	msg[0].flags = 0;                     /* 表示写 */
 
	ret = i2c_transfer(at24cxx_client->adapter, msg, 1);/* 1代表一个数组项 */
	if (ret == 1) /* msg一项,返回1表示成功 */
		return 2;
	else
		return -EIO;
}
 
 
static struct file_operations at24cxx_fops = {
	.owner = THIS_MODULE,
	.read  = at24cxx_read,
	.write = at24cxx_write,
};
 
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{	
	printk("at24cxx_detect\n");
 
	/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
	at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	at24cxx_client->addr    = address;
	at24cxx_client->adapter = adapter;
	at24cxx_client->driver  = &at24cxx_driver;
	strcpy(at24cxx_client->name, "at24cxx");
	i2c_attach_client(at24cxx_client);
	
	major = register_chrdev(0, "at24cxx", &at24cxx_fops);
 
	cls = class_create(THIS_MODULE, "at24cxx");
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
	
	return 0;
}
 
static int at24cxx_attach(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
 
static int at24cxx_detach(struct i2c_client *client)
{
	printk("at24cxx_detach\n");
	class_device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, "at24cxx");
 
	i2c_detach_client(client);
	kfree(i2c_get_clientdata(client));
 
	return 0;
}
 
 
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
	.driver = {
		.name	= "at24cxx",
	},
	.attach_adapter = at24cxx_attach,
	.detach_client  = at24cxx_detach,
};
 
static int at24cxx_init(void)
{
	i2c_add_driver(&at24cxx_driver);
	return 0;
}
 
static void at24cxx_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");

3.5 测试程序

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* i2c_test r addr
 * i2c_test w addr val
 */
 
void print_usage(char *file)
{
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/at24cxx\n");
		return -1;
	}
 
	if (strcmp(argv[1], "r") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0); //把字符串转化为整型
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if (strcmp(argv[1], "w") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		write(fd, buf, 2);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

4、Linux3.4.2下的IIC驱动框架

应用层open()write()、…
设备驱动层 drv.cxxx_open()xxx_write()
核心层 platform_bus_type .match提供统一的I2C操作函数提供统一的I2C操作函数
总线驱动层(适配器) dev.c具体I2C设备的硬件操作具体I2C设备的硬件操作
  • 这三层通过总线设备驱动模型联系在一块,在内核中有很多虚拟的总线例如platfoem_bus_type,对于IIC来说是i2c_bus_type总线,在这条总线中有i2c_clienti2c_driver两条链表,当i2c_client加到链表中后,会先跟i2c_driver链表的每一项比较,如果能匹配的话就调用相对应的i2c_driver的probe函数;对于i2c_driver加到链表中后,一样会先跟i2c_client链表的每一项比较,如果能匹配的话就调用i2c_driver的probe函数。匹配是调用i2c_bus_type总线中的match函数进行比较,其中比较的是id_table,而在id_table中比较的是两者链表中的name,因此简单描述如下:

image-20210813194017090

  1. 左边注册一个设备:i2c_client

  2. 右边注册一个驱动:i2c_driver

  3. 比较他们的名字,如果相同,则调用probe函数

  4. probe函数里,可以注册字符设备驱动

在i2c_bus_type总线中,dev链表不仅有i2c_client还有i2c_adapter,后面再解释。

  • 需要理解bus-drv-dev模型的含义,然后知道设备的4种构建方法(在Linux3.4.2内核目录中有个文档Documentation\i2c\instantiating-devices里面介绍了四种方法怎么去构造IIC设备)。

首先,需要i2c_bus_type总线模型的左边,注册并且设置一个i2c_client:总共有4种方法

4.1 方法一:Declare the I2C devices by bus number

Method 1: Declare the I2C devices by bus number
-----------------------------------------------
    
This method is appropriate when the I2C bus is a system bus as is the case
for many embedded systems. On such systems, each I2C bus has a number
which is known in advance. It is thus possible to pre-declare the I2C
devices which live on this bus. This is done with an array of struct
i2c_board_info which is registered by calling i2c_register_board_info().
    
Example (from omap2 h4):

static struct i2c_board_info __initdata h4_i2c_board_info[] = {
	{
		I2C_BOARD_INFO("isp1301_omap", 0x2d),
		.irq		= OMAP_GPIO_IRQ(125),
	},
	{	/* EEPROM on mainboard */
		I2C_BOARD_INFO("24c01", 0x52),
		.platform_data	= &m24c01,
	},
	{	/* EEPROM on cpu card */
		I2C_BOARD_INFO("24c01", 0x57),
		.platform_data	= &m24c01,
	},
};

static void __init omap_h4_init(void)
{
	(...)
	i2c_register_board_info(1, h4_i2c_board_info,
			ARRAY_SIZE(h4_i2c_board_info));
	(...)
}

The above code declares 3 devices on I2C bus 1, including their respective
addresses and custom data needed by their drivers. When the I2C bus in
question is registered, the I2C devices will be instantiated automatically
by i2c-core.

The devices will be automatically unbound and destroyed when the I2C bus
they sit on goes away (if ever.)
  • 该方法通过总线number来声明

    • 首先定义一个i2c_board_info结构体(结构体中包含有名字和设备地址);

    • 然后,根据i2c_board_info结构体构造一个i2c_client。即在入口函数中通过i2c_register_board_info(busnum, ...)函数注册设备,其中的list_add_tail()函数会把它们放入到__i2c_board_list链表中去,其函数定义如下:

    • int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
      {
      	int status;
       
      	down_write(&__i2c_board_lock);
       
      	/* dynamic bus numbers will be assigned after the last static one */
      	if (busnum >= __i2c_first_dynamic_bus_num)
      		__i2c_first_dynamic_bus_num = busnum + 1;
       
      	for (status = 0; len; len--, info++) {
      		struct i2c_devinfo	*devinfo;
       
      		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
      		if (!devinfo) {
      			pr_debug("i2c-core: can't register boardinfo!\n");
      			status = -ENOMEM;
      			break;
      		}
       
      		devinfo->busnum = busnum;
      		devinfo->board_info = *info;
      		list_add_tail(&devinfo->list, &__i2c_board_list);   //把它们放入i2c_board_list链表中去
      	}
       
      	up_write(&__i2c_board_lock);
       
      	return status;
      }
      
    • 搜索__i2c_board_list链表,在drivers\i2c\i2c-core.c中,在list_for_each_entry函数中链表被用到:(i2c_register_adapter –> i2c_scan_static_board_info –> i2c_new_device)。

    • 对链表的每一个成员都调用一个i2c_new_device,通过i2c_new_device构造一个i2c_client放到链表中去。

    • static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
      {
      	struct i2c_devinfo	*devinfo;
       
      	down_read(&__i2c_board_lock);
      	list_for_each_entry(devinfo, &__i2c_board_list, list) {  
      		if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter,&devinfo->board_info))
      			dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr);
      	}
      	up_read(&__i2c_board_lock);
      }
      
  • 使用此方法的前提使用前,必须在注册适配器(i2c_register_adapter)之前 i2c_scan_static_board_info,所以必须事先知道开发板上已经有哪些I2C设备了,故不适合动态加载(insmod)I2C模块驱动。

4.2 方法二:Instantiate the devices explicitly

Method 2: Instantiate the devices explicitly(精确创建I2C设备)
--------------------------------------------

This method is appropriate when a larger device uses an I2C bus for
internal communication. A typical case is TV adapters. These can have a
tuner, a video decoder, an audio decoder, etc. usually connected to the
main chip by the means of an I2C bus. You won't know the number of the I2C
bus in advance, so the method 1 described above can't be used. Instead,
you can instantiate your I2C devices explicitly. This is done by filling
a struct i2c_board_info and calling i2c_new_device().

Example (from the sfe4001 network driver):

static struct i2c_board_info sfe4001_hwmon_info = {
	I2C_BOARD_INFO("max6647", 0x4e),
};

int sfe4001_init(struct efx_nic *efx)
{
	(...)
	efx->board_info.hwmon_client =
		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);

	(...)
}

The above code instantiates 1 I2C device on the I2C bus which is on the
network adapter in question.

A variant of this is when you don't know for sure if an I2C device is
present or not (for example for an optional feature which is not present
on cheap variants of a board but you have no way to tell them apart), or
it may have different addresses from one board to the next (manufacturer
changing its design without notice). In this case, you can call
i2c_new_probed_device() instead of i2c_new_device().

Example (from the nxp OHCI driver):

static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

static int __devinit usb_hcd_nxp_probe(struct platform_device *pdev)
{
	(...)
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info i2c_info;

	(...)
	i2c_adap = i2c_get_adapter(2);
    
	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
	strlcpy(i2c_info.type, "isp1301_nxp", I2C_NAME_SIZE);
    
	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, normal_i2c, NULL);
	i2c_put_adapter(i2c_adap);
	(...)
}

The above code instantiates up to 1 I2C device on the I2C bus which is on
the OHCI adapter in question. It first tries at address 0x2c, if nothing
is found there it tries address 0x2d, and if still nothing is found, it
simply gives up.

The driver which instantiated the I2C device is responsible for destroying
it on cleanup. This is done by calling i2c_unregister_device() on the
pointer that was earlier returned by i2c_new_device() or
i2c_new_probed_device().
  • 直接i2c_new_device 或 i2c_new_probe_device
    • 先完善i2c_driver,在probe和remove加上打印语句:
#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 int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};
 
 
/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "darkbirds",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};
 
static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}
 
static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
 
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");
  • 完善i2c_device

    • 先尝试直接i2c_new_device方式(认为设备肯定存在):

    • #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_board_info at24cxx_info = {	
      	I2C_BOARD_INFO("at24c08", 0x50),  //0x50是芯片地址,取7位
      };
       
      static struct i2c_client *at24cxx_client;
       
      static int at24cxx_dev_init(void)
      {
      	struct i2c_adapter *i2c_adap;
       
      	i2c_adap = i2c_get_adapter(0);
      	at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
      	i2c_put_adapter(i2c_adap);
      	
      	return 0;
      }
       
      static void at24cxx_dev_exit(void)
      {
      	i2c_unregister_device(at24cxx_client);
      }
       
       
      module_init(at24cxx_dev_init);
      module_exit(at24cxx_dev_exit);
      MODULE_LICENSE("GPL");
      

      编译执行测试程序,若把设备地址改成0x60,也是一样的结果。因为i2c_new_device()会认为设备肯定存在

    • insmod at24cxx_drv.ko
      insmod at24cxx_dev.ko
      /work/drivers_and_test_new/i2c/at24cxx_drv.c at24cxx_probe 13
      rmmod at24cxx dev
      /work/drivers_and_test_new/i2c/at24cxx drv.c at24cxx remove 19
      
  • 而**i2c_new_probe_device()**只对已经能识别出来的设备probe_device才会创建设备。

    • 其参数中的probe会被调用,否则调用默认的probe来识别设备,我们用默认的probe识别,写为NULL就行。在probe函数中,判断addr_list的每一项,看设备是否真正存在,只有存在的情况下才调用i2c_new_device函数。
    • i2c_new_probed_device
      • probe(adap, addr_list[i]) /* 确定设备是否真实存在 */
      • info->addr = addr_list[i];
      • i2c_new_device(adap, info);
struct i2c_client * i2c_new_probed_device(struct i2c_adapter *adap,
		      								struct i2c_board_info *info,
		      								unsigned short const *addr_list,
		      								int (*probe)(struct i2c_adapter *, unsigned short addr))
{
	int i;
 
	if (!probe)
		probe = i2c_default_probe;
 
	for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
		/* Check address validity */
		if (i2c_check_addr_validity(addr_list[i]) < 0) {
			dev_warn(&adap->dev, "Invalid 7-bit address 0x%02x\n", addr_list[i]);
			continue;
		}
 
		/* Check address availability */
		if (i2c_check_addr_busy(adap, addr_list[i])) {
			dev_dbg(&adap->dev, "Address 0x%02x already in "
				"use, not probing\n", addr_list[i]);
			continue;
		}
 
		/* Test address responsiveness */
		if (probe(adap, addr_list[i]))
			break;
	}
 
	if (addr_list[i] == I2C_CLIENT_END) {
		dev_dbg(&adap->dev, "Probing failed, no device found\n");
		return NULL;
	}
 
	info->addr = addr_list[i];
	return i2c_new_device(adap, info);
}

参考文档中的例子是先把i2c_board_info结构体清零,再设置名字和设备地址,参数addr_list含有设备地址,可以放多个设备地址,若第一个设备地址不存在,则判断第二个设备地址。

修改i2c_device:

#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 *at24cxx_client;
 
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
 
static int at24cxx_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info at24cxx_info;
 
	memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));	
	strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
 
	i2c_adap = i2c_get_adapter(0);
	at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
	i2c_put_adapter(i2c_adap);
 
	if (at24cxx_client)
		return 0;
	else
		return -ENODEV;
}
 
static void at24cxx_dev_exit(void)
{
	i2c_unregister_device(at24cxx_client);
}
 
 
module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_LICENSE("GPL");

加载驱动后,会先识别0x60再识别0x50,而地址0x60设备并不存在,故不会创建对应的设备;而对于地址0x50的设备,因为其真正存在,所以会创建出设备,因此装载会打印出来:

insmod at24cxx_drv.ko
insmod at24cxx_dev.ko
/work/drivers_and_test_new/i2c/at24cxx_drv.c at24cxx_probe 13
rmmod at24cxx dev
/work/drivers_and_test_new/i2c/at24cxx drv.c at24cxx remove 19

4.3 方法三:Instantiate from user-space

Method 4: Instantiate from user-space
    	  (从用户空间创建设备!只编写右边的设备驱动,左边的设备在用户空间创建)
-------------------------------------

In general, the kernel should know which I2C devices are connected and
what addresses they live at. However, in certain cases, it does not, so a
sysfs interface was added to let the user provide the information. This
interface is made of 2 attribute files which are created in every I2C bus
directory: new_device and delete_device. Both files are write only and you
must write the right parameters to them in order to properly instantiate,
respectively delete, an I2C device.

File new_device takes 2 parameters: the name of the I2C device (a string)
and the address of the I2C device (a number, typically expressed in
hexadecimal starting with 0x, but can also be expressed in decimal.)

File delete_device takes a single parameter: the address of the I2C
device. As no two devices can live at the same address on a given I2C
segment, the address is sufficient to uniquely identify the device to be
deleted.

Example:
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device

While this interface should only be used when in-kernel device declaration
can't be done, there is a variety of cases where it can be helpful:
* The I2C driver usually detects devices (method 3 above) but the bus
  segment your device lives on doesn't have the proper class bit set and
  thus detection doesn't trigger.
* The I2C driver usually detects devices, but your device lives at an
  unexpected address.
* The I2C driver usually detects devices, but your device is not detected,
  either because the detection routine is too strict, or because your
  device is not officially supported yet but you know it is compatible.
* You are developing a driver on a test board, where you soldered the I2C
  device yourself.

This interface is a replacement for the force_* module parameters some I2C
drivers implement. Being implemented in i2c-core rather than in each
device driver individually, it is much more efficient, and also has the
advantage that you do not have to reload the driver to change a setting.
You can also instantiate the device before the driver is loaded or even
available, and you don't need to know what driver the device needs.
  • 从用户空间创建设备:
    • 创建设备:echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device,导致i2c_new_device被调用;
    • 删除设备:echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device,导致i2c_unregister_device被调用 。
创建设备:
# echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device
/work/drivers_and_test_new/i2c/at24cxx_drv.c at24cxx_probe 13	//i2c_new_device被调用

删除设备:
# echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device
/work/drivers_and_test_new/i2c/at24cxx drv.c at24cxx remove 19	//i2c_unregister_device被调用

4.4 方法四:Probe an I2C bus for certain devices

Method 3: Probe an I2C bus for certain devices
    	 (事先不知道I2C总线是哪一个,即不知道设备接在哪个适配器上,
    	   所以只需要写出右边的设备驱动程序,左边不用写)
----------------------------------------------

Sometimes you do not have enough information about an I2C device, not even
to call i2c_new_probed_device(). The typical case is hardware monitoring
chips on PC mainboards. There are several dozen models, which can live
at 25 different addresses. Given the huge number of mainboards out there,
it is next to impossible to build an exhaustive list of the hardware
monitoring chips being used. Fortunately, most of these chips have
manufacturer and device ID registers, so they can be identified by
probing.

In that case, I2C devices are neither declared nor instantiated
explicitly. Instead, i2c-core will probe for such devices as soon as their
drivers are loaded, and if any is found, an I2C device will be
instantiated automatically. In order to prevent any misbehavior of this
mechanism, the following restrictions apply:
* The I2C device driver must implement the detect() method, which
  identifies a supported device by reading from arbitrary registers.
* Only buses which are likely to have a supported device and agree to be
  probed, will be probed. For example this avoids probing for hardware
  monitoring chips on a TV adapter.

Example:
See lm90_driver and lm90_detect() in drivers/hwmon/lm90.c

I2C devices instantiated as a result of such a successful probe will be
destroyed automatically when the driver which detected them is removed,
or when the underlying I2C bus is itself destroyed, whichever happens
first.

Those of you familiar with the i2c subsystem of 2.4 kernels and early 2.6
kernels will find out that this method 3 is essentially similar to what
was done there. Two significant differences are:
* Probing is only one way to instantiate I2C devices now, while it was the
  only way back then. Where possible, methods 1 and 2 should be preferred.
  Method 3 should only be used when there is no other way, as it can have
  undesirable side effects.
* I2C buses must now explicitly say which I2C driver classes can probe
  them (by the means of the class bitfield), while all I2C buses were
  probed by default back then. The default is an empty class which means
  that no probing happens. The purpose of the class bitfield is to limit
  the aforementioned undesirable side effects.

Once again, method 3 should be avoided wherever possible. Explicit device
instantiation (methods 1 and 2) is much preferred for it is safer and
faster.

在lm90.c中,class表示去哪一类适配器上查找能支持该设备,用detect这个函数来检测该设备地址确实存在于某address_list中。首先去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",如果能找到就调用i2c_new_device来注册i2c_client,,然后和i2c_driver的id_table比较,如果匹配,则调用对应的probe函数。

static struct i2c_driver lm90_driver = {
	.class		= I2C_CLASS_HWMON,		 /* 表示去哪些适配器上找设备 */
	.driver = {
		.name	= "lm90",
        .owner	= THIS_MODULE,
	},
	.probe		= lm90_probe,
	.remove		= lm90_remove,
	.alert		= lm90_alert,
	.id_table	= lm90_id_table,
	.detect		= lm90_detect,		 /* 用这个函数来检测设备确实存在 */
	.address_list	= normal_i2c,	 /* 这些设备的地址 */
};

以下是i2c_add_driver注册driver的过程,除了从dev链表里取出能匹配的i2c_client,若没有合适的i2c_client,在dev链表还存在着适配器,其中含有type来区别是否为适配器,对于每一个适配器,会调用它的函数确定address_list里的设备是否存在,如果存在,再调用detect进一步确定、设置,最终i2c_new_device

i2c_add_driver
    i2c_register_driver
        a. 将at24cxx_driver放入i2c_bus_type的drv链表
           并且从dev链表里取出能匹配的i2c_client并调用probe
        driver_register
               
        b. 对于每一个适配器,调用__process_new_driver
           对于每一个适配器,调用它的函数确定address_list里的设备是否存在
           如果存在,再调用detect进一步确定、设置,然后i2c_new_device
    
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
    __process_new_driver
        i2c_do_add_adapter
            /* Detect supported devices on that bus, and instantiate them */
            i2c_detect(adap, driver);
				/* 
				 * 第一次为0x60,这个设备不存在,因此不会调用detect 
				 * 第二次为0x50,会调用到detect函数,因此能运行到detect说明设备已经存在
				 */
                for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {  
                     err = i2c_detect_address(temp_client, driver);             
                           
                    
                    /* 判断这个设备是否存在:简单的发出S信号确定有ACK */
                    if (!i2c_default_probe(adapter, addr))
                         return 0;
                                        
                    memset(&info, 0, sizeof(struct i2c_board_info));
                    info.addr = addr;    
                                      
                    // 设置info.type
                    err = driver->detect(temp_client, &info);
                    
                    i2c_new_device //需要addr和info.type

因此只需要写出i2c_driver

#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 int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};
 
static int at24cxx_detect(struct i2c_client *client, struct i2c_board_info *info)
{
	/* 能运行到这里, 表示该addr的设备是存在的
	 * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
	 * 还需要进一步读写I2C设备来分辨是哪款芯片
	 * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
	 */
	
	printk("at24cxx_detect : addr = 0x%x\n", client->addr);
 
	/* 进一步判断是哪一款 */
	
	strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
	return 0;
	/* 返回0之后, 会创建一个新的I2C设备
	 * i2c_new_device(adapter, &info), 其中的info->type = "at24c08",
	 * 它和i2c_device_id中的名字进行比较,一致则调用probe
	 */
}
 
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
 
/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.class  = I2C_CLASS_HWMON,			/* 表示去哪些适配器上找设备 */
	.driver	= {
		.name	= "DarkBirds",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
	.detect     = at24cxx_detect,  		/* 用这个函数来检测设备确实存在 */
	.address_list	= addr_list,   		/* 这些设备的地址 */
};
 
static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}
 
static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
 
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

执行结果:

insmod at24cxx drv.ko
at24cxx_detect : addr = 0x50
i2c i2c-0: DarkBirds detection fuction provided no name for 0x50

这种方法,事先并不知道这个I2C设备在哪个适配器上,去class表示的所有的适配器上查找,如果和某适配器上的一些I2C设备的地址是一样,则继续用detect函数分辨它。

**所以:**其它三种方法都要事先确定用哪个适配器adap(IIC总线,IIC控制器)。

那如果事先并不知道IIC设备在哪个适配器上,怎么办?答:得去class表示的所有的适配器上查找。

因为一些I2C设备的上层地址是一样,怎么继续分辨它是哪一款?答:要用到detect函数

与内核2.6的IIC驱动相似,在文档中参考lm90.c,文档中建议用另外三种方法,这种方法在万不得已的情况再用。

  • 对于裸板程序来说,可以直接对寄存器进行映射使用,但在内核中有完善的IIC总线驱动程序,会帮我们去发出起始信号,识别IIC设备参考i2c-s3c2410.c,在入口函数中注册了平台总线,在probe函数中设置adap并注册添加了adap(IIC适配器:i2c_add_adapter>i2c_register_adapter),在设置adap中的algo(算法)设置s3c24xx_i2c_xfer函数,这个函数实现了识别IIC设备的功能(发S信号,设备地址,等待应答)
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};
 
static struct s3c24xx_i2c s3c24xx_i2c = {
	.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
	.wait		= __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
	.tx_setup	= 50,
	.adap		= {
		.name			= "s3c2410-i2c",
		.owner			= THIS_MODULE,
		.algo			= &s3c24xx_i2c_algorithm,
		.retries		= 2,
		.class			= I2C_CLASS_HWMON,
	},
};
...
 
static int __init i2c_adap_s3c_init(void)
{
	int ret;
 
	ret = platform_driver_register(&s3c2410_i2c_driver);
	if (ret == 0) {
		ret = platform_driver_register(&s3c2440_i2c_driver);
		if (ret)
			platform_driver_unregister(&s3c2410_i2c_driver);
	}
 
	return ret;
}
 
...
 
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
...
 
	ret = s3c24xx_i2c_init(i2c);
	if (ret != 0)
		goto err_iomap;
 
	/* find the IRQ for this unit (note, this relies on the init call to
	 * ensure no current IRQs pending 
	 */
 
	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "cannot find IRQ\n");
		ret = -ENOENT;
		goto err_iomap;
	}
 
	ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
			  pdev->name, i2c);
 
	if (ret != 0) {
		dev_err(&pdev->dev, "cannot claim IRQ\n");
		goto err_iomap;
	}
 
	i2c->irq = res;
		
	dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
		(unsigned long)res->start);
 
	ret = i2c_add_adapter(&i2c->adap);
...
}
 
...
static int __init i2c_adap_s3c_init(void)
{
	int ret;
 
	ret = platform_driver_register(&s3c2410_i2c_driver);
	if (ret == 0) {
		ret = platform_driver_register(&s3c2440_i2c_driver);
		if (ret)
			platform_driver_unregister(&s3c2410_i2c_driver);
	}
 
	return ret;
}
  • 参考IIC设备驱动\drivers\i2c\chips\eeprom.c,在入口函数中i2c_add_driver添加driver设备,attach_adapter会加到适配器中去,在i2c_add_driver过程中总线为i2c_bus_type,会先把i2c_driver放入总线的dri链表,从adap链表中取出"适配器"并调用attach_adapter,而attach_adapter会调用i2c_probe,其中会使用master_xfer函数发S信号,发设备地址,如果能收到ACK信号,则会调用i2c_probe参数中的function函数,这其中我们可以注册字符设备驱动来完善IIC设备驱动程序,对于总线驱动程序中i2c_add_adapter函数会把adap(适配器)放入链表,一样会调用dri的attach_adapter然后调用master_xfer函数。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;
 
...
	if (driver->attach_adapter) {
		struct i2c_adapter *adapter;
 
		list_for_each_entry(adapter, &adapters, list) {
			driver->attach_adapter(adapter);
		}
	}
 
	mutex_unlock(&core_lists);
	return 0;
}
 
static int eeprom_attach_adapter(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, eeprom_detect);
}
 
static struct i2c_driver eeprom_driver = {
	.driver = {
		.name	= "eeprom",
	},
	.id		= I2C_DRIVERID_EEPROM,
	.attach_adapter	= eeprom_attach_adapter,
	.detach_client	= eeprom_detach_client,
};
...
static int __init eeprom_init(void)
{
	return i2c_add_driver(&eeprom_driver);
}

i2c_probe函数过程如下

  • i2c_probe(adapter, &addr_data, eeprom_detect);
    • i2c_probe_address // 发出S信号,发出设备地址(来自addr_data)
      • i2c_smbus_xfer
        • i2c_smbus_xfer_emulated
          • i2c_transfer
            • adap->algo->master_xfer // s3c24xx_i2c_xfer
  • i2c_add_driver:

    • 1.把i2c_driver放入链表;
    • 2.从adap链表里取出适配器,调用drv的attch_adapter函数;
    • 3.调用i2c_probe(adapter, &addr_data, function),master_xfer函数用adapter参数发信号,确定有无设备,有则调用function;
    • 4.构造i2c_client结构体(.address .adapter(指向左边的adap) .driver(指向右边的dri) )
  • i2c_add_adapter:

    • 1.把adap适配器放入链表;
    • 2.调用drv的attch_adapter函数;
    • 3.同上

image-20210812224535987

5、完善设备驱动

5.1 用户态直接访问

不需要自己写驱动程序,在内核有人完善了IIC驱动程序,可以让我们直接使用。在内核文档中dev-interface有介绍怎么访问,在C例子中含有i2c_smbus_write_word_data等函数,这些函数在i2c工具包中得到(i2c-tools package),在网上下载个i2c工具包。

Each registered i2c adapter gets a number, counting from 0. You can
examine /sys/class/i2c-dev/ to see what number corresponds to which adapter.
Alternatively, you can run "i2cdetect -l" to obtain a formated list of all
i2c adapters present on your system at a given time. i2cdetect is part of
the i2c-tools package.

C example
=========

So let's say you want to access an i2c adapter from a C program. The
first thing to do is "#include <linux/i2c-dev.h>". Please note that
there are two files named "i2c-dev.h" out there, one is distributed
with the Linux kernel and is meant to be included from kernel
driver code, the other one is distributed with i2c-tools and is
meant to be included from user-space programs. You obviously want
the second one here.

Now, you have to decide which adapter you want to access. You should
inspect /sys/class/i2c-dev/ or run "i2cdetect -l" to decide this.
Adapter numbers are assigned somewhat dynamically, so you can not
assume much about them. They can even change from one boot to the next.

Next thing, open the device file, as follows:

  int file;
  int adapter_nr = 2; /* probably dynamically determined */
  char filename[20];
  
  snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
  file = open(filename, O_RDWR);
  if (file < 0) {
    /* ERROR HANDLING; you can check errno to see what went wrong */
    exit(1);
  }

When you have opened the device, you must specify with what device
address you want to communicate:

  int addr = 0x40; /* The I2C address */

  if (ioctl(file, I2C_SLAVE, addr) < 0) {
    /* ERROR HANDLING; you can check errno to see what went wrong */
    exit(1);
  }

Well, you are all set up now. You can now use SMBus commands or plain
I2C to communicate with your device. SMBus commands are preferred if
the device supports them. Both are illustrated below.

  __u8 register = 0x10; /* Device register to access */
  __s32 res;
  char buf[10];

  /* Using SMBus commands */
  res = i2c_smbus_read_word_data(file, register);
  if (res < 0) {
    /* ERROR HANDLING: i2c transaction failed */
  } else {
    /* res contains the read word */
  }

  /* Using I2C Write, equivalent of 
     i2c_smbus_write_word_data(file, register, 0x6543) */
  buf[0] = register;
  buf[1] = 0x43;
  buf[2] = 0x65;
  if (write(file, buf, 3) ! =3) {
    /* ERROR HANDLING: i2c transaction failed */
  }

  /* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
  if (read(file, buf, 1) != 1) {
    /* ERROR HANDLING: i2c transaction failed */
  } else {
    /* buf[0] contains the read byte */
  }

Note that only a subset of the I2C and SMBus protocols can be achieved by
the means of read() and write() calls. In particular, so-called combined
transactions (mixing read and write messages in the same transaction)
aren't supported. For this reason, this interface is almost never used by
user-space programs.

IMPORTANT: because of the use of inline functions, you *have* to use
'-O' or some variation when you compile your program!

i2c-tools-3.1.0\include\linux\i2c-dev.h中的函数对应着内核中的i2c-dev.c驱动程序,直接写出测试程序,包含工具包的"i2c-dev.h"。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "i2c-dev.h"
 
 
/* 
 * i2c_usr_test </dev/i2c-0> <dev_addr> r addr
 * i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
 */
 
void print_usage(char *file)
{
	printf("%s </dev/i2c-0> <dev_addr> r addr\n", file);
	printf("%s </dev/i2c-0> <dev_addr> w addr val\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char addr, data;
	unsigned int dev_addr;
	
	if ((argc != 5) && (argc != 6))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can't open %s\n", argv[1]);
		return -1;
	}
 
	dev_addr = strtoul(argv[2], NULL, 0);
	if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)		//判断地址是否被使用
	{    
		/* ERROR HANDLING; you can check errno to see what went wrong */    
		printf("set addr error!\n");
		return -1;
	}
 
	if (strcmp(argv[3], "r") == 0)
	{
		addr = strtoul(argv[4], NULL, 0);
		
		data = i2c_smbus_read_word_data(fd, addr);
			
		printf("data: %c, %d, 0x%2x\n", data, data, data);
	}
	else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
	{
		addr = strtoul(argv[4], NULL, 0);
		data = strtoul(argv[5], NULL, 0);
		i2c_smbus_write_byte_data(fd, addr, data);		
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

i2c-dev.c需要编译进内核或者以insmod加载(重新配置内核:Device Drivers——> I2C support——><*> I2C device interface 或者在/drivers/i2c/Makefile中,添加配置项CONFIG_I2C_CHARDEV),修改内核并重新加载内核,执行测试程序。

在文档中建议不用read、write函数是因为读写混合,例如需要想读地址,需要把地址先写给IIC设备,然后再去写,但是利用i2c_smbus_write_word_data等函数可以一次性操作完成。

执行测试程序,当我们加载之前的驱动,重新去读就没办法了,因为地址0x50的这个设备已经被dev和drv占用了(在内核i2c-dev.c中的ioctl有判断地址是否被使用,为了防止破坏别人的驱动程序)。

# ./i2c_usr_test
./i2c_usr_test </dev/i2c-0> <dev_addr> r addr
./i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
# 
# ./i2c_usr_test /dev/i2c-0 0x50 r 0
data: 2, 50, 0x32
# 
# ./i2c_usr_test /dev/i2c-0 0x50 w 0 0x61
# ./i2c_usr_test /dev/i2c-0 0x50 r 0
data: a, 97, 0x61
# 
# insmod at24cxx_drv.ko
# insmod at24cxx_dev.ko
# ./i2c_usr_test /dev/i2c-0 0x50 r 0 
set addr error!

5.2 重编I2C总线驱动

在总线模型中匹配成功后会调用i2c_driver的probe函数,在probe注册字符设备驱动,写出at24c08的读写函数。怎么读写?利用核心层提供的操作函数(SMBus系统管理总线、i2c_transfer)。

SMBus是IIC协议中的一小部分即协议的子集,文档中建议我们用SMBus,因为有些适配器只支持SMBus,之前的内核2.6的IIC驱动是以i2c_transfer来传输数据。

在smbus-protocol文档中,i2c_smbus_read_byte_datai2c_smbus_write_byte_data的格式对应着at24c08的读写格式,因此使用这两个函数:

SMBus Read Byte:  i2c_smbus_read_byte_data()
============================================

This reads a single byte from a device, from a designated register.
The register is specified through the Comm byte.

S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] NA P                 //NA没有应答信号

........................................................................................................

SMBus Write Byte:  i2c_smbus_write_byte_data()
==============================================

This writes a single byte to a device, to a designated register. The
register is specified through the Comm byte. This is the opposite of
the Read Byte operation.

S Addr Wr [A] Comm [A] Data [A] P
  • 完整驱动程序

修改方法二中的i2c_driver,i2c_device不变:

#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>
#include <linux/fs.h>
#include <asm/uaccess.h>
 
 
static int major;
static struct class *class;
static struct i2c_client *at24cxx_client;
 
/* 传入: buf[0] : addr
 * 输出: buf[0] : data
 */
static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
	unsigned char addr, data;
	
	copy_from_user(&addr, buf, 1);
	data = i2c_smbus_read_byte_data(at24cxx_client, addr);
	copy_to_user(buf, &data, 1);
	return 1;
}
 
/* buf[0] : addr
 * buf[1] : data
 */
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
	unsigned char ker_buf[2];
	unsigned char addr, data;
 
	copy_from_user(ker_buf, buf, 2);
	addr = ker_buf[0];
	data = ker_buf[1];
 
	printk("addr = 0x%02x, data = 0x%02x\n", addr, data);
 	//i2c_smbus...是i2c_xfer的子集
	if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data))
		return 2;
	else
		return -EIO;	
}
 
static struct file_operations at24cxx_fops = {
	.owner = THIS_MODULE,
	.read  = at24cxx_read,
	.write = at24cxx_write,
};
 
static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	at24cxx_client = client;
		
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "at24cxx", &at24cxx_fops);
	class = class_create(THIS_MODULE, "at24cxx");
	device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
	
	return 0;
}
 
static int __devexit at24cxx_remove(struct i2c_client *client)
{
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, "at24cxx");
		
	return 0;
}
 
static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};
 
 
/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "100ask",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};
 
static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}
 
static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
 
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");
  • 测试程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* i2c_test r addr
 * i2c_test w addr val
 */
 
void print_usage(char *file)
{
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/at24cxx\n");
		return -1;
	}
 
	if (strcmp(argv[1], "r") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		if (write(fd, buf, 2) != 2)
			printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}
  • 先去掉内核自带总线驱动:“Device Drivers——>I2C support——>I2C Hardware Bus support——>< > S3C2410 I2C Driver”,重新编译、加载内核。
  • 先加载drv驱动,再加载dev驱动,两者匹配之后,调用i2c_driver中的probe函数,才会出现字符设备驱动。
# lsmod
at24cxx_drv 1746 0 -Live 0xbf024000 (o)
# rmmod at24cxx_drv
# insmod at24cxx_drv.ko
# ls /dev/at*
ls: /dev/at*: No such file or directory
# insmod at24cxx_dev.ko
# ls /dev/at*
/dev/at24cxx
# 
# ./i2c_test
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test r 0
data: 1, 49, 0x31
#
# ./i2c_test w 0x32
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test w 0x32

= {
{ “at24c08”, 0 },
{}
};

/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = “100ask”,
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = __devexit_p(at24cxx_remove),
.id_table = at24cxx_id_table,
};

static int at24cxx_drv_init(void)
{
/* 2. 注册i2c_driver */
i2c_add_driver(&at24cxx_driver);

return 0;

}

static void at24cxx_drv_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE(“GPL”);


* **测试程序**

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* i2c_test r addr
 * i2c_test w addr val
 */
 
void print_usage(char *file)
{
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/at24cxx\n");
		return -1;
	}
 
	if (strcmp(argv[1], "r") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		if (write(fd, buf, 2) != 2)
			printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}
  • 先去掉内核自带总线驱动:“Device Drivers——>I2C support——>I2C Hardware Bus support——>< > S3C2410 I2C Driver”,重新编译、加载内核。
  • 先加载drv驱动,再加载dev驱动,两者匹配之后,调用i2c_driver中的probe函数,才会出现字符设备驱动。
# lsmod
at24cxx_drv 1746 0 -Live 0xbf024000 (o)
# rmmod at24cxx_drv
# insmod at24cxx_drv.ko
# ls /dev/at*
ls: /dev/at*: No such file or directory
# insmod at24cxx_dev.ko
# ls /dev/at*
/dev/at24cxx
# 
# ./i2c_test
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test r 0
data: 1, 49, 0x31
#
# ./i2c_test w 0x32
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test w 0x32

参考资料:

Linux2.6之IIC驱动_月月鸟呀的博客-CSDN博客

Linux3.4.2之IIC驱动_月月鸟呀的博客-CSDN博客

Okay,你想了解如何编写 MSP430 的 IIC 驱动。MSP430 是一款低功耗 MCU,它可以通过 I2C 或 SPI 等接口和其他设备进行通信。以下是一些用于编写 MSP430 IIC 驱动程序的步骤: 1. 确定 IIC 的时钟速率和设备地址 MSP430 可以通过内置的 USCI 模块(主要是 USCI_A0 和 USCI_B0)来实现 I2C 通信。在编写 IIC 驱动程序之前,您需要确认所需的 IIC 时钟速率和要访问的设备的地址。根据 IIC 总线规范,设备地址可以是 7 位或 10 位。 2. 初始化 USCI 模块 在使用 USCI 模块之前,需要配置模块的各种控制参数。这些参数包括时钟源、时钟分频器、数据位、停止位等。您可以使用 MSP430 提供的模板代码来初始化 USCI 模块,或根据您的特定需求自行编写初始化代码。 3. 发送和接收数据 一旦 USCI 模块成功初始化,您就可以使用 send() 和 receive() 函数来发送和接收 IIC 数据。在发送数据时,您需要指定要发送的设备地址和要发送的数据。在接收数据时,您需要指定设备地址并分配足够的缓冲区来保存接收到的数据。 4. 处理 IIC 中断 在实现 IIC 驱动程序时,您还需要考虑处理中断。当 MSP430 接收到 IIC 中断时,您可以在 ISR 中处理并执行相关操作。在处理中断时,您需要注意及时清除标志位、停止发送或接收数据等。 以上是一些基本的步骤,你可以从这里开始编写你的 MSP430 IIC 驱动程序。希望我的回答能够帮助到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值