【I2C】Linux使用GPIO模拟I2C

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-draini2c-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", &reg))
		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

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值