基于linux的I2C驱动与调试(传统ID匹配方式)
- 1.Linux I2C驱动框架
-
- 1.1. I2C驱动的主要对象
-
- 1.1.1. I2C总线
- 1.1.2. I2C设备
- 1.1.3. I2C驱动
- 1.1.4. I2C适配器
- 1.1.5. 总结一下
- 1.2. I2C框架分析
- 1.3. I2C流程分析
- 2.Linux I2C驱动框架源码剖析
-
- 2.1.注册I2C设备
- 2.2.注册I2C驱动
- 2.3. I2C适配器的构建
- 2.4. I2C数据传输
- 3.I2C背景了解
-
- 3.1.物理接口
- 3.2通信特征
- 4. I2C时序图
-
- 4.1. I2C起始信号
- 4.2. I2C终止信号
- 4.3. I2C应答信号
- 4.4. I2C写时序
- 4.5. I2C读时序
- 5. I2C实际操作图解
-
- 5.1.写操作
- 5.2.读操作
- 5.3. 驱动代码
- 6.总结
1.Linux I2C驱动框架 I2C驱动框架可以分为四部分,I2C核心、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核心中。
文章图片
1.1. I2C驱动的主要对象 2C总线用于管理I2C设备和I2C驱动,维护一个设备链表和驱动链表,定义了设备和驱动的匹配规则,定义了匹配成功后的行为,其在内核中的定义如下。
1.1.1. 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,
.pm= &i2c_device_pm_ops,
};
1.1.2. I2C设备
I2C设备描述了I2C设备的硬件信息,例如I2C设备的地址、I2C设备在接在哪一个I2C控制器上,其结构体定义如下
struct i2c_client {
unsigned short flags;
/* div., see below*/
unsigned short addr;
/* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits*/
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;
/* the adapter we sit on */
struct i2c_driver *driver;
/* and our access routines */
struct device dev;
/* the device structure*/
int irq;
/* irq issued by device*/
struct list_head detected;
};
1.1.3. I2C驱动
I2C驱动是I2C设备的驱动程序,用于匹配I2C设备,其结构体定义如下。
struct i2c_driver {
...
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration*/
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
...
struct list_head clients;
};
1.1.4. I2C适配器
I2C适配器是SOC上的I2C控制器的软件抽象,可以通过其定义的算法向硬件设备传输数据,其结构体定义如下。
struct i2c_adapter {
struct module *owner;
unsigned int class;
/* classes to allow probing for */
const struct i2c_algorithm *algo;
/* the algorithm to access the bus */
void *algo_data;
...
struct device dev;
/* the adapter device */
...
};
【Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)】其中的i2c_algorithm表示算法,用于向硬件设备传输数据,其定义如下。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
1.1.5. 总结一下
I2C驱动的主要对象是I2C总线、I2C设备、I2C驱动、I2C适配器
I2C总线用于管理I2C设备和I2C驱动
I2C设备描述了I2C设备的硬件信息
I2C驱动是I2C设备对应的驱动程序
I2C适配器是SOC上的I2C控制器,其定义了算法,可以向I2C硬件设备传输数据
其中直接面向编写I2C设备驱动的开发者的是I2C设备和I2C驱动,I2C总线和I2C适配器是幕后工作者
1.2. I2C框架分析 I2C核心维护着一条I2C总线,提供了注册I2C设备、I2C驱动、I2C适配器的接口。
I2C总线维护着一条设备链表和驱动链表,当向I2C核心层注册设备时,会将其添加到总线的设备链表中,然后遍历总线上的驱动链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
当注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,然后遍历总线的设备链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
在I2C驱动程序中,通过I2C适配器中的算法向I2C硬件设备传输数据。
1.3. I2C流程分析
文章图片
2.Linux I2C驱动框架源码剖析 2.1.注册I2C设备 (1)注册I2C适配可以通过i2c_new_device,此函数会生成一个i2c_client,指定对应的总线为I2C总线,然后向总线注册设备。
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL);
client->dev.bus = &i2c_bus_type;
...
status = device_register(&client->dev);
}
(2)看一下其中的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,
.pm= &i2c_device_pm_ops,
};
(3)下面再来看看device_register向总线注册设备过程中会发生什么。device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数,下面看一看源码分析。
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
int device_add(struct device *dev)
{
...
error = bus_add_device(dev);
...
bus_probe_device(dev);
....
}
(4)其中bus_add_device函数会将设备添加到总线的设备链表中,如下。
int bus_add_device(struct device *dev)
{
....
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
...
}
(5)bus_probe_device函数会遍历总线的驱动链表,如下。
void bus_probe_device(struct device *dev)
{
....
if (bus->p->drivers_autoprobe) {
ret = device_attach(dev);
WARN_ON(ret < 0);
}
...
}
int device_attach(struct device *dev)
{
...
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
...
}
(6)bus_for_each_drv(dev->bus, NULL, dev, __device_attach); 会遍历总线的驱动链表的每一项,然后调用__device_attach。
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
if (!driver_match_device(drv, dev))
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
driver_match_device函数会判断设备和驱动是否匹配,如果匹配就调用driver_probe_device。
(7)首先来看一看driver_match_device函数的定义。
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
(8)发现它调用了总线的match函数,这里的总线在注册I2C设备的时候已经被设置为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,
.pm= &i2c_device_pm_ops,
};
(9)所以这里会调用到i2c_device_match函数,i2c_device_match会通过I2C驱动的id_table中每一的name和I2C设备的name进行匹配。
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
(10)如果匹配成功会调用driver_probe_device,下面再来看看driver_probe_device,driver_probe_device函数最终会先调用到I2C总线的probe函数,然后再调用I2C驱动的probe函数。
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_runtime_put_sync(dev);
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
...
}
(11)总线的probe函数为i2c_device_probe,此函数会调用驱动的probe函数
static int i2c_device_probe(struct device *dev)
{
...
status = driver->probe(client, i2c_match_id(driver->id_table,
...
}
2.2.注册I2C驱动 (1) 可以通过i2c_add_driver注册I2C驱动,该函数会指定驱动对应的总线为I2C总线,然后向总线注册驱动。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
...
driver->driver.bus = &i2c_bus_type;
...
res = driver_register(&driver->driver);
...
}
(2) i2c_bus_type的定义如下。
struct bus_type i2c_bus_type = {
.name= "i2c",
.match= i2c_device_match,
.probe= i2c_device_probe,
.remove= i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm= &i2c_device_pm_ops,
};
(3) driver_register函数遍历总线的设备链表进行操作,然后将驱动添加到总线的驱动链表中。
int driver_register(struct device_driver *drv)
{
...
ret = bus_add_driver(drv);
...
}
int bus_add_driver(struct device_driver *drv)
{
...
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister;
}
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
}
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
(4)下面来看一看__driver_attach函数,此函数会判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数。
static int __driver_attach(struct device *dev, void *data)
{
...
if (!driver_match_device(drv, dev))
return 0;
...
if (!dev->driver)
driver_probe_device(drv, dev);
}
2.3. I2C适配器的构建 对于三星平台,在drivers\i2c\busses\i2c-s3c2410.c文件中构建并注册了I2C适配器,这是三星平台I2C控制器的驱动。
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer= s3c24xx_i2c_xfer,
.functionality= s3c24xx_i2c_func,
};
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
...
i2c->adap.algo= &s3c24xx_i2c_algorithm;
...
ret = i2c_add_numbered_adapter(&i2c->adap);
...
}
其中的s3c24xx_i2c_algorithm中的s3c24xx_i2c_xfer就是通过操作寄存器来通过I2C控制器传输数据。
2.4. I2C数据传输 上面介绍I2C数据传输是通过I2C适配器完成的,下面来分析一下源码,在I2C驱动中,使用i2c_transfer来传输I2C数据,此函数肯定是通过I2C适配器的算法进行操作的,如下。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
....
ret = adap->algo->master_xfer(adap, msgs, num);
...
}
3.I2C背景了解 3.1.物理接口
1)SCL(serial clock ):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道;
2)SDA(serial data):数据线,通信数据都通过SDA线传输。
3.2通信特征
1)I2C 属于串行通信,所有的数据以位为单位在SDA线上串行传输。2)同步通信就是通信双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线传输自己的时钟给B,B工作在A传输的时钟下,所以同步通信的显著特征就是:通信线中有CLK。3)非差分。因为I2C速率不高且通信双方距离很近,所以使用电平信号通信。4)低速率。 I2C一般是用在板子上的2个IC之间的通信,而且用来传输的数据量不大,所以本身通信速率很低(几百KHz,不同的I2C芯片的通信速率可能不同,具体在编程的时候要看自己使用的设备允许的I2C通信最高速率,不能超过这个速率)
4. I2C时序图 4.1. I2C起始信号
1) SCL为高电平的时候,SDA由高电平向低电平跳变。
文章图片
4.2. I2C终止信号
1) 终止信号:SCL为高电平的时候,SDA由低电平向高电平跳变。
文章图片
4.3. I2C应答信号
1)I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功,对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
文章图片
4.4. I2C写时序
1) 开始信号:主机+从设备地址+写命令,从机应答,应答成功,表示有这个设备,然后主机+设备内部寄存器地址,此时不用再加写命令控制字,从机应答,应答成功,表示设备内有这个地址,主机写入数据,从机应答,是否继续发送,不发送的话,发送停止信号P。
文章图片
4.5. I2C读时序
1) 首先要知道将要所读取设备的地址告诉从设备,从设备才能将数据放到(发送)SDA上使主设备读取,从设备将数据放入SDA上的过程,由硬件主动完成,不用人为的写入。所以首先先写入从机地址,然后+写控制命令,从机应答,应答成功,表示有这个设备,然后写入内部寄存器地址,此时不用再加写命令控制字,从机应答,应答成功,表示设备内有这个地址。然后主机继续发出:写入从机地址,然后+读命令,从机应答,应答成功,此时便可以读取数据了,从设备已经将数据放入到SDA上了。
文章图片
5. I2C实际操作图解
1)首先本次操作是通过I2C读写CPLD的一个实验。第一,我们进行一个写数据操作,I2C从设备地址位0x16,CPLD寄存器地址为0x13,写入CPLD的数据为0x2;然后在读取写入CPLD中寄存器的值。
5.1.写操作
文章图片
由上图所示,我可以得到从设备的地址为0x16,为写操作写入的高八位数据为0x0。
文章图片
由上图所示,我可以得到写操作写入的低八位数据为0x13,接着写入的高八位数据为0x00。
文章图片
由上图所示,我们可以得到写入的低八位数据为0x2,然后终止传输信号。
5.2.读操作
文章图片
由上图所示,我可以得到从设备的地址为0x16,为写操作写入的高八位数据为0x0。
文章图片
由上图所示,我可以得到写操作写入的低八位数据为0x13,然后重新发送起始型号,发送从设备地址0x16,然后读取数据。
文章图片
由上图所示,我可以读取到高八位数据:0x0,接着为读取到的第八位数据:0x2,然后终止传输信号。
5.3. 驱动代码
- 以下代码为nst175温度传感器的代码.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include #define I2C_MASTER_RD 0x0001 //read data, from slave to master
#define DEVICE_NAME "sensor"//设备名:srnsor
#define DEVICE_MINOR_NUM 1//次设备号数struct i2c_nst175_command {
u16address;
u16data_buff;
};
struct nst175_device{
dev_t devid;
//设备ID
struct cdev nst175_cdev;
//字符设备结构体
struct class *nst175_class;
//类
struct device *device;
//创建设备
int major;
//主设备
int minor;
//从设备
int private_data[2];
//私有数据,用于存放从设备地址
};
static struct i2c_client *this_client;
static struct nst175_device nst175dev;
//------------------------I2C读取NST175数据------------------------
static int i2c_nst175_rxdata(int saddr,char *regaddr,int regaddr_len,char *rxdata, int data_len)
{
int ret;
struct i2c_msg msgs[] = {
{
.addr= saddr,
.flags = 0,
.len= regaddr_len,
.buf= regaddr,
},
{
.addr= saddr,
.flags = I2C_MASTER_RD,
.len= data_len,
.buf= rxdata,
},
};
ret = i2c_transfer( this_client->adapter, msgs, 2);
if( ret < 0)
printk( "read error:%d.\n", ret);
return ret;
}//---------------------------打开操作---------------------------
static int nst175_open(struct inode *inode, struct file *filp)
{
return 0;
}//---------------------------控制操作---------------------------
static long nst175_ioctl( struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_nst175_command cmdframe;
int ret = 0;
switch( cmd) {
//-----------------------------nst175_1(板子标识号u8)-----------------------------
case 0:
copy_from_user((void *)&cmdframe, (void __user *)arg, sizeof( cmdframe));
ret = i2c_nst175_rxdata(nst175dev.private_data[0],(u8 *)&cmdframe.address,1,(u8 *)&cmdframe.data_buff,2);
copy_to_user( (void __user *)arg, (void *)&cmdframe, sizeof( cmdframe));
break;
//-----------------------------nst175_2(板子标识号u9)-----------------------------
case 1:
copy_from_user((void *)&cmdframe, (void __user *)arg, sizeof( cmdframe));
ret = i2c_nst175_rxdata(nst175dev.private_data[1],(u8 *)&cmdframe.address,1,(u8 *)&cmdframe.data_buff,2);
copy_to_user( (void __user *)arg, (void *)&cmdframe, sizeof( cmdframe));
break;
default:
printk( "invalid cmd %#x\n", cmd);
return -EINVAL;
}return ret;
}//---------------------------设备操作---------------------------
static struct file_operations nst175_fops =
{
.owner= THIS_MODULE,
.open= nst175_open,
.unlocked_ioctl = nst175_ioctl,
};
//---------------------------探针函数----------------------------
static int sensor_nst175_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int i,err;
int devno;
//-------------------------注册字符设备驱动--------------------------------- if (nst175dev.major) //创建设备号
{
//静态注册设备号
nst175dev.devid = MKDEV(nst175dev.major, 0);
register_chrdev_region(nst175dev.devid, DEVICE_MINOR_NUM,id->name);
}
else
{
//动态注册设备号
alloc_chrdev_region(&nst175dev.devid, 0, DEVICE_MINOR_NUM,id->name);
/* 申请设备号 */
nst175dev.major = MAJOR(nst175dev.devid);
/* 获取主设备号 */
nst175dev.minor = MINOR(nst175dev.devid);
/* 获取次设备号 */
} //创建类
nst175dev.nst175_class = class_create(THIS_MODULE,id->name);
if (IS_ERR(nst175dev.nst175_class)) {
return PTR_ERR(nst175dev.nst175_class);
} //设备初始化{
//数据初始化
nst175dev.nst175_cdev.owner = THIS_MODULE;
cdev_init(&nst175dev.nst175_cdev,&nst175_fops);
devno = MKDEV(nst175dev.major,nst175dev.minor+id->driver_data);
//注册到系统
err = cdev_add(&nst175dev.nst175_cdev,devno, 1);
if(err){
printk(KERN_EMERG "cdev_add is fail! %d\n",err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",nst175dev.minor+id->driver_data);
} //创建设备节点
device_create(nst175dev.nst175_class,NULL,devno,NULL,"%s",id->name);
this_client = client;
return 0;
}//--------------------------移除字符函数---------------------------
static int sensor_nst175_remove(struct i2c_client *client)
{
cdev_del(&nst175dev.nst175_cdev);
//删除字符设备
unregister_chrdev_region(nst175dev.devid, 1);
//注销字符设备
//注销掉类和设备
device_destroy(nst175dev.nst175_class, nst175dev.devid);
class_destroy(nst175dev.nst175_class);
return 0;
}//-----------------------传统匹配方式ID列表------------------------
static const struct i2c_device_id sensor_nst175_id[] = {
{"nst175_1", 0},
{"nst175_2", 1},
{/* END OF LIST */}
};
//-------------------------I2C板载设备信息-------------------------
static struct i2c_board_info sensor_nst175_i2c_board_info[] = {
{I2C_BOARD_INFO("nst175_1", 0x49),},
{I2C_BOARD_INFO("nst175_2", 0x4a),},};
//--------------------------i2c驱动结构体--------------------------
static struct i2c_driver sensor_nst175_driver = {
.probe = sensor_nst175_probe,
.remove = sensor_nst175_remove,
.id_table = sensor_nst175_id,
.driver = {
.owner = THIS_MODULE,
.name = "sensor_nst175",
},
};
//----------------------------加载函数-----------------------------
static int __init sensor_nst175_init(void)
{
int ret = 0,i;
struct i2c_adapter *i2c_adap = NULL;
struct i2c_client *client = NULL;
//获取adapter总线上的相应的I2C设备:I2C(0)
i2c_adap = i2c_get_adapter(0);
if(i2c_adap < 0)
{
printk("i2c_get_adapter() failed...\n");
}
//添加新的I2C设备
for(i=0;
iaddr;
}
//添加I2C驱动
ret = i2c_add_driver(&sensor_nst175_driver);
if(ret)
{
printk("i2c_add_driver() failed...\n");
}
return ret;
}//-----------------------------卸载函数-----------------------------
static void __exit sensor_nst175_exit(void)
{
printk(KERN_EMERG "sensor_nst175_exit!\n");
i2c_del_driver(&sensor_nst175_driver);
}module_init(sensor_nst175_init);
//入口函数
module_exit(sensor_nst175_exit);
//出口函数MODULE_LICENSE("GPL");
MODULE_AUTHOR("author:shzn");
6.总结 最后关于I2C的调试过程讲解就到这了,主要是想讲述一下调试中的过程,同时附带了驱动代码,欢迎大家一起来学习。。。
推荐阅读
- Linux-notes|Linux ftpget和ftpput命令
- Linux-notes|如何把uboot从SD卡烧到emmc
- Linux系统编程|Linux线程间通信之条件变量(十七)
- Linux|109 个实用 shell 脚本
- linux笔记|linux 常用命令汇总(面向面试)
- c语言|一文搞懂栈(stack)、堆(heap)、单片机裸机内存管理malloc
- Linux|Linux--网络基础
- linux|apt update和apt upgrade命令 - 有什么区别()
- linux|2022年云原生趋势