这里以rk3288为例子,使用的是linux4.14,根据设备树节点i2c 与rk3x_i2c_driver,match之后,就会调用对应的probe(rk3x_i2c_probe),这里主要就是注册一个adapt(i2c_add_adapter),也就是i2c控制器,或者说是i2c主设备,既然是主设备,就需要提供读写的能力,以及SCL的时钟:
1.i2c->adap.algo = &rk3x_i2c_algorithm;//提供读写的方法
2.clk_rate = clk_get_rate(i2c->clk);
rk3x_i2c_adapt_div(i2c, clk_rate); //设置时钟分频
3.ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq, 0, dev_name(&pdev->dev), i2c);
//申请对应的中断
4.ret = i2c_add_adapter(&i2c->adap); //注册adapt
在ret = i2c_add_adapter(&i2c->adap);中有一个特别的函数of_i2c_register_devices(adap);
它会根据adapt对应的设备节点,轮询其子节点,然后of_i2c_register_device(adap, node);,这里最后就会注册i2c_client,既然是从设备,那么也有几点必须的:
1.of_modalias_node(node, info.type, sizeof(info.type)) //名字,用于与i2c_driver匹配
2.addr_be = of_get_property(node, "reg", &len); //每一种从设备都有自己的地址,地址其实是实现i2c总线的基础
3.i2c_new_device(adap, &info);
然后另一方面,各种各样的i2c_driver也会注册到i2c_bus中,i2c_driver会与i2c_client 进行match (i2c_match中对type有要求,adapt不会被匹配上),如果匹配上,就会调用driver->probe,至于probe中干什么事情,就得根据该i2c的用途来决定了,比如是rt5640(音频),那么里面就会注册一个codec,如果是触摸屏,那就就会注册一个输入设备等等,但是无论是用于什么,里面一定会提供读写的方法,而这个方法最后一定会调用到 i2c->adap.algo->master_xfer。这样i2c的基本流程就已经完成了。
通过设备树可以发现,一个adapt下往往挂着多个client,那么就有问题了,
1.该如何发送给我想要发送的那个client呢?
2.A,B两个从设备同时挂在一个adapt下,如果两个同时发送,会怎么处理呢?
第一个问题上面也已经提到了,每一种设备都有一个设备地址,通过发送不同的设备地址,就可以访问到想要访问的从设备。
第二个问题,就拿一般的i2c设备来说,读写会调用i2c_transfer
if (in_atomic() || irqs_disabled()) {
ret = i2c_trylock_adapter(adap);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
i2c_lock_adapter(adap);
}
这一段代码,使用互斥锁,保证了一个进程正在使用该adapt时,另一个进程就会被进入睡眠,如果在进行原子操作或者中断是关闭的(不允许调度),那么直接返回。
最后看一下发送的具体实现:rk3x_i2c_xfer
1.clk_enable(i2c->clk);clk_enable(i2c->pclk); //使能时钟
2.rk3x_i2c_setup(i2c, msgs + i, num - i); //配置从设备地址,以及读还是写
3.rk3x_i2c_start(i2c); //使能i2c控制器,使能start中断,i2c的模式,最后发送start信号。
4.wait_event_timeout(i2c->wait, !i2c->busy,msecs_to_jiffies(WAIT_TIMEOUT));//等待唤醒。
由于在第3步中使能了start中断,并且开启了start信号,就会进入到中断服务函数,那么查看一下中断服务函数rk3x_i2c_irq:其实就是一个很简单的状态机
switch (i2c->state) {
case STATE_START:
rk3x_i2c_handle_start(i2c, ipd); //启动start信号后,触发中断,准备发送或读取数据
break;
case STATE_WRITE:
rk3x_i2c_handle_write(i2c, ipd); //发送完mtxcnt后,触发中断,是否继续发送或者stop
break;
case STATE_READ:
rk3x_i2c_handle_read(i2c, ipd); //收到mrxcnt后,触发中断,是否继续接收或者stop
break;
case STATE_STOP:
rk3x_i2c_handle_stop(i2c, ipd); //启动stop信号后,触发中断,唤醒i2c->wait
break;
case STATE_IDLE:
break;
}