文章目录
- 1. I2C GPIO系统架构简介
- 2. 如何使能I2C GPIO驱动
- 2.1 config配置
- 2.2 dts配置
- 2.3 测试验证
- 3. 简单分析i2c-gpio.c驱动
- 3.1 解析设备树
- 3.2 配置SDA和SCL
- 3.3 注册到i2c-algo-bit.c
- 4. 简单分析i2c-algo-bit.c驱动
- 4.1 提供I2C通信时的算法
- 4.2 注册Adapter
- 5. 参考资料
1. I2C GPIO系统架构简介
在Linux项目中,如果出现硬件硬件I2C不够用的情况下,我们就可以通过GPIO模拟I2C来解决。
Lnux内核的i2c-gpio是使用GPIO模拟I2C协议的驱动,在内核中已经实现了,我们要做的只需要配置2个GPIO(SDA和SCL)即可。
i2c-gpio的大致框架如下:
i2c-gpio.c:
- 解析设备树中的引脚配置信息
- 提供GPIO SDA和SCL引脚配置接口。
i2c-algo-bit.c:
- 向I2C Core注册一个adapter
- 提供I2C通信时的算法,然后通过i2c-gpio.c提供GPIO配置接口来收发数据。
注册成功后,"i2c-dev"
驱动就会自动创建对应的"/dev/i2c-x"
字符设备,然后我们就可以在应用层和驱动层操作该总线。
2. 如何使能I2C GPIO驱动
2.1 config配置
在对应的板级deconfig文件中,设置CONFIG_I2C_GPIO=y
。
对应的选项为:
Device Drivers->
I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
确认配置后,i2c-gpio
相关驱动就会被编译进内核。
2.2 dts配置
- 修改一
我这里是在IMX6ULL平台上测试的,修改文件:arch/arm/boot/dts/imx6ul.dtsi
。
!!!为什么需要修改aliases呢?!!!
因为在添加添加adapter时,会通过aliases
的别名编号配置adapter->nr
总线编号。注册成功后,会创建/dev/i2c-4
设备。
- 修改二
添加需要模拟i2c的gpio,一定是先放sda再放scl,因为它是在i2c-gpio.c
里面定义好的,必须这么写才可以。
图片效果如下:i2c5:i2c5_gpio { #address-cells = <1>; #size-cells = <0>; compatible = "i2c-gpio"; gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>, /* sda */ <&gpio1 28 GPIO_ACTIVE_HIGH>; /* scl */ i2c-gpio,delay-us = <5>; /* ~100 kHz */ status = "disabled"; };
!!!什么时候需要添加open drain属性?!!!
使用GPIO模拟I2C模式时,一般GPIO需要工作在开漏模式。在of_i2c_gpio_get_props
函数中,解析是否有定义open drain相关属性。如下:
当定义i2c-gpio,sda-open-drain
和i2c-gpio,scl-open-drain
属性后,说明是其它子系统已经将该GPIO配置成开漏输出了,这里不再进行开漏的配置。如果dts里面不定义,就启动GPIOD_OUT_HIGH_OPEN_DRAIN
配置GPIO。所以,我这里并没有定义该属性,需要它在这里配置为开漏。
- 修改三
使能模拟i2c5总线,并且在该总线下挂载ap3216c
设备。&i2c5 { status = "okay"; ap3216c@1e { compatible = "lite-on,ap3216c"; reg = <0x1e>; }; };
2.3 测试验证
重新编译烧录固件后,这里新增了/dev/i2c-4
总线设备,它就是我们新增的GPIO模拟I2C总线设备。
使用i2c_tools
测试该总线,可以正常的识别到设备,说明移植已经成功了。(备注:需要了解i2c_tools使用的,可以参考这篇博客《【I2C】基于Linux移植i2c-tool工具》)
3. 简单分析i2c-gpio.c驱动
前面已经提到i2c-gpio.c驱动主要是3个功能:
3.1 解析设备树
在probe函数中会调用of_i2c_gpio_get_props
函数来解析相关属性:
i2c-gpio,delay-us
:配置每个bit的使用时间,也就是I2C通信时Clock的频率。i2c-gpio,timeout-ms
:配置i2c通信时的超时时间,如果超过这个时间没有收到ack,说明通信失败。i2c-gpio,sda-open-drain
:是否有在其它子系统里面定义了sda gpio为开漏模式,如果有就定义该属性。i2c-gpio,scl-open-drain
:是否有在其它子系统里面定义了scl gpio为开漏模式,如果有就定义该属性。i2c-gpio,scl-output-only
:配置scl gpio只支持输出模式,不支持输入模式。
具体代码如下:
static void of_i2c_gpio_get_props(struct device_node *np,
struct i2c_gpio_platform_data *pdata)
{
u32 reg;
of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay);
if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", ®))
pdata->timeout = msecs_to_jiffies(reg);
pdata->sda_is_open_drain =
of_property_read_bool(np, "i2c-gpio,sda-open-drain");
pdata->scl_is_open_drain =
of_property_read_bool(np, "i2c-gpio,scl-open-drain");
pdata->scl_is_output_only =
of_property_read_bool(np, "i2c-gpio,scl-output-only");
}
通过i2c_gpio_get_desc解析dts设备树文件里面定义gpios配置:
gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>, <&gpio1 28 GPIO_ACTIVE_HIGH>;
具体代码如下:
if (pdata->sda_is_open_drain)
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;
priv->sda = i2c_gpio_get_desc(dev, "sda", 0, gflags);
if (IS_ERR(priv->sda))
return PTR_ERR(priv->sda);
if (pdata->scl_is_open_drain)
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;
priv->scl = i2c_gpio_get_desc(dev, "scl", 1, gflags);
3.2 配置SDA和SCL
配置操作SDA和SCL 2个GPIO的函数接口,后面可以通过它设置和获取GPIO的高低电平,具体代码如下:
bit_data->setsda = i2c_gpio_setsda_val;
bit_data->setscl = i2c_gpio_setscl_val;
if (!pdata->scl_is_output_only)
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda;
3.3 注册到i2c-algo-bit.c
将配置好struct i2c_adapter
的信息注册到i2c-algo-bit.c
驱动中,具体代码如下:
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->dev.parent = dev;
adap->dev.of_node = np;
adap->nr = pdev->id; // 其实这里adapter的编号是-1,真正的编号是后面注册时aliases获取的。前面已经有分析。
ret = i2c_bit_add_numbered_bus(adap);
4. 简单分析i2c-algo-bit.c驱动
前面已经提到i2c-algo-bit.c驱动主要是2个功能:
4.1 提供I2C通信时的算法
在__i2c_bit_add_bus
函数中会这一个i2c_bit_algo
算法接口,我们使用i2c_transfer收发数据时,最终都会调用到i2c_bit_algo的bit_xfer函数收发数据。
const struct i2c_algorithm i2c_bit_algo = {
.master_xfer = bit_xfer,
.master_xfer_atomic = bit_xfer_atomic,
.functionality = bit_func,
};
adap->algo = &i2c_bit_algo;
代码里面定义了很多模拟i2c时序的函数,就算我们自己写GPIO模拟I2C驱动,也都必须实现这些函数。
4.2 注册Adapter
向I2C Core注册一个adapter,注册成功后,"i2c-dev"
驱动就会自动创建对应的"/dev/i2c-x"
字符设备,然后我们就可以在应用层和驱动层操作该总线。具体代码如下:
static int __i2c_bit_add_bus(struct i2c_adapter *adap,
int (*add_adapter)(struct i2c_adapter *))
{
...
ret = add_adapter(adap);
if (ret < 0)
return ret;
return 0;
}
int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
}
5. 参考资料
Linux内核驱动:gpio模拟i2c驱动:
https://blog.csdn.net/landishu/article/details/118441943