超硬干货:I2C最全教程,绝对不负期望!(万字长文,建议收藏)
扫描二维码
随时随地手机看文章
01
裸机操作篇
下图为从设备接收
模式
1-- I2C总线控制寄存器
IICCON寄存器用于控制是否发出ACK信号、设置发送器的时钟、开启I2C中断,并标识中断是否发生
使用IICCON寄存器时,有如下注意事项
1)、发送模式的时钟频率由位[6]、位[3:0]联合决定。另外,当IICCON[6]=0时,IICCON[3:0]不能取0或1。
2)、位[4]用来标识是否有I2C中断发生,读出为0时标识没有中断发生,读出为1时标识有中断发生。当此位为1时,SCL线被拉低,此时所以I2C传输停止;如果要继续传输,需写入0清除它。
中断在以下3种情况下发生:
1 -- 当发送地址信息或接收到一个从机地址并且吻合时;
2 -- 当总线仲裁失败时;
3 -- 当发送/接收完一个字节的数据(包括响应位)时;
3)、基于SDA、SCL线上时间特性的考虑,要发送数据时,先将数据写入IICDS寄存器,然后再清除中断。
4)、如果IICCON[5]=0,IICCON[4]将不能正常工作,所以,即使不使用I2C中断,也要将IICCON[5]设为1.
2 -- I2C状态寄存器
IICSTAT寄存器用于选择I2C接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机时是否被寻址、是否接收到0地址、是否接收到ACK信号等。
3 -- I2C数据发送/接收移位寄存器
fs4412的i2c总线上挂载了mpu6050
mpu6050每次读取或者要写入数据时,必须先告知从设备要操作的内部寄存器地址(RA),然后紧跟着读取或者写入数据(DATA),内部寄存器的配置和读取一次最多1个data,交互时序如下:
【注意】上述两个时序非常重要,后续我们要编写基于linux的驱动编写i2c_msg也要基于上述时序来实现。
【寄存器使用规则】
下面先提前讲一下具体应用中如何启动和恢复IIC的传输
启动或恢复4412的I2C传输有以下两种方法。
1) 当IICCON[4]即中断状态位为0时,通过写IICSTAT寄存器启动I2C操作。有以下两种情况。
1--在主机模式,
令IICSTAT[5:4]等于0b11,将发出S信号和IICDS寄存器的数据(寻址),
令IICSTAT[5:4]等于0b01,将发出P信号。
2--在从机模式,令IICSTAT[4]等于1将等待其他主机发出S信号及地址信息。
2)当IICCON[4]即中断状态为1时,表示I2C操作被暂停。在这期间设置好其他寄存器之后,向IICCON[4]写入0即可恢复I2C操作。所谓“设置其他寄存器”,有以下三种情况:
1--对于主机模式,可以按照上面1的方法写IICSTAT寄存器,恢复I2C操作后即可发出S信号和IICDS寄存器的值(寻址),或发出P信号。
2--对于发送器,可以将下一个要发送的数据写入IICDS寄存器中,恢复I2C操作后即可发出这个数据。
3--对于接收器,可以从IICDS寄存器读出接收到的数据。最后向IICCON[4]写入0的同时,设置IICCON[7]以决定是否在接收到下一个数据后是否发出ACK信号。
【MPU6050硬件电路图】(实际板子电路图不一定和下面一样,具体问题具体分析,本例参考exynos-fs4412开发板)
1 AD0接地的 值为 0
所以从设备地址为0x86
2 SCL、SDA连接的i2c_SCL5、i2c_SDA5
由此可得这两个信号线复用了GPIO的GPB的2、3引脚
3 查阅exynos4412 datasheet 6.2.2 Part 1可得
所以设置GPIO 的 GPB 【15:8】= 0x33 即可
下面是个IIC总线实例:
用IIC总线实现CPU与MPU-6050的数据查询
具体代码如下:
//****************************************
// MPU6050常用内部地址,以下地址在mpu6050内部
//****************************************
void mydelay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
/**********************************************************************
* @brief iic read a byte program body
* @param[in] slave_addr, addr, &data
* @return None
**********************************************************************/
void iic_read(unsigned char slave_addr, unsigned char addr, unsigned char *data)
{
/*根据mpu6050的datasheet,要读取数据必须先执行写操作:写入一个从设备地址,
然后执行读操作,才能读取到该内部寄存器的内容*/
I2C5.I2CDS = slave_addr; //将从机地址写入I2CDS寄存器中
I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5); //设置时钟并使能中断
I2C5.I2CSTAT = 0xf0; //[7:6]设置为0b11,主机发送模式;
//往[5:4]位写0b11,即产生启动信号,发出IICDS寄存器中的地址
while(!(I2C5.I2CCON & (1 << 4))); // 等待传输结束,传输结束后,I2CCON [4]位为1,标识有中断发生;
// 此位为1时,SCL线被拉低,此时I2C传输停止;
I2C5.I2CDS = addr; //写命令值
I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));// I2CCON [4]位清0,继续传输
while(!(I2C5.I2CCON & (1 << 4)));// 等待传输结束
I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位写0b01,发出停止信号
I2C5.I2CDS = slave_addr | 1; //表示要读出数据
I2C5.I2CCON = (1 << 7)|(1 << 6) |(1 << 5) ; //设置时钟并使能中断
I2C5.I2CSTAT = 0xb0;//[7:6]位0b10,主机接收模式;
//往[5:4]位写0b11,即产生启动信号,发出IICDS寄存器中的地址
// I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4)); 如果强行关闭,将读取不到数据
while(!(I2C5.I2CCON & (1 << 4)));//等待传输结束,接收数据
I2C5.I2CCON &= ~((1<<7)|(1 << 4));/* Resume the operation & no ack*/
// I2CCON [4]位清0,继续传输,接收数据,
// 主机接收器接收到最后一字节数据后,不发出应答信号 no ack
// 从机发送器释放SDA线,以允许主机发出P信号,停止传输;
while(!(I2C5.I2CCON & (1 << 4)));// 等待传输结束
I2C5.I2CSTAT = 0x90;
*data = I2C5.I2CDS;
I2C5.I2CCON &= ~(1<<4); /*clean interrupt pending bit */
mydelay_ms(10);
*data = I2C5.I2CDS;
}
/**********************************************************************
* @brief iic write a byte program body
* @param[in] slave_addr, addr, data
* @return None
**********************************************************************/
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
I2C5.I2CDS = slave_addr;
I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5) ;
I2C5.I2CSTAT = 0xf0;
while(!(I2C5.I2CCON & (1 << 4)));
I2C5.I2CDS = addr;
I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
while(!(I2C5.I2CCON & (1 << 4)));
I2C5.I2CDS = data;
I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
while(!(I2C5.I2CCON & (1 << 4)));
I2C5.I2CSTAT = 0xd0;
I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
mydelay_ms(10);
}
void MPU6050_Init ()
{
iic_write(SlaveAddress, PWR_MGMT_1, 0x00);
iic_write(SlaveAddress, SMPLRT_DIV, 0x07);
iic_write(SlaveAddress, CONFIG, 0x06);
iic_write(SlaveAddress, GYRO_CONFIG, 0x18);
iic_write(SlaveAddress, ACCEL_CONFIG, 0x01);
}
/*读取mpu6050某个内部寄存器的内容*/
int get_data(unsigned char addr)
{
char data_h, data_l;
iic_read(SlaveAddress, addr, &data_h);
iic_read(SlaveAddress, addr+1, &data_l);
return (data_h<<8)|data_l;
}
/*
* 裸机代码,不同于LINUX 应用层, 一定加循环控制
*/
int main(void)
{
int data;
unsigned char zvalue;
GPB.CON = (GPB.CON & ~(0xff<<8)) | 0x33<<8; // GPBCON[3], I2C_5_SCL GPBCON[2], I2C_5_SDA
mydelay_ms(100);
uart_init();
/*---------------------------------------------------------------------*/
I2C5.I2CSTAT = 0xD0;
I2C5.I2CCON &= ~(1<<4); /*clean interrupt pending bit */
/*---------------------------------------------------------------------*/
mydelay_ms(100);
MPU6050_Init();
mydelay_ms(100);
printf("\n********** I2C test!! ***********\n");
while(1)
{
//Turn on
data = get_data(GYRO_ZOUT_H);
printf(" GYRO --> Z <---:Hex: %x", data);
data = get_data(GYRO_XOUT_H);
printf(" GYRO --> X <---:Hex: %x", data);
printf("\n");
mydelay_ms(1000);
}
return 0;
}
实验结果如下:
********** I2C test!! ***********
GYRO --> Z <---:Hex: 1c GYRO --> X <---:Hex: feda
GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fed6
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fedc
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda
GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda
GYRO --> Z <---:Hex: fcf2 GYRO --> X <---:Hex: 202
GYRO --> Z <---:Hex: ec GYRO --> X <---:Hex: faa0
GYRO --> Z <---:Hex: 4c GYRO --> X <---:Hex: e
GYRO --> Z <---:Hex: fe GYRO --> X <---:Hex: fed8
GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: fede
GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: feda
读写操作代码解析:
写入一个数据流程
读数据流程
02
驱动篇-之基于linux的mpu6050驱动
03
驱动篇-之内核架构分析
通过前两章的讲解,大家对在裸机下读写mpu6050和基于Linux的内核I2C框架如何编写mpu6050驱动都已经有了一定了解。本文以linux3.14.0为参考, 讨论Linux中的i2c控制器驱动是的。
三星的i2c控制器驱动是基于platform总线实现的,struct platform_driver定义如下:
当设备树节点信息的compatible信息和注册的platform_driver.driver. of_match_table字符串会通过platform总线的macth方法进行配对,匹配成功后会调用probe函数s3c24xx_i2c_probe();
要理解i2c的内核架构首先必须了解一下这几个机构体:
s3c24xx_i2c
该结构体是三星i2c控制器专用结构体,描述了控制器的所有资源,包括用于等待中断唤醒的等待队列、传输i2c_msg的临时指针、记录与硬件通信的状态、中断号、控制器基地址、时钟、i2c_adapter、设备树信息pdata等。i2c控制器初始化的时候会为该控制器创建该结构体变量,并初始化之。
i2c_adapter
对象实现了一组通过一个i2c控制器发送消息的所有信息, 包括时序, 地址等等, 即封装了i2c控制器的"控制信息"。它被i2c主机驱动创建, 通过clien域和i2c_client和i2c_driver相连, 这样设备端驱动就可以通过其中的方法以及i2c物理控制器来和一个i2c总线的物理设备进行交互。
i2c_algorithm
描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中注册的函数master_xfer()最终被设备驱动端的i2c_transfer()回调。
i2c_msg
描述一个在设备端和主机端之间进行流动的数据, 在设备驱动中打包并通过i2c_transfer()发送。相当于skbuf之于网络设备,urb之于USB设备。
这几个结构体之间关系:
i2c_client
描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。从设备所挂载的i2c控制器会在初始化的时候保存到成员adapter。
i2c_driver
描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连。
如上图所示:Linux内核维护了i2c bus总线,所有的i2c从设备信息都会转换成i2c_client,并注册到i2c总线,没有设备的情况下一般填写在一下文件中:
linux-3.14-fs4412\arch\arm\mach-s5pc100\ Mach-smdkc100.c
内核启动会将i2c_board_info结构体转换成i2c_client。
有设备树的情况下,内核启动会自动将设备树节点转换成i2c_client。
i2c_adapter
我首先说i2c_adapter, 并不是编写一个i2c设备驱动需要它, 通常我们在配置内核的时候已经将i2c控制器的设备信息和驱动已经编译进内核了, 就是这个adapter对象已经创建好了, 但是了解其中的成员对于理解i2c驱动框架非常重要, 所有的设备驱动都要经过这个对象的处理才能和物理设备通信
//include/linux/i2c.h
428-->这个i2c控制器需要的控制算法, 其中最重要的成员是master_xfer()接口, 这个接口是硬件相关的, 里面的操作都是基于具体的SoC i2c寄存器的, 它将完成将数据发送到物理i2c控制器的"最后一公里"
436-->表示这个一个device, 会挂接到内核中的链表中来管理, 其中的
443-->这个节点将一个i2c_adapter对象和它所属的i2c_client对象以及相应的i2c_driver对象连接到一起
下面是2个i2c-core.c提供的i2c_adapter直接相关的操作API, 通常也不需要设备驱动开发中使用。
i2c控制器设备树节点信息通过platform总线传递下来,即参数pdev。probe函数主要功能是初始化adapter,申请i2c控制器需要的各种资源,同时通过设备树节点初始化该控制器下的所有从设备,创建i2c_client结构体。
ps3c24xx_i2c_probe
static int s3c24xx_i2c_probe(struct platform_device *pdev) { struct s3c24xx_i2c *i2c;//最重要的结构体 //保存设备树信息 struct s3c2410_platform_i2c *pdata = NULL; struct resource *res; int ret; if (!pdev->dev.of_node) { pdata = dev_get_platdata(&pdev->dev); if (!pdata) { dev_err(&pdev->dev, "no platform data\n"); return -EINVAL; } } /*为结构体变量i2c分配内存*/ i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); if (!i2c) { dev_err(&pdev->dev, "no memory for state\n"); return -ENOMEM; } i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!i2c->pdata) { dev_err(&pdev->dev, "no memory for platform data\n"); return -ENOMEM; } /*i2c控制器的一些特殊行为 #define QUIRK_S3C2440 (1 << 0) #define QUIRK_HDMIPHY (1 << 1) #define QUIRK_NO_GPIO (1 << 2) #define QUIRK_POLL (1 << 3) 其中bite:3如果采用轮训方式与底层硬件通信值为1,中断方式值为0*/ i2c->quirks = s3c24xx_get_device_quirks(pdev); if (pdata) memcpy(i2c->pdata, pdata, sizeof(*pdata)); else s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c); strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; /*为i2c_msg传输方法赋值,*/ i2c->adap.algo = &s3c24xx_i2c_algorithm; i2c->adap.retries = 2; i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; i2c->tx_setup = 50; //初始化等待队列,该等待队列用于唤醒读写数据的进程 init_waitqueue_head(&i2c->wait); /* find the clock and enable it */ i2c->dev = &pdev->dev; //获取时钟 i2c->clk = devm_clk_get(&pdev->dev, "i2c"); if (IS_ERR(i2c->clk)) { dev_err(&pdev->dev, "cannot get clock\n"); return -ENOENT; } dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); /* map the registers */ //通过pdev得到i2c控制器的寄存器地址资源 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //映射i2c控制器的物理基地址为虚拟基地址 i2c->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(i2c->regs)) return PTR_ERR(i2c->regs); dev_dbg(&pdev->dev, "registers %p (%p)\n", i2c->regs, res); /* setup info block for the i2c core */ /*将结构体变量i2c保存到i2c_adapter的私有变量指针algo_data, 编写i2c设备驱动可以通过adapter指针找到结构体i2c*/ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /* inititalise the i2c gpio lines */ //得到i2c复用的gpio引脚并初始化 if (i2c->pdata->cfg_gpio) { i2c->pdata->cfg_gpio(to_platform_device(i2c->dev)); } else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) { return -EINVAL; } /* initialise the i2c controller */ clk_prepare_enable(i2c->clk); /*将从设备地址写入寄存器S3C2410_IICADD,同时初始化时钟频率*/ ret = s3c24xx_i2c_init(i2c); clk_disable_unprepare(i2c->clk); if (ret != 0) { dev_err(&pdev->dev, "I2C controller init failed\n"); return ret; } /* find the IRQ for this unit (note, this relies on the init call to * ensure no current IRQs pending */ if (!(i2c->quirks & QUIRK_POLL)) { /*获得中断号*/ i2c->irq = ret = platform_get_irq(pdev, 0); if (ret <= 0) { dev_err(&pdev->dev, "cannot find IRQ\n"); return ret; } /*注册中断处理函数s3c24xx_i2c_irq()*/ ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0, dev_name(&pdev->dev), i2c); if (ret != 0) { dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq); return ret; } } ret = s3c24xx_i2c_register_cpufreq(i2c); if (ret < 0) { dev_err(&pdev->dev, "failed to register cpufreq notifier\n"); return ret; } /* Note, previous versions of the driver used i2c_add_adapter() * to add the bus at any number. We now pass the bus number via * the platform data, so if unset it will now default to always * being bus 0. */ /*保存i2c控制器的通道号,本例是bus 5*/ i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; //注册adapter ret = i2c_add_numbered_adapter(&i2c->adap); if (ret < 0) { dev_err(&pdev->dev, "failed to add bus to i2c core\n"); s3c24xx_i2c_deregister_cpufreq(i2c); return ret; } /*保存私有变量i2c到pdev->dev->p->driver_data*/ platform_set_drvdata(pdev, i2c); pm_runtime_enable(&pdev->dev); pm_runtime_enable(&i2c->adap.dev); dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); return 0; } |
i2c_add_numbered_adapter
老版本的注册函数为i2c_add_adapter()新的版本对该函数做了封装,将i2c控制的通道号做了注册,默认情况下nr值为0.
i2c_add_numbered_adapter->__i2c_add_numbered_adapter-> i2c_register_adapter
int i2c_add_numbered_adapter(struct i2c_adapter *adap) { if (adap->nr == -1) /* -1 means dynamically assign bus id */ return i2c_add_adapter(adap); return __i2c_add_numbered_adapter(adap); } |
i2c_add_adapter
static int i2c_register_adapter(struct i2c_adapter *adap) { int res = 0; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) { res = -EAGAIN; goto out_list; } /* Sanity checks */ if (unlikely(adap->name[0] == '\0')) { pr_err("i2c-core: Attempt to register an adapter with " "no name!\n"); return -EINVAL; } if (unlikely(!adap->algo)) { pr_err("i2c-core: Attempt to register adapter '%s' with " "no algo!\n", adap->name); return -EINVAL; } rt_mutex_init(&adap->bus_lock); mutex_init(&adap->userspace_clients_lock); INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */ if (adap->timeout == 0) adap->timeout = HZ; //设置adapter名字,本例注册后会生成以下节点/dev/i2c-5 dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; res = device_register(&adap->dev); if (res) goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); if (res) dev_warn(&adap->dev, "Failed to create compatibility class link\n"); #endif /* bus recovery specific initialization */ /*初始化sda、scl,通常这两个引脚会复用gpio引脚*/ if (adap->bus_recovery_info) { struct i2c_bus_recovery_info *bri = adap->bus_recovery_info; if (!bri->recover_bus) { dev_err(&adap->dev, "No recover_bus() found, not using recovery\n"); adap->bus_recovery_info = NULL; goto exit_recovery; } /* Generic GPIO recovery */ if (bri->recover_bus == i2c_generic_gpio_recovery) { if (!gpio_is_valid(bri->scl_gpio)) { dev_err(&adap->dev, "Invalid SCL gpio, not using recovery\n"); adap->bus_recovery_info = NULL; goto exit_recovery; } if (gpio_is_valid(bri->sda_gpio)) bri->get_sda = get_sda_gpio_value; else bri->get_sda = NULL; bri->get_scl = get_scl_gpio_value; bri->set_scl = set_scl_gpio_value; } else if (!bri->set_scl || !bri->get_scl) { /* Generic SCL recovery */ dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n"); adap->bus_recovery_info = NULL; } } exit_recovery: /* create pre-declared device nodes */ /*通过设备树节点注册所有该控制器下的所有从设备*/ of_i2c_register_devices(adap); acpi_i2c_register_devices(adap); /*与动态分配的总线号相关,动态分配的总线号应该是从已经现有最大总线号基础上+1的, 这样能够保证动态分配出的总线号与板级总线号不会产生冲突 在没有设备树情况下,会基于队列__i2c_board_list, 创建i2c_client 其中节点struct i2c_board_info手动填写*/ if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); /* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); return 0; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; } |
of_i2c_register_devices
该函数用于将从设备节点转换成i2c_client,并注册到i2c总线上。
static void of_i2c_register_devices(struct i2c_adapter *adap) { void *result; struct device_node *node; /* Only register child devices if the adapter has a node pointer set */ if (!adap->dev.of_node) return; dev_dbg(&adap->dev, "of_i2c: walking child nodes\n"); for_each_available_child_of_node(adap->dev.of_node, node) { struct i2c_board_info info = {}; struct dev_archdata dev_ad = {}; const __be32 *addr; int len; dev_dbg(&adap->dev, "of_i2c: register %s\n", node->full_name); if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) { dev_err(&adap->dev, "of_i2c: modalias failure on %s\n", node->full_name); continue; } /*获取从设备的地址*/ addr = of_get_property(node, "reg", &len); if (!addr || (len < sizeof(int))) { dev_err(&adap->dev, "of_i2c: invalid reg on %s\n", node->full_name); continue; } /*存储从设备地址*/ info.addr = be32_to_cpup(addr); if (info.addr > (1 << 10) - 1) { dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s\n", info.addr, node->full_name); continue; } /*获取中断号*/ info.irq = irq_of_parse_and_map(node, 0); info.of_node = of_node_get(node); info.archdata = &dev_ad; /*获取设备树节点wakeup-source信息*/ if (of_get_property(node, "wakeup-source", NULL)) info.flags |= I2C_CLIENT_WAKE; request_module("%s%s", I2C_MODULE_PREFIX, info.type); /*将i2c_board_info转换成i2c_client并注册到i2c总线*/ result = i2c_new_device(adap, &info); if (result == NULL) { dev_err(&adap->dev, "of_i2c: Failure registering %s\n", node->full_name); of_node_put(node); irq_dispose_mapping(info.irq); continue; } } } |
i2c_new_device ( )
将i2c_board_info转换成i2c_client并注册到Linux核心。
{ struct i2c_client *client; int status; /*给i2c_client分配内存*/ client = kzalloc(sizeof *client, GFP_KERNEL); if (!client) return NULL; /*将adapter的地址保存到i2c_client->adapter, 在驱动函数中可以通过i2c_client找到adapter*/ client->adapter = adap; client->dev.platform_data = info->platform_data; if (info->archdata) client->dev.archdata = *info->archdata; /*保存从设备地址类型*/ client->flags = info->flags; /*保存从设备地址*/ client->addr = info->addr; /*保存从设备中断号*/ client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name)); /* Check for address validity */ /*检测从设备地址是否合法*/ status = i2c_check_client_addr_validity(client); if (status) { dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n", client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr); goto out_err_silent; } /* Check for address business */ /*检测从设备地址是否被占用*/ status = i2c_check_addr_busy(adap, client->addr); if (status) goto out_err; /*建立从设备与适配器的父子关系*/ client->dev.parent = &client->adapter->dev; client->dev.bus = &i2c_bus_type; client->dev.type = &i2c_client_type; client->dev.of_node = info->of_node; ACPI_COMPANION_SET(&client->dev, info->acpi_node.companion); i2c_dev_set_name(adap, client); /*注册到Linux核心*/ status = device_register(&client->dev); if (status) goto out_err; dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); return client; out_err: dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x " "(%d)\n", client->name, client->addr, status); out_err_silent: kfree(client); return NULL; } |
核心方法i2c_transfer
l i2c_transfer()是i2c核心提供给设备驱动的发送方法, 通过它发送的数据需要被打包成i2c_msg, 这个函数最终会回调相应i2c_adapter->i2c_algorithm->master_xfer()接口将i2c_msg对象发送到i2c物理控制器,
i2c_adapte->algo在函数s3c24xx_i2c_probe()中赋值:
该变量定义如下:
i2c_transfer()最终会调用函数s3c24xx_i2c_xfer();
i2c_msg中断传输
以下是一次i2c_msg传输的中断模式的大概步骤:
1. i2c_transfer()首先通过函数i2c_trylock_adapter()尝试获得adapter的控制权。如果adapter正在忙则返回错误信息;
2. __i2c_transfer()通过调用方法adap->algo->master_xfer(adap, msgs, num)传输i2c_msg,如果失败会尝试重新传送,重传次数最多adap->retries;
3. adap->algo->master_xfer()就是函数s3c24xx_i2c_xfer(),该函数最终调用 s3c24xx_i2c_doxfer(i2c, msgs, num)传输信息;
4. s3c24xx_i2c_doxfer()通过函数s3c24xx_i2c_message_start(i2c, msgs)产生S和AD+W的信号,然后通过函数wait_event_timeout( )阻塞在等待队列i2c->wait上;
5. 右上角时序mpu6050的写和读的时序,从设备回复ACK和DATA都会发送中断信号给CPU。每次中断都会调用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,
6. 最后一次中断,所有数据发送或读取完毕会调用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通过wake_up唤醒阻塞在等待队列i2c->wait上的任务。
详细的代码流程如下:
对着可以根据上图代码行号一步步去跟代码,涉及到寄存器设置可以参考
-END-
本文授权转载自一口Linux,作者:土豆居士
推荐阅读
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!