玉树wap网站建设公司,网站设计基础,网站seo优化排名,宁波企业如何建网站目录
1 概述
2 I2c子系统框架
3 I2C的使用流程
3.1 在驱动里使用
3.2 在应用层使用
3.3 I2ctool的使用
4 为硬件i2c注册一个适配器
5 i2c子系统源码流程分析
5.1 i2c device与driver绑定过程
5.1.1 Driver的注册与处理
5.1.2 Client device的生成
5.2 I2c的发送与接…目录
1 概述
2 I2c子系统框架
3 I2C的使用流程
3.1 在驱动里使用
3.2 在应用层使用
3.3 I2ctool的使用
4 为硬件i2c注册一个适配器
5 i2c子系统源码流程分析
5.1 i2c device与driver绑定过程
5.1.1 Driver的注册与处理
5.1.2 Client device的生成
5.2 I2c的发送与接收
5.3 i2c总线设备生成过程
6 i2c作为slave的使用 1 概述 本文主要描述了i2c的使用以及分析了大部分i2c子系统源码和实现原理本文以读者对i2c硬件原理已掌握为基础来描述需要读者理解基础的i2c通信过程。 2 I2c子系统框架 从分层角度上看i2c子系统大致分为设备驱动层client、i2c核心层和i2c适配器层如下图大致描述了整个i2c应用和内核框架的关系逻辑从上到下用户可通过底层提供的总线设备或者外设设备来访问挂载在总线上的i2c设备。 i2c子系统向驱动层提供了i2c client每一个i2c设备将被实现成一个client设备驱动在拿到i2c_client后即可通过该对象来读写i2c数据访问i2c设备。I2c核心层向下也提供了i2c适配器层每一个硬件i2c都被实现成一个i2c adapter主要负责向i2c核心层提供硬件操作接口 3 I2C的使用流程 本节讲i2c在驱动层如何被调用使用没有特殊说明均认为i2c作为master后面章节将介绍i2c作为slave的用法。 3.1 在驱动里使用 在i2c的驱动应用中比较常见的是先在设备树里的i2c节点挂在硬件上挂在到该i2c总线的i2c设备例如一个sensor的节点
i2c2 {status okay;
...ov5695: ov569536 {compatible ovti,ov5695;reg 0x36;avdd-supply vcc2v8_dvp;clocks cru SCLK_CIF_OUT;clock-names xvclk;dvdd-supply vcc1v5_dvp;dovdd-supply vcc1v8_dvp;pinctrl-names default;pinctrl-0 cif_clkout_m0 mipi_pdn;reset-gpios gpio2 RK_PB6 GPIO_ACTIVE_LOW;...};
}; 然后在sensor的驱动里调用i2c_register_driver将自己定义好的struct i2c_driver传入该接口i2c总线将会调用我们自定义好的probe函数让设备驱动程序加载起来有时也会用i2c注册宏module_i2c_driver来做。
static struct i2c_driver ov5695_i2c_driver {.driver {.name ov5695,.pm ov5695_pm_ops,.of_match_table of_match_ptr(ov5695_of_match),},.probe ov5695_probe,.remove ov5695_remove,
};
module_i2c_driver(ov5695_i2c_driver);
----------------------或者-----------------------
static int mpu6050_driver_init(void)
{i2c_add_driver(mpu6050_driver);return 0;
}
static void mpu6050_driver_exit(void)
{i2c_del_driver(mpu6050_driver);
}
module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit); 之后将进入probe函数传入的i2c_client结构体用于i2c数据收发
static int ov5695_probe(struct i2c_client *client)
{
}
static int ov5695_read_reg(struct i2c_client *client, u16 reg, unsigned int len, u32 *val)
{struct i2c_msg msgs[2];...int ret;if (len 4)return -EINVAL;data_be_p (u8 *)data_be;/* Write register address */msgs[0].addr client-addr;msgs[0].flags 0;msgs[0].len 2;msgs[0].buf (u8 *)reg_addr_be;/* Read data from register */msgs[1].addr client-addr;msgs[1].flags I2C_M_RD;msgs[1].len len;msgs[1].buf data_be_p[4 - len];ret i2c_transfer(client-adapter, msgs, ARRAY_SIZE(msgs));...return 0;
}
3.2 在应用层使用 I2c子系统同时提供了每个i2c总线的设备应用可以直接打开i2c总线设备传入从机地址来进行通信
int fd open(/dev/i2c-0, O_RDWR);
int i2c_write(uint8_t slave, uint8_t reg, uint8_t * data, int len)
{unsigned char buf[1024];struct i2c_rdwr_ioctl_data i2c_data;struct i2c_msg i2c_msg;i2c_data.nmsgs 1;i2c_data.msgs i2c_msg ;ioctl(_fd, I2C_TIMEOUT, 1);ioctl(_fd, I2C_RETRIES, 2);memset(buf, 0, 1024);buf[0] reg;memcpy(buf[1], buf, len);i2c_data.msgs[0].addr slave;i2c_data.msgs[0].flags 0; i2c_data.msgs[0].buf buf[0];i2c_data.msgs[0].len len1;int ret ioctl(fd, I2C_RDWR, (unsigned long)i2c_data);...return 0;
}
int i2c_read(int8_t slave, int8_t reg, uint8_t * data, int len)unsigned char buf[2];struct i2c_rdwr_ioctl_data i2c_data;struct i2c_msg i2c_msg[2] ;i2c_data.nmsgs 2;i2c_data.msgs i2c_msg[0] ;ioctl(_fd, I2C_TIMEOUT, 1);ioctl(_fd, I2C_RETRIES, 2);buf[0] reg ;i2c_data.msgs[0].addr slave;i2c_data.msgs[0].flags 0; i2c_data.msgs[0].buf buf[0];i2c_data.msgs[0].len 1;i2c_data.msgs[1].addr slave;i2c_data.msgs[1].flags 1; i2c_data.msgs[1].buf data;i2c_data.msgs[1].len len;int ret ioctl(_fd, I2C_RDWR, (unsigned long)i2c_data);....
}
close(fd); 3.3 I2ctool的使用 有时在调试时会使用i2ctool命令行进行测试使用这些命令行接口需要先把该库打包进行文件系统或者移植。 i2cdetect用于扫描 i2c 总线上的设备并显示地址。 i2cset设置i2c设备某个寄存器的值。 i2cget读取i2c设备某个寄存器的值。 i2cdump读取某个i2c设备所有寄存器的值。 i2ctransfer一次性读写多个字节。
注参考https://blog.csdn.net/yyz_1987/article/details/131953108 驱动中常用接口 //发送接收消息
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
static inline int i2c_master_recv(const struct i2c_client *client,char *buf, int count)
static inline int i2c_master_send(const struct i2c_client *client,const char *buf, int count) //注册与注销
#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver) 另外i2c还提供了SMBus设备相关接口不在此文讨论范围。 4 为硬件i2c注册一个适配器 如何注册一个i2c adapter 以rockchip为例子分析rockchip注册一个adapter源码实现在drivers/i2c/busses/i2c-rk3x.c
设备树对i2c2的定义如下
i2c2: i2cff1a0000 {compatible rockchip,px30-i2c, rockchip,rk3399-i2c;reg 0x0 0xff1a0000 0x0 0x1000;clocks cru SCLK_I2C2, cru PCLK_I2C2;clock-names i2c, pclk;interrupts GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH;pinctrl-names default;pinctrl-0 i2c2_xfer;#address-cells 1;#size-cells 0;status disabled;}; 将和驱动匹配
static struct platform_driver rk3x_i2c_driver {.probe rk3x_i2c_probe,.remove_new rk3x_i2c_remove,.driver {.name rk3x-i2c,.of_match_table rk3x_i2c_match,.pm rk3x_i2c_pm_ops,},
};
module_platform_driver(rk3x_i2c_driver); 注册过程
rk3x_i2c_probe
struct rk3x_i2c *i2c;
//adapter结构体的初始化其中rk3x_i2c_algorithm是硬件接口后面分析strscpy(i2c-adap.name, rk3x-i2c, sizeof(i2c-adap.name));i2c-adap.owner THIS_MODULE;i2c-adap.algo rk3x_i2c_algorithm;i2c-adap.retries 3;i2c-adap.dev.of_node np;i2c-adap.algo_data i2c;
i2c-adap.dev.parent pdev-dev;
//后续大部分是获取i2c设备节点的信息包括基地址、中断申请、时钟获取和配置等
i2c-regs devm_platform_ioremap_resource(pdev, 0);
ret devm_request_irq(pdev-dev, irq, rk3x_i2c_irq,0, dev_name(pdev-dev), i2c);
ret clk_prepare(i2c-clk);
//最后调用i2c子系统api注册一个adapter
ret i2c_add_adapter(i2c-adap);
------------------------------------------------------------------------------
注册到adapter的硬件回调如下这些将提供给i2c子系统操作硬件的接口主要是发送与接收数据
static const struct i2c_algorithm rk3x_i2c_algorithm {.master_xfer rk3x_i2c_xfer,//发送与接收.master_xfer_atomic rk3x_i2c_xfer_polling,//原子发送接收.functionality rk3x_i2c_func,//当前i2c 适配器支持哪些特性
}; 以上便是一个i2c adapter的注册过程比较简单需要注意的是在设备树定义了多个i2c节点是这个driver将被调用多次即将申请多个adapter这里一个硬件i2c就申请一个adapter。
i2c0: i2cff180000 {compatible rockchip,px30-i2c, rockchip,rk3399-i2c;...};i2c1: i2cff190000 {compatible rockchip,px30-i2c, rockchip,rk3399-i2c;....};i2c2: i2cff1a0000 {compatible rockchip,px30-i2c, rockchip,rk3399-i2c;...};i2c3: i2cff1b0000 {compatible rockchip,px30-i2c, rockchip,rk3399-i2c;....}; 其他都是I2C硬件接口配置过程不做详细分析。 5 i2c子系统源码流程分析 I2c子系统源码实现在Linux内核的drivers/i2c下这里有芯片产商文件也有i2c子系统核心文件 drivers/i2c/i2c-boardinfo.c//i2c ip信息整理接口 drivers/i2c/i2c-core-acpi.c//acpi设备接口 drivers/i2c/i2c-core-base.c//核心文件 drivers/i2c/i2c-core-of.c//设备树解析相关文件 drivers/i2c/i2c-core-slave.c//i2c从机接口 drivers/i2c/i2c-core-smbus.c/smbus设备相关接口 drivers/i2c/i2c-dev.c//i2c总线设备相关文件 5.1 i2c device与driver绑定过程 I2c子系统如何生成client device与client驱动匹配后调用client的probe函数的流程 5.1.1 Driver的注册与处理 以sensor节点为例子在设备树中有如下设备树i2c定义i2c节点相关信息定义还有定义在i2c2下的ov5695节点。 i2c2: i2cff1a0000 {compatible rockchip,px30-i2c, rockchip,rk3399-i2c;reg 0x0 0xff1a0000 0x0 0x1000;clocks cru SCLK_I2C2, cru PCLK_I2C2;clock-names i2c, pclk;interrupts GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH;pinctrl-names default;pinctrl-0 i2c2_xfer;#address-cells 1;#size-cells 0;status disabled;};
i2c2 {...ov5695: ov569536 {compatible ovti,ov5695;reg 0x36;avdd-supply vcc2v8_dvp;clocks cru SCLK_CIF_OUT;clock-names xvclk;dvdd-supply vcc1v5_dvp;dovdd-supply vcc1v8_dvp;pinctrl-names default;pinctrl-0 cif_clkout_m0 mipi_pdn;reset-gpios gpio2 RK_PB6 GPIO_ACTIVE_LOW;...};
}; 在Linux初始化的时候这里会被Linux解析成两个device且i2c2这个device下挂着ov5695这个deviceov5695的driver定义在drivers/media/i2c/ov5695.c看驱动注册部分
static struct i2c_driver ov5695_i2c_driver {.driver {.name ov5695,.pm ov5695_pm_ops,.of_match_table of_match_ptr(ov5695_of_match),},.probe ov5695_probe,.remove ov5695_remove,
};
module_i2c_driver(ov5695_i2c_driver);
MODULE_DESCRIPTION(OmniVision ov5695 sensor driver);
MODULE_LICENSE(GPL v2); 定义了一个name为ov5695 的sensor driver通过module_i2c_driver 注册成一个drivermodule_i2c_driver 声明
clude/linux/i2c.h
#define module_i2c_driver(__i2c_driver) \module_driver(__i2c_driver, i2c_add_driver, \i2c_del_driver)
#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)include/linux/device/driver.h
#define module_driver(__driver, __register, __unregister, ...) \
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 ov9282_driver_init(void)
{ return i2c_register_driver(THIS_MODULE, (ov9282_driver));
}
module_init(ov9282_driver_init);
static void __exit ov9282_driver_exit(void)
{ i2c_del_driver((ov9282_driver));
}
module_exit(ov9282_driver_exit); 即最后调用i2c_register_driver 来注册看这个函数如何注册一个driver的
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)drivers/i2c/i2c-core-base.cdriver-driver.owner owner;driver-driver.bus i2c_bus_type;//选择总线该总线在i2c子系统初始化时加载INIT_LIST_HEAD(driver-clients);
res driver_register(driver-driver);//将该driver注册到i2c_bus_type这个总线上。i2c_for_each_dev(driver, __process_new_driver);//检查是否已有对应device有则扫描该i2c设备是否在线。 I2c子系统对driver的注册比较简单整体来说就是将driver注册到名为i2c_bus_type 的总线上这条总线是在i2c子系统初始化的时候注册的
struct bus_type i2c_bus_type {.name i2c,.match i2c_device_match,.probe i2c_device_probe,.remove i2c_device_remove,.shutdown i2c_device_shutdown,
};
static int __init i2c_init(void) {drivers/i2c/i2c-core-base.cretval bus_register(i2c_bus_type);//注册一条platform总线
...
retval i2c_add_driver(dummy_driver);//添加一个dummy driver没有做任操作用来匹配adapter注册时生成的device
}
postcore_initcall(i2c_init);
module_exit(i2c_exit);
一旦总线匹配到device和对应driverprobe将被调用
static int i2c_device_probe(struct device *dev) {drivers/i2c/i2c-core-base.c
struct i2c_client *client i2c_verify_client(dev);//拿到dev绑定的clientif (driver-probe)status driver-probe(client);//调用driver的probe并传入client这里将调用ov5695里probe函数elsestatus -EINVAL;
} 上面分析了driver是如何注册以及如何被调用client如何传进driver的probe函数接下来分析i2c子系统是如何生成对应的device以匹配bus上的driver调用driver的probe函数的。 5.1.2 Client device的生成 来看此时i2c子系统是如何解析这个层级关系以及client如何生成。I2c子系统在添加一个adapter的时候就会轮询该i2c节点下所有的节点逐一生成client和device的。
int i2c_add_adapter(struct i2c_adapter *adapter)drivers/i2c/i2c-core-base.cstruct device *dev adapter-dev;//如下主要寻找i2c adapter记录在i2c_adapter_idr的序号如果用的是设备节点的方式则进如下if分支if (dev-of_node) {id of_alias_get_id(dev-of_node, i2c);if (id 0) {adapter-nr id;return __i2c_add_numbered_adapter(adapter);}}
//如果用不是设备树方式或者第一次创建将用随机分配id的方式进行id idr_alloc(i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, 0,GFP_KERNEL);return i2c_register_adapter(adapter);
__i2c_add_numbered_adapter(这里分析的是使用设备树方式)
记录adapter到i2c_adapter_idr后调用i2c_register_adapter
i2c_register_adapterdev_set_name(adap-dev, i2c-%d, adap-nr);//生成一个i2c-x的device匹配bus注册是dummy driveradap-dev.bus i2c_bus_type;adap-dev.type i2c_adapter_type;res device_register(adap-dev);//注册到i2c_bus_type总线。
of_i2c_register_devices(drivers/i2c/i2c-core-of.c)
bus of_node_get(adap-dev.of_node);//找到parent节点即i2c2这个节点for_each_available_child_of_node(bus, node) {//轮询i2c2节点下的所有子节点if (of_node_test_and_set_flag(node, OF_POPULATED))//该子节点是否被其他驱动使用了continue;client of_i2c_register_device(adap, node);//注册一个client device}of_node_put(bus);
of_i2c_register_device(drivers/i2c/i2c-core-of.c)ret of_i2c_get_board_info(adap-dev, node, info);//获取节点的数据client i2c_new_client_device(adap, info);//生成client device
of_i2c_get_board_info
//找出node这个子节点compatible标签里的值并去掉该值‘,’前面的值取后面的值在这里取到了ovti,ov5695只取, //后面的值最后type的值是ov5695if (of_alias_from_compatible(node, info-type, sizeof(info-type)) 0) {dev_err(dev, of_i2c: modalias failure on %pOF\n, node);return -EINVAL;
}
ret of_property_read_u32(node, reg, addr);//读取i2c设备的从机地址i2c_new_client_device(drivers/i2c/i2c-core-base.c)struct i2c_client *client;client kzalloc(sizeof *client, GFP_KERNEL);//申请一个i2c_client
//后续有很多client数据填充
client-adapter adap;
...
i2c_check_addr_validity(client-addr, client-flags);//检查地址有效性必须是7bit地址i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));//检查i2c设备是否在线
//如下是重点配置dev的属性client-dev.parent client-adapter-dev;client-dev.bus i2c_bus_type;//该dev要挂载的总线跟之前driver挂在在同一个总线上client-dev.type i2c_client_type;//将ov5695节点赋值到dev下同时和driver的compitable 一致所以这里将和上面注册driver匹配
client-dev.of_node of_node_get(info-of_node);client-dev.fwnode info-fwnode;
i2c_dev_set_name(adap, client, info);//设置device的名称status device_register(client-dev);//注册到总线此时driver的probe函数将被调用。如上分析了client device的生成以及如何和driver匹配使得driver驱动被加载。 5.2 I2c的发送与接收 I2c的接收发送接口最终都调用到adapter提供的硬件通信接口
i2c_master_sendinclude/linux/i2c.h
i2c_transfer_buffer_flagsdrivers/i2c/i2c-core-base.c
组装msg
struct i2c_msg msg {.addr client-addr,.flags flags | (client-flags I2C_M_TEN),.len count,.buf buf,};
i2c_transfer
申请总线锁
__i2c_transfer
分原子操作和非原子操作接口取决于硬件adapter是否实现了原子操作
adap-algo-master_xfer_atomic
adap-algo-master_xfer adapter的硬件接口
--------------------------------------------------------
i2c_transfer
__i2c_transfer
adap-algo-master_xfer adapter的硬件接口5.3 i2c总线设备生成过程 总线设备的生成主要实现在drivers/i2c/i2c-dev.c这个文件里。 static int __init i2c_dev_init(void) {...res register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, i2c);//申请设备号范围i2c_dev_class class_create(i2c-dev);//创建class...
//如果有设备注册到总线i2c_bus_typei2cdev_notifier会立即被回调回调下面分析res bus_register_notifier(i2c_bus_type, i2cdev_notifier);...
//这里实际上跟上一句话目的一样如果此时已经有设备注册则轮询所有已注册设备绑定adapter创建总线设备。i2c_for_each_dev(NULL, i2c_dev_attach_adapter);...
}
module_init(i2c_dev_init); 回调过程
i2c_dev_attach_adapter / i2cdev_notifier_call-i2cdev_attach_adapter
i2cdev_attach_adapter
i2c_dev get_free_i2c_dev(adap);//获取未注册过的i2c dev
cdev_init(i2c_dev-cdev, i2cdev_fops);//初始化操作集相关
res dev_set_name(i2c_dev-dev, i2c-%d, adap-nr);// /dev下的设备名称nr是适配器序号即设备树i2cxres cdev_device_add(i2c_dev-cdev, i2c_dev-dev);//生成该设备
其中操作集的定义如下
static const struct file_operations i2cdev_fops {.owner THIS_MODULE,.llseek no_llseek,.read i2cdev_read,.write i2cdev_write,.unlocked_ioctl i2cdev_ioctl,.compat_ioctl compat_i2cdev_ioctl,.open i2cdev_open,.release i2cdev_release,
};
操作集都将调用i2c子系统的发送接收接口最终调用adapter的硬件接口
---------------
i2cdev_read
i2c_master_recv
-------------
i2cdev_write
i2c_master_send
-------------
i2cdev_ioctl
i2cdev_ioctl_rdwr
i2c_transfer6 i2c作为slave的使用 I2c子系统对salve的实现是在drivers/i2c/i2c-core-slave.c提供了三个api。 注册一个slave
int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb) 释放
int i2c_slave_unregister(struct i2c_client *client) 有数据请求时将被调用由控制器驱动实现最终调用用户在i2c_slave_register注册的slave_cb回调。
int i2c_slave_event(struct i2c_client *client,enum i2c_slave_event event, u8 *val)
检查当前设备树配置的设备是否是从机设备
bool i2c_detect_slave_mode(struct device *dev)