参考文档:
https://blog.csdn.net/wq690968346/article/details/50921039(i2c_client的四中方法)
https://blog.csdn.net/pengliang528/article/details/78262441
1:最新的I2c模型:设备,总线,驱动模型。
分层模型(1):核心层:提供统一的I2C接口操作函数,提供了总线驱动和设备驱动的注册,注销方法,i2c的通信方式,与具体适配器无关的代码以及探测设备
(2):适配器:基于板卡的I2c的硬件操作。基于某个芯片的硬件操作。
bus:I2C_bus_type提供了注册接口函数。
I2c总线层驱动(device):i2c_clinet 注册: i2c_new_device 结构体:i2c_adapter(i2c_board_info适配器数据结构体),i2c_algorithm和控制i2c适配器产生通信信号的函数。是对I2c硬件体系结构中适配器的实现,适配器可由CPU控制,或者直接集成在CPU内部,我们可以控制i2c适配器产生开始位,停止位,以及读写周期,以及以从设备方式读写,产生ACK。
驱动层(driver):i2c_driver 注册:i2c_add_driver() 结构体:i2c_driver 比较(i2c_device_id)
比较如果名称相同,则调用i2c_driver 的probe函数
第一层:i2c adapter的硬件驱动,探测,初始化i2c_adapter(申请i2c的io地址和中断号),驱动soc控制控制adapter产生的硬件信号以及I2c中断。
第二层:提供i2c_adapter的algorithm,用适配器xxx_xferf()填充i2c_algorithm的master_xfer函数指针,并把i2c_algorithm赋给algo指针,覆盖图中的抽象层,i2c核心层。
第三层:i2c_driver接口,用具体的i2c device设备的attach_adapter()、detach_adapter()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的挂接
第四层:具体device的驱动,i2c_driver只是实现设备与总线的挂接,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)
与之相关的c文件:i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
所有的i2c设备都在sysfs文件系统中显示,存在于sys/bus/i2c目录下,以适配器地址和芯片地址形式列出。
i2c设备在内核原代码中的位置linux-3.10.y\drivers\i2c
目录下的功能的简单介绍:
i2c-core:i2c的核心功能以及/proc/bus/i2c接口
algos文件夹:实现一些i2c总线适配器的通信方式。有四个主要结构体
i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,每个i2c适配器分配一个设备,应用程序通过i2c-%d文件名并使用文件操作接口等来访问这个设备。应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c.
algos文件夹实现了一些I2C总线适配器的algorithm.实现一些i2c总线适配器的通信方式。有四个主要结构体i2c_adapter,i2c_algorithm,i2c_driver,i2c_client结构体
先看一些结构体:(从下到上)
i2c的主机操作驱动主要聚集在这一部分上。
- struct i2c_algorithm { //i2c传输
- 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);//smbus传输函数指针
- u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能
- };
- struct i2c_adapter { //i2c物理上的适配器,一个适配器上有多个i2c_client ,所以i2c_adapter中包含依附于它的i2c_clinet
- 的链表。
- struct module *owner;//所属模块
- unsigned int id;//algorithm的类型,定义于i2c-id.h,
- unsigned int class;
- const struct i2c_algorithm *algo; //总线通信方法结构体指针
- void *algo_data;//algorithm数据
- struct rt_mutex bus_lock;//控制并发访问的自旋锁
- int timeout;
- int retries;//重试次数
- struct device dev; //适配器设备
- int nr;
- char name[48];//适配器名称
- struct completion dev_released;//用于同步
- struct list_head userspace_clients;//client链表头
- };
- struct i2c_client { //对应真是的i2c的物理设备,每个i2c设备都要有一个i2c-client来描述
- unsigned short flags;//标志
- unsigned short addr; //低7位为芯片地址
- char name[I2C_NAME_SIZE];//设备名称
- struct i2c_adapter *adapter;//依附的i2c_adapter
- struct i2c_driver *driver;//依附的i2c_driver
- struct device dev;//设备结构体
- int irq;//设备所使用的结构体
- struct list_head detected;//链表头
- };
- struct i2c_driver { //与i2c_client 是一对多,一个i2c_driver 可以支持多个同等类型的i2c_client
- unsigned int class;
- int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针
- int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针
- int (*probe)(struct i2c_client *, const struct i2c_device_id *);
- int (*remove)(struct i2c_client *);
- void (*shutdown)(struct i2c_client *);
- int (*suspend)(struct i2c_client *, pm_message_t mesg);
- int (*resume)(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;
- const struct i2c_device_id *id_table;//该驱动所支持的设备ID表
- int (*detect)(struct i2c_client *, struct i2c_board_info *);
- const unsigned short *address_list;
- struct list_head clients;
- };
- struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_16BIT_REG 0x0002 /* indicate reg bit-width is 16bit */
#define I2C_M_16BIT_DATA 0x0008 /* indicate data bit-width is 16bit */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
搞清楚以上四个结构体:i2c驱动差不多就能搞清楚。
i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方式,一个i2c适配器需要i2c_algorithm提供的通信函数来控制适配器产生特定的访问周期,(所以说i2c_algorithm很重要)。
i2c_algorithm到的关键函数是master_xfer()用于i2c访问周期的需要的信号,以i2c_msg为单位,i2c_msg结构体很重要,表明了i2c的传输方向,传输地址,缓冲区,缓冲长度等信息。
i2c_driver对应一套驱动方式,主要成员函数probe,remove,suspend,然后根据i2c——device_id的形式的id_table是该驱动所支持i2c设备的id表。
i2c_client对应于真是的物理设备,每个i2c 设备都需要一个i2c_clinet来描述,i2c_driver与i2c_clinet的关系是一对多,
i2c_clinet的信息通畅在bsp的板文件中通过i2c_board_info填充,
主要函数
i2c_transfer()只是用来找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正的驱动硬件流程
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)从i2cbus中读取字节。
- //增加/删除i2c_adapter
- int i2c_add_adapter(struct i2c_adapter *adapter)
- int i2c_del_adapter(struct i2c_adapter *adap)
- //增加/删除i2c_driver
- int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
- void i2c_del_driver(struct i2c_driver *driver)
- //i2c_client依附/脱离
- int i2c_attach_client(struct i2c_client *client)
- //I2C传输,发送和接收
- int i2c_master_send(struct i2c_client *client,const char *buf ,int count) //用于时序比较简单的send
- int i2c_master_recv(struct i2c_client *client, char *buf ,int count) //用于时序比较简单的写
- int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 用于i2c 是额脾气和i2c设备之间的一组消息交互,该函数一次可以传输多个i2c_msg,本身不具备驱动硬件适配器以完成消息交互的能力,只是寻找到与i2c_adapter对应的i2c_algorithm,并使用master-xfer函数真正驱动硬件流程。
下面通过简单的实力分析一个i2c 设备:
i2c控制器本身连接在platform总线上platform_driver和platform_device匹配来执行,
一般我们会在platform_driver的probe中完成几个工作:初始化i2c适配器的硬件资源(申请io,中断号,时钟等)
通过i2c_add_adapter添加i2c_adapter的数据结构体。
i2c适配器驱动的注册过程
static int hi_i2c_probe(struct platform_device *pdev)
{
int errorcode;
struct hi_i2c *pinfo;
struct i2c_adapter *adap;
struct resource *mem;
struct hi_platform_i2c *platform_info;
platform_info =
(struct hi_platform_i2c *)pdev->dev.platform_data;
if (platform_info == NULL) {
dev_err(&pdev->dev, "%s: Can't get platform_data!\n",
__func__);
errorcode = -EPERM;
goto i2c_errorcode_na;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem == NULL) {
dev_err(&pdev->dev, "Get I2C mem resource failed!\n");
errorcode = -ENXIO;
goto i2c_errorcode_na;
}
pinfo = kzalloc(sizeof(struct hi_i2c), GFP_KERNEL);
if (pinfo == NULL) {
dev_err(&pdev->dev, "Out of memory!\n");
errorcode = -ENOMEM;
goto i2c_errorcode_na;
}
pinfo->regbase = (unsigned char __iomem *)IO_ADDRESS(mem->start);
pinfo->mem = mem;
pinfo->dev = &pdev->dev;
pinfo->pdata = platform_info;
pinfo->g_last_dev_addr = 0;
hi_i2c_hw_init(pinfo);
platform_set_drvdata(pdev, pinfo);
adap = &pinfo->adap;
i2c_set_adapdata(adap, pinfo);
adap->owner = THIS_MODULE;
adap->class = platform_info->i2c_class;
strlcpy(adap->name, pdev->name, sizeof(adap->name));
adap->algo = &hi_i2c_algo; //用来分配和注册i2c的传输函数
adap->dev.parent = &pdev->dev;
adap->nr = pdev->id;
adap->retries = CONFIG_HI_I2C_RETRIES;
errorcode = i2c_add_numbered_adapter(adap);
if (errorcode) {
dev_err(&pdev->dev,
"%s: Adding I2C adapter failed!\n", __func__);
goto i2c_errorcode_free_irq;
}
dev_notice(&pdev->dev,
"Hisilicon [%s] probed!\n",
dev_name(&pinfo->adap.dev));
goto i2c_errorcode_na;
i2c_errorcode_free_irq:
free_irq(pinfo->irq, pinfo);
kfree(pinfo);
i2c_errorcode_na:
return errorcode;
}
//接下来分析底层的i2c的硬件传输函数
static int hi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct hi_i2c *pinfo;
int errorcode;
pinfo = (struct hi_i2c *)i2c_get_adapdata(adap);
pinfo->msgs = msgs;
pinfo->msg_num = num;
pinfo->msg_index = 0;
if (msgs->flags & I2C_M_RD)
errorcode = hi_i2c_read(pinfo);
else
errorcode = hi_i2c_write(pinfo);
return errorcode;
}
static const struct i2c_algorithm hi_i2c_algo = {
.master_xfer = hi_i2c_xfer,
.functionality = hi_i2c_func,
};
writel() 往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节)。
接下来的读写函数就是基于板卡硬件平台的i2c驱动操作。
i2c设备驱动:i2c设备驱动使用i2c_driver和i2c_client数据结构体并且填充i2c_driver,我们县定义一个初始化额i2c_driver;
static struct i2c_driver xxx_i2c_driver = {
.driver = {
.name = "xxx",
},
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_id,
};
i2c设备驱动模块的加载:通过核心函数i2c_add_driverde api函数添加i2c_driver的工作
在i2c设备上读写数据的时序且数据通过i2c_msg数组进行组织,最后通过i2c_transfer函数完成
linux/drivers/i2c/busses/i2c-hisilicon.c下的基于hi3536设备的模块的加载(此文件夹下是i2c的总线驱动程序)
接下来我们分析一个设备驱动程序:
关于i2c的设备和驱动的匹配
设备驱动不依赖具体的cpu和i2c控制器我的硬件特性,如果某一电路板包含该外设,只需在板级文件中添加对应的i2c_board_info
在支持设备树的情况下,简单的在.dts文件中添加一个节点即可。
i2c_new_dummy->i2c_new_device:创建一个i2c设备,用来绑定一个驱动模块。
设备驱动编写
i2c_client:的信息一般是在BSP的板文件中通过i2c_board_info填充,例如定义i2c的设备id,地址信息,中断号等信息,每个i2c设备都需要一个i2c_client来描述,对应的是真实的物理设备,在i2c总线驱动i2c_bus_type的match()函数i2c_driver_match()中,会调用i2c_match_id()函数匹配板文件中的定义的iD和i2c_driver所支持的ID表。
常用的i2c_client设置
eg1:static struct i2c_board_info mini2440_i2c_devs[] __initdata = { { /* 遇到与”24c08一样的名称”的驱动就会与之绑定,0x50是I2C设备的地址 */ I2C_BOARD_INFO("24c08", 0x50), .platform_data = &at24c08, }, }; /* 这里的0代表:i2c-0总线 */ i2c_register_board_info(0, mini2440_i2c_devs, ARRAY_SIZE(mini2440_i2c_devs));
使用i2c_register_board_info去实例化必须知道我们使用的I2C设备是挂载到哪个总线上,并知道设备的地址。
在Linux启动的时候会将信息进行收集,i2c适配器会扫描已经静态注册的i2c_board_info,通过调用i2c_register_board_info函数将包含所有I2C设备的i2c_board_info信息的i2c_devinfo变量加入到__i2c_board_list链表中,并调用i2c_new_device为其实例化一个i2c_client。在驱动加载的时候遇到同名的i2c_board_info就会将i2c_client和driver绑定,并且执行driver的probe函数。
i2c_new_device用来创建i2c_client实例化。
方法二:/*@后面是设备的起始地址*/
&i2c-0@fe { /* i2c_client的name = "hall-i2c" */
compatible = "qcom, hall-i2c";
reg = <fe>; interrupts = <70>; /* 如果设置成disabled,在初始化的时候就不会被实例化,可以在linux内置文档查看更多 */
status = "disabled"; };
其中:i2c-0中的0是总线编号,reg是设备地址,interrupts是中断号。
在初始化的时候i2c总线会调用qup_i2c_probe(),接着调用of_i2c_register_devices对dtsi上所描述的设备进行实例化。并创建相应的sys文件:sys/bus/i2c/devices/0-00fe。
用于创建i2c_client实例化的方法连接:
https://blog.csdn.net/lugandong/article/details/48092397
主要看对于sysfs创建的二进制属性文件如何在应用程序中调用:
https://www.linuxidc.com/Linux/2013-10/91993p14.htm