串行异步通信协议:
RS232总线:
全双工,可以同时接受和发送数据。
缺点:
- 传输距离短,最大传输距离标准值为15米;
- 传输速率低,最大传输速率20KB/s;
RS485总线:
半双工,同一时刻只能发送或者接受数据;
相比于RS232优点:
- 使用差分量计算高低电平,有效解决了共模干扰;
- 传输距离增大,标准最大传输距离1220m;
- 最大传输速率10Mbit/s;
串行同步通信协议:
同步传输通信协议,需要同步时钟信号,增加数据传输的可靠性;
IIC总线:
- 只需要两条总线,但需要共地系统;
- 没有严格波特率要求,使用SCL进行输出传输同步;
- 多出从设备,提供总线仲裁和冲突检测;
- 传输速度:
-
- 标准模式:100 Kbps
- 快速模式:400 Kbps
硬件拓扑:
- 所有I2C设备的SCL连在一起,SDA也连在一起;
- 设备的SCL和SDA的GPIO均要设置成开漏输出模式;(开漏输出的GPIO引脚在输出低电平时与地相连,在输出高电平时则是高阻态(浮空),外部需要使用上拉电阻将输出拉到高电平。)
- SCL和SDA各添加一个上拉电阻;
IIC时序:
I2C时序图:
- Start:SCL高电平期间,SDA从高电平切换到低电平;
- Stop:SCL高电平期间,SDA从低电平切换到高电平;
- 发送一个字节:SCL低电平期间,主机将数据位依此放到SDA线上(高位优先),然后释放SCL,丛集将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,一次循环8次,完成一个字节传输;
- 接受一个字节:SCL低电平期间,从机将数据位依此放到SDA线上(高位优先),然后释放SCL,主机将在SCL高电平期间读取数据位,一次循环8次,完成1个字节数据的接受;
- 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答;
- 接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答。
I2C写数据:
I2C读数据:
IIC总线仲裁:
- I2C总线上可能在某一个时刻有两个主控设备要同时向总线发送数据,这种情况叫总线竞争;
- 仲裁原理:
-
- 假设主控制器1要发送数据DATA1位101...,主控器2要发送的数据DATA2位1001...;
- 两个主控制器在每发送一个数据位时都要对自己的输出电平进行检测,只要检测与自己发出的电平一致,就继续占用总线;
- 当主控制器1发送第三位数据1,而主控制器2发送第三位数据0时,线与结果为SDA变为低电平0,主控制器1读取SDA实际电平和自己想要输出的电平不一致,主控制器1主动放弃总线控制,主控制器2称为中线主宰者;
MPL3115A2:
压力温度传感器,使用I2C通信接口。
RK3568 IIC控制器:
基于上述I2C时序,可以使用芯片的GPIO管脚实现软件IIC,软件I2C是通过CPU的GPIO模拟实现的,通过软件控制时序和数据传输,但通信速度较慢。硬件IIC是由芯片内部的硬件模块实现的,利用CPU的时钟信号来控制数据传输和时序,通信速度快;
RK3568有6个I2C控制器,其中I2C0已经被外设PMIC占用。
I2C控制器属于RK3568外设,参考RK3568硬件手册:
Datasheet:
📎Rockchip RK3568 TRM Part1 V1.1-20210301.pdf
- I2C_RF:I2C控制器和CPU总线接口,寄存器操作,中断配置等;
- I2C_PE:I2C Master,接收和发送数据;
功能:
- Master only,RK3568只能作为主设备,不能设置为I2C从设备;
- 7-bits/10-bits 地址模式,10bits地址一般很少用到;
- up to 400Kbit/s;
Mix mode 流程,即发送一次数据后等待接受从设备数据:
Linux IIC驱动:
字符设备:
crw------- 1 root root 89, 0 Jan 22 04:34 i2c-0 // 89-主设备号,0-次设备号
crw-r--r-- 1 root root 1, 11 Jan 22 04:34 kmsg // 1-主设备号,11-次设备号
Linux设备模型:
设备负责提供硬件资源而驱动代码负责去使用这些设备提供的硬件资源。 并由总线将它们联系起来。
- 设备(device):挂在在某个总线的物理设备;
- 驱动(driver):与特定设备相关的软件,服务初始化该设备以及提供一些操作该设备的操作方式;
- 总线(bus):负载管理挂载对应总线的设备以及驱动;
- 类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理;
总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动, 同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。在插入的同时总线会执行一个match的方法对新插入的设备/驱动进行匹配。在匹配成功的时候会调用驱动device_driver结构体中probe方法,并且在移除设备或驱动时,会调用device_driver结构体中remove方法。
在Linux I2C驱动中,需要区别两个总线,一个是platform总线(平台总线-platform_bus_type),一个是I2C总线(i2c_bus_type),这两个都是软件抽象,platform总线是用来匹配I2C控制器驱动和I2C控制器设备的,而I2C总线是用来匹配I2C控制器和I2C设备(I2C外设,通过外部电路和MCU的管脚相连的设备)。
Platform平台总线-平台驱动-平台设备:
其他总线-驱动-设备:
框架和上述一样,只是需要自己定义bus_type,定义自己的驱动和设备的match方式等。
IIC驱动框架:
IIC驱动中涉及两部分驱动,一部分是对IIC控制器的驱动,另一部分是对具体的挂在IIC控制器上的设备的驱动,但是具体设备的驱动不是必须的,框架如下。
- i2c核心:提供i2c总线驱动设备驱动的注册、注销方法;
- i2c总线驱动:i2c总线驱动是对i2c控制器的驱动实现;
- i2c设备驱动:对挂在i2c控制器上的i2c设备的驱动实现;
I2C总线驱动:
RK3568上I2C的总线驱动代码位于文件i2c-rk3x.c,I2C总线驱动属于平台驱动:
static struct platform_driver rk3x_i2c_driver = {
.probe = rk3x_i2c_probe, // match设备后执行probe函数
.remove = rk3x_i2c_remove,
.driver = {
.name = "rk3x-i2c",
.of_match_table = rk3x_i2c_match, // match table
.pm = &rk3x_i2c_pm_ops, // 电源管理相关
},
};
module_platform_driver(rk3x_i2c_driver); // 宏定义,就是驱动的register
I2C控制器设备是通过设备树来创建的,具体可以查看topeet_rk3568_a55.dts,RK3568有6个I2C控制器,拿其中一个为例:
i2c@fe5c0000 {
compatible = "rockchip,rk3399-i2c"; // 驱动和设备的匹配方式
reg = <0x00 0xfe5c0000 0x00 0x1000>; // 控制器寄存器地址和范围
clocks = <0x1f 0x14c 0x1f 0x14b>; // 控制器时钟配置
clock-names = "i2c\0pclk";
interrupts = <0x00 0x31 0x04>; // 控制器中断配置
pinctrl-names = "default";
pinctrl-0 = <0xe9>; // pin属性配置
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "okay";
phandle = <0x1cb>;
};
当驱动和设备注册到平台总线后(控制器设备由设备树相关代码自动创建platform device),平台总线会使用of_driver_match_device的方式来匹配驱动和设备:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
...
}
static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
再看一下rk3568上的I2C控制器驱动的match table,这里的“rockchip,rk3399-i2c”就和之前设备树中的compatible信息对应上了。
static const struct of_device_id rk3x_i2c_match[] = {
{
.compatible = "rockchip,rv1108-i2c",
.data = &rv1108_soc_data
},
{
.compatible = "rockchip,rk3066-i2c",
.data = &rk3066_soc_data
},
{
.compatible = "rockchip,rk3188-i2c",
.data = &rk3188_soc_data
},
{
.compatible = "rockchip,rk3228-i2c",
.data = &rk3228_soc_data
},
{
.compatible = "rockchip,rk3288-i2c",
.data = &rk3288_soc_data
},
{
.compatible = "rockchip,rk3399-i2c",
.data = &rk3399_soc_data
},
{},
};
驱动设备匹配后,就会调用rk3x_i2c_driver的probe函数:
static int rk3x_i2c_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
struct rk3x_i2c *i2c;
int ret = 0;
int bus_nr;
u32 value;
int irq;
unsigned long clk_rate;
i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
if (!i2c)
return -ENOMEM;
match = of_match_node(rk3x_i2c_match, np);
i2c->soc_data = match->data;
/* use common interface to get I2C timing properties */
i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
strlcpy(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->dev = &pdev->dev;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
i2c->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(i2c->regs))
return PTR_ERR(i2c->regs);
/* Try to set the I2C adapter number from dt */
bus_nr = of_alias_get_id(np, "i2c");
/*
* Switch to new interface if the SoC also offers the old one.
* The control bit is located in the GRF register space.
*/
if (i2c->soc_data->grf_offset >= 0) {
struct regmap *grf;
grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(grf)) {
dev_err(&pdev->dev,
"rk3x-i2c needs 'rockchip,grf' property\n");
return PTR_ERR(grf);
}
if (bus_nr < 0) {
dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
return -EINVAL;
}
/* 27+i: write mask, 11+i: value */
value = BIT(27 + bus_nr) | BIT(11 + bus_nr);
ret = regmap_write(grf, i2c->soc_data->grf_offset, value);
if (ret != 0) {
dev_err(i2c->dev, "Could not write to GRF: %d\n", ret);
return ret;
}
}
/* IRQ setup */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
0, dev_name(&pdev->dev), i2c);
if (ret < 0) {
dev_err(&pdev->dev, "cannot request IRQ\n");
return ret;
}
platform_set_drvdata(pdev, i2c);
if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
/* Only one clock to use for bus clock and peripheral clock */
i2c->clk = devm_clk_get(&pdev->dev, NULL);
i2c->pclk = i2c->clk;
} else {
i2c->clk = devm_clk_get(&pdev->dev, "i2c");
i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
}
if (IS_ERR(i2c->clk)) {
ret = PTR_ERR(i2c->clk);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
return ret;
}
if (IS_ERR(i2c->pclk)) {
ret = PTR_ERR(i2c->pclk);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
return ret;
}
ret = clk_prepare(i2c->clk);
if (ret < 0) {
dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
return ret;
}
ret = clk_prepare(i2c->pclk);
if (ret < 0) {
dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
goto err_clk;
}
i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
if (ret != 0) {
dev_err(&pdev->dev, "Unable to register clock notifier\n");
goto err_pclk;
}
clk_rate = clk_get_rate(i2c->clk);
rk3x_i2c_adapt_div(i2c, clk_rate);
ret = i2c_add_adapter(&i2c->adap);
if (ret < 0)
goto err_clk_notifier;
return 0;
err_clk_notifier:
clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
err_pclk:
clk_unprepare(i2c->pclk);
err_clk:
clk_unprepare(i2c->clk);
return ret;
}
- Line12:分配一个改控制器的私有数据结构i2c,可以理解为改控制器的软件抽象;
- Line16:从rk3x_i2c_match表中找到对应的compatible中的data值,calc_timtings用于规定SCL的信号时序要求,比如SCL时钟频率,高电平延时要求,低电平延时要求等;
static const struct rk3x_i2c_soc_data rk3399_soc_data = {
.grf_offset = -1,
.calc_timings = rk3x_i2c_v1_calc_timings,
};
- Line20:解析设备树中有关时钟频率的设定,比如SCL的频率,信号保持时间等,如果设备树中没有指定,那么就用默认值;
- Line22-28:初始化i2c adapter的内容,其中Line24表示为CPU对改I2C控制器的操作方式,可以理解为通过配置其寄存器让控制器可以产生对应的SCL和SDA信号来对外设I2C设备通信,master_xfer就是让I2C控制器发送数据的接口,这部分内容之后再讲;
static const struct i2c_algorithm rk3x_i2c_algorithm = {
.master_xfer = rk3x_i2c_xfer,
.functionality = rk3x_i2c_func,
};
- Line35:得到设备树中的reg信息,通过ioremap来映射IIC控制的物理寄存器;
- Line40-69:这一部分不用管,grf_offset为-1;
- Line72:中断配置信息获取;
- Line76:中断号,中断下文,中断回调等初始化,使用i2c中断方式时,当从设备有数据回复时,会产生中断,中断处理函数为rk3x_i2c_irq;
- Line83:设置该IIC控制器设备的私有数据i2c;
- Line85-126:用于控制器的时钟配置,不用管;
- Line128:初始化I2C adapter;
看一下这部分代码做了什么事:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
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);
}
}
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
mutex_unlock(&core_lock);
if (WARN(id < 0, "couldn't get idr"))
return id;
adapter->nr = id;
return i2c_register_adapter(adapter);
}
Line6:如果控制器设备是通过设备树创建的,那么走Line6-12的逻辑;
Line7:通过设备树中I2C控制器的位置,设置id,RK3568上有6个I2C控制器,那么就会创建6个I2C adapter,分别为/dev/i2c-0-/dev/i2c-6;
Line10:得到id后调用__i2c_add_numbered_adapter;
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
mutex_unlock(&core_lock);
if (WARN(id < 0, "couldn't get idr"))
return id == -ENOSPC ? -EBUSY : id;
return i2c_register_adapter(adap);
}
Line6:需要消耗一个id,该id为adatper的nr,就是上面通过of_alias_get_id分配的id;
Line1380:注册i2c adapter:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = -EINVAL;
/* Can't register until after driver model init */
if (WARN_ON(!is_registered)) {
res = -EAGAIN;
goto out_list;
}
/* Sanity checks */
if (WARN(!adap->name[0], "i2c adapter has no name"))
goto out_list;
if (!adap->algo) {
pr_err("adapter '%s': no algo supplied!\n", adap->name);
goto out_list;
}
if (!adap->lock_ops)
adap->lock_ops = &i2c_adapter_lock_ops;
adap->locked_flags = 0;
rt_mutex_init(&adap->bus_lock);
rt_mutex_init(&adap->mux_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;
/* register soft irqs for Host Notify */
res = i2c_setup_host_notify_irq_domain(adap);
if (res) {
pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",
adap->name, res);
goto out_list;
}
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) {
pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
goto out_list;
}
res = of_i2c_setup_smbus_alert(adap);
if (res)
goto out_reg;
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
pm_runtime_no_callbacks(&adap->dev);
pm_suspend_ignore_children(&adap->dev, true);
pm_runtime_enable(&adap->dev);
#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
i2c_init_recovery(adap);
/* create pre-declared device nodes */
of_i2c_register_devices(adap);
i2c_acpi_register_devices(adap);
i2c_acpi_install_space_handler(adap);
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_reg:
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released);
out_list:
mutex_lock(&core_lock);
idr_remove(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock);
return res;
}
Line34:设置I2C适配器的Host Notify软中断;
Line41:设置设配置的设备名称,i2c-0-i2c-5;
Line42:该I2C adapter是一个设备,挂在i2c bus下;
Line44:注册i2c adapter设备,如果有i2c bus总线下的i2c driver,那么就会进行匹配,然后probe;
Line50:SMBUS相关,不用管,SMBUS可以理解为I2C协议的子集;
Line56-58:电源管理相关代码,不用管;
Line68:IIC总线恢复逻辑,RK控制器驱动中没有这部分逻辑,不用管;
Line71:在设备树中一个I2C控制器下面可以挂在子节点,这些子节点就是在物理上连接该IIC控制器的设备,属于外设,of_i2c_register_devices用于遍历该控制器下的所有子节点,并为子节点创建对应的设备结构。
比如在RK3568中IIC0控制器下挂了两个子节点,分别为设备tcs4525和设备pmic,前者为一个DC-DC转换器,后者为电源管理器:
i2c@fdd40000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x00 0xfdd40000 0x00 0x1000>;
clocks = <0x32 0x07 0x32 0x2d>;
clock-names = "i2c\0pclk";
interrupts = <0x00 0x2e 0x04>;
pinctrl-names = "default";
pinctrl-0 = <0x35>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "okay";
phandle = <0x17a>;
tcs4525@1c {
compatible = "tcs,tcs452x";
reg = <0x1c>;
vin-supply = <0x36>;
regulator-compatible = "fan53555-reg";
regulator-name = "vdd_cpu";
regulator-min-microvolt = <0xadf34>;
regulator-max-microvolt = <0x1535b0>;
regulator-init-microvolt = <0xdbba0>;
regulator-ramp-delay = <0x8fc>;
fcs,suspend-voltage-selector = <0x01>;
regulator-boot-on;
regulator-always-on;
phandle = <0x05>;
regulator-state-mem {
regulator-off-in-suspend;
};
};
pmic@20 {
compatible = "rockchip,rk809";
reg = <0x20>;
interrupt-parent = <0x37>;
这里就是遍历所有子节点了,然后调用of_i2c_register_device去注册设备了。
void of_i2c_register_devices(struct i2c_adapter *adap)
{
struct device_node *bus, *node;
struct i2c_client *client;
/* 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");
bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
if (!bus)
bus = of_node_get(adap->dev.of_node);
for_each_available_child_of_node(bus, node) {
if (of_node_test_and_set_flag(node, OF_POPULATED))
continue;
client = of_i2c_register_device(adap, node);
if (IS_ERR(client)) {
dev_err(&adap->dev,
"Failed to create I2C device for %pOF\n",
node);
of_node_clear_flag(node, OF_POPULATED);
}
}
of_node_put(bus);
}
Line72-Line73:通过acpi方式获取设备信息,UEFI相关的方式,不用管;
Line80:也是添加IIC设备的方法,前者是通过设备树,这里是通过自动探测的方式,不深入解析;
i2c_register_adapter就讲完了,上面提到的设备的注册是通过of_i2c_register_device来完成的,这里对具体的代码做详细的解释:
static stru