概述
iic子系统分为三层
iic设备驱动层(i2c-dev.c , 或者自己的其他i2c设备驱动如TP)
功能:
1、为用户提供调用和接口
2、实现策略相关问题:知道发什么数据,但不知道怎么发
iic核心层(i2c-core.c)
1、内核初始化时,注册一根i2c总线
2、给驱动编程人员提供编程接口
3、发送数据时,数据封包,i2c_msg结构体
iic总线驱动层
1、初始化硬件(iic控制器/I2C适配器,)
2、实现操作方法
iic驱动分为两部分:IIC总线驱动和IIC设备驱动,I2C总线对应着一条/bus下的一条总线,该总线管理着i2c设备和i2c驱动的匹配删除等操作,和其他总线也一样,当匹配成功后就会调用probe函数。
一、结构体
i2c_driver结构体,表示一个i2c驱动,
i2c_driver{//表示一个i2c驱动
/*检测函数*/
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;//记录i2c驱动能服务于哪些设备
...
}
i2c设备,硬件设备的一个抽象
struct i2c_client {//表示i2c设备
unsigned short flags; /* 标号*/
unsigned short addr; /* 设备地址 7位NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE]; //设备名字
struct i2c_adapter *adapter; /* 所属适配器 */
/*设备结构体 */
struct device dev;
int irq; /* 中断号 */
struct list_head detected;//链表
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
i2c适配器, 对应SOC上的I2C控制器,
struct i2c_adapter { //i2c设配器、控制器
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /*表示i2c操作方法(利用i2c协议传输数据)*/
-->int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
.....
};
以上四个结构体就是最为关键的四个结构体,此外,还有其他的结构体
i2c_msg表示i2c数据包,数据在核心层杯打包成数据包的形式。
struct i2c_msg {//表示i2c数据包
__u16 addr; /* 设备地址,表示该数据应该传送到哪一个设备地址 */
__u16 flags;//表示数据包时读还是写1-读 0-写
__u16 len; /* 有效数据长度 */
__u8 *buf; /* 有效数据指针 */
}
/*表示一个驱动*/
struct device_driver {
/*设备驱动程序的名称。在调用driver_register()往设备驱动程序模型中插入一个新的
device_driver对象时,driver_register()函数会调用bus_add_driver()函数,
bus_add_driver()调用kobject_set_name()函数将name赋给drv>kobj.name或者
drv->kobj.k_name。注:drv为要调用driver_register()注册的device_driver类型
的对象。*/
const char *name;
struct bus_type *bus; //指向总线描述符的指针,总线连接所支持的设备
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);//探测
int (*remove) (struct device *dev);//移除
void (*shutdown) (struct device *dev);//关闭
int (*suspend) (struct device *dev, pm_message_t state);//挂起
int (*resume) (struct device *dev);//激活
const struct attribute_group **groups;
const struct dev_pm_ops *pm;/*操作函数集合*/
struct driver_private *p;/*指向设备私密数据*/
};
/*表示一个设备用于描述设备相关的信息设备之间的层次关系,以及设备与总线、驱动的关系。*/
struct device {//
struct device *parent;/*表示总线节点,例如TP父节点位I2C*/
struct device_private *p;/*指向设备私密数据*/
/*实现了基本的面向对象管理机制,是构成Linux2.6设备模型的核心结构。它与sysfs文件系统
紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录。类似于C++中的
基类,Kobject常被嵌入于其他类型(即:容器)中。如bus,devices,drivers都是典型的容器。
这些容器通过kobject连接起来,形成了一个树状结构。详情链接:
https://blog.youkuaiyun.com/weixin_42261068/article/details/116954707?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166554118616782412565338%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=166554118616782412565338&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~pc_rank_34-3-116954707-null-null.142^v52^pc_rank_34_1,201^v3^add_ask&utm_term=struct%20kobject%20kobj%3B&spm=1018.2226.3001.4187
*/
struct kobject kobj;/*内嵌的kobject结构,主要用于计数*/
const char *init_name; /* initial name of the device */
/*device_type 表示设备的类型。一个设备类或者总线可以包含不同类型的设备,例如“分区
”和“磁盘” ,“鼠标”和“事件” 。device_type就可以标识某个设备类型和该设备的特有信息,
它就等同于kobject结构中的kobj_type一样。如果name数据成员被指定,那么uevent成员函
数就会把它包含在DEVTYPE变量中。*/
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /*指向总线 type of bus device is on */
struct device_driver *driver; /*which driver has allocated this
device */
void *platform_data; /*设备树文件信息, platform私有数据
Platform specific data, device
core doesn't touch it */
void *driver_data; /* 驱动私有数据
Driver data, set and get with
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* 指向dma屏蔽字dma mask (if dma'able device) */
u64 coherent_dma_mask;/* 设备一致性DMA的屏蔽字
Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;/*指向设备所使用的一致性DMA存储
器描述符的指针*/
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
struct removed_region *removed_mem;
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;/**/
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);/*设备释放*/
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
};
二 设备驱动层
i2c_dev.c
i2c_MAJOR = 123
static int __init i2c_dev_init(void)
/*注册设备号*/
--->register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
/*创建类*/
class_create(THIS_MODULE, "i2c-dev");
/*绑定存在的适配器,每找到一个设备,都会调用i2cdev_attach_adapter函数,*/
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
/*创建设备节点*/
--->cdev_init();cdev_add();device_create()
三 设备驱动重要函数(linux/i2c.h)
/*发送I2C数据*/
int i2c_master_send(const struct i2c_client *client, const char *buf,
int count);
/*接受I2C数据*/
int i2c_master_recv(const struct i2c_client *client, char *buf,
int count);
/*传送一个I2C数据数据包,当注册完成后,发送数据时,则使用该函数来发送信息*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* 注册、注销适配器信息,在注册函数中做了三件事,1、绑定总线,2、向总线注册驱动,3、
遍历总线设备。*/
int i2c_add_adapter(struct i2c_adapter *);
void i2c_del_adapter(struct i2c_adapter *);
int i2c_add_numbered_adapter(struct i2c_adapter *);
/*注册注销i2c驱动*/
int i2c_register_driver(struct module *, struct i2c_driver *);
void i2c_del_driver(struct i2c_driver *);
/*注册、注销设备,注册i2c设备,将i2c加入总线的设备链表中,调用总线的匹配函数判断是
够匹配,如果匹配,就调用驱动的probe函数*/
struct i2c_client *i2c_new_device(struct i2c_adapter *adap,
struct i2c_board_info const *info);
void i2c_unregister_device(struct i2c_client *);
四、应用例子
该例子时调用linux自带的i2c设备驱动,
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 //命令
/*
*argv[1]:filename:"/dev/i2c-%d"
*argv[2]:write \ read
*argv[3]:write data
*/
int main(int argc, char **argv)
{
int fd;
int device_addr = 0x50;
char register_addr = 0x3;
int res;
char wbuf[10];
char rbuf[10];
char *operation_type = &argv[2];
/*open device*/
file = open(argv[1], O_RDWR);
if (file < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
perror("open failed!\n")
exit(1);
}
/*sure the device address,相当于与设备握手*/
if (ioctl(fd, I2C_SLAVE, device_addr ) < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
perror("ioctl failed!\n")
exit(1);
};
if(strcmp(*operation_type, "write") == 0)
{
/* Using I2C Write, equivalent of
i2c_smbus_write_word_data(file, reg, 0x6543) */
wbuf[0] = register_addr ;
wbuf[1] = atoi(argv[3]);
if (write(fd, wbuf, 2) != 2) {
/* ERROR HANDLING: i2c transaction failed */
perror("write failed!\n")
exit(1);
}
}
else
{
/* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
if (write(fd, ®ister_addr , 1) != 1){
/* ERROR HANDLING: i2c transaction failed */
perror("write failed!\n")
exit(1);
}
if (read(fd, rbuf, 1) != 1) {
/* ERROR HANDLING: i2c transaction failed */
perror("readfailed!\n")
exit(1);
} else {
printf("read data = %d\n", rbuf);
}
}
return 0;
}
五、IIC应用例子流程
app : fd = open("/dev/i2c-0",oRDWR)
===================================
i2c-dev.c
--->file_operation.open() = i2cdev_open;
--->minor=iminor(inode) /*获取次设备号*/
adap = i2c_get_adapter(minor); /*获取适配器*/
/*绑定适配器和设备*/
app:ioctl(fd, I2C_SLAVE, device_addr )
====================================
i2c-dev.c
--->file_operation.unlocked_ioctl = i2cdev_ioctl
--->switch (cmd) { /*判断命令*/
case I2C_SLAVE:
client->addr = arg;/*指定client设备地址*/
六、I2C总线驱动层(I2C适配器驱动层)
在i2c/busses/目录下的文件中,注册i2c平台驱动(i2c-0,i2c-1…)平台不同 ,调用的文件不同,在设备树中存放设备信息,在内核初始化时,解析设备树,创建设备,当平台驱动初始化完成后,即将平台驱动加入到 调用总线中的match即匹配设备,按照平台设备名字和平台驱动id_table名字匹配,即调用probe。
七、具体流程总结
在开始时,先注册一个驱动和一个设备(来自设备树),将驱动和设备都添加到i2c总线,i2c总线维护着这两个链表,一个总线链表,一个设备链表,当添加驱动或者设备时,就会去匹配对应的设备或者驱动,当匹配后,则调用probe 函数,然后就在probe函数里注册字符设备、创建节点等
<img title="" src="assets/2022-10-31-09-42-25-image.png" alt="" width="456" data-align="center">