# 内核自带模拟I2C驱动框架 参考资料: * Linux文档 * `Linux-5.4\Documentation\devicetree\bindings\i2c\i2c-gpio.yaml` * `Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt` * Linux驱动源码 * `Linux-5.4\drivers\i2c\busses\i2c-gpio.c` * `Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c` ## 驱动程序分析 内核自带的模拟i2c驱动也是使用平台总线驱动模型: ![](media/063_i2c-gpio_module.png) 从上图可知设备树为我们提供平台设备,当平台驱动在insmod的时候,如果匹配到设备树里面的`compatible =i2c-gpio`时候,就会调用平台驱动的`i2c_gpio_probe`函数。 ### 设备树 * ![](media/image-20210926141124311.png) ### I2C-GPIO层次 ![](media/064_i2c-gpio_level.png) ## 怎么使用I2C-GPIO 参见工程【19.i2c/ap3216_5】,工程目录如下: ![](media/image-20210926093012739.png) 使用说明: 1. 配置内核,使能内核自带的模拟i2c驱动程序,这里选择编译出模块,如下所示,保存并退出 ![](media/image-20210926083825723.png) 2. 切换到【19.i2c/ap3216_5】目录,执行`make`,会自动到内核目录下执行`make modules` 编译过程如下: ![](media/image-20210926092929287.png) **注意**:我们使能的内核自带的模拟i2c的模块位置如下:`drivers/i2c/busses/i2c-gpio.ko` 3. 测试:这里使用i2c-tools来进行测试模拟的i2c驱动 ```bash [root@100ask:~]# i2cdetect -l // 加载i2c-gpio.ko前只看到2条I2C BUS i2c-1 i2c 21a4000.i2c I2C adapter i2c-0 i2c 21a0000.i2c I2C adapter [root@100ask:~]# [root@100ask:~]# insmod i2c-gpio.ko [ 45.067602] i2c-gpio i2c_gpio_100ask: using pins 116 (SDA) and 117 (SCL) [root@100ask:~]# i2cdetect -l // 加载i2c-gpio.ko后看到3条I2C BUS i2c-1 i2c 21a4000.i2c I2C adapter i2c-4 i2c i2c_gpio_100ask I2C adapter i2c-0 i2c 21a0000.i2c I2C adapter [root@100ask:~]# [root@100ask:~]# i2cdetect -y 4 // 检测到0x50的设备 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- [root@100ask:~]# [root@100ask:~]# i2cset -f -y 4 0x50 0 0x55 // 往0地址写入0x55 [root@100ask:~]# i2cget -f -y 4 0x50 0 // 读0地址 0x55 ``` ### 源码分析 1. 【19.i2c/ap3216_5/Makefile】文件 ![](media/image-20210926093603316.png) 2. 设备树:【19.i2c/ap3216_5/imx6ull-14x14-evk.dts】文件跟根节点下添加下面的信息: ```c i2c_gpio_xym { compatible = "i2c-gpio"; gpios = <&gpio5 20 0 /* sda */ &gpio4 21 0 /* scl */ >; i2c-gpio,delay-us = <5>; /* ~100 kHz */ #address-cells = <1>; #size-cells = <0>; status="okey"; }; ``` 当我们执行`insmod i2c-gpio.ko`的时候会自动和设备树的`compatible = "i2c-gpio"`属性进行匹配,如果匹配成功,就会调用i2c-gpio.c下的probe函数,在probe函数中又会出现i2c适配器注册的那一套 - 分配适配器 ```c struct i2c_gpio_private_data { struct i2c_adapter adap; struct i2c_algo_bit_data bit_data; struct i2c_gpio_platform_data pdata; }; struct i2c_gpio_private_data *priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ``` - 设置适配器 ```c adap->algo_data = bit_data;// 算法结构体,很重要,注意:此时bit_data还没有具体的内容, adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->dev.parent = &pdev->dev; adap->dev.of_node = pdev->dev.of_node; adap->nr = pdev->id; ``` 注意:`bit_data`和重要下面会分析。 - 注册适配器 ```c i2c_bit_add_numbered_bus(adap);// 在该函数里面才会真正的实现adap->algo_data的注册,后面分析 ``` 整个probe函数如下: ```c static int i2c_gpio_probe(struct platform_device *pdev) { struct i2c_gpio_private_data *priv; struct i2c_gpio_platform_data *pdata; struct i2c_algo_bit_data *bit_data; struct i2c_adapter *adap; unsigned int sda_pin, scl_pin; int ret; /* * 1.下面这一段是从设备树拿到 sda和scl的gpio引脚,并初始化gpio功能 */ /* First get the GPIO pins; if it fails, we'll defer the probe. */ if (pdev->dev.of_node) { ret = of_i2c_gpio_get_pins(pdev->dev.of_node, &sda_pin, &scl_pin); if (ret) return ret; } else { if (!dev_get_platdata(&pdev->dev)) return -ENXIO; pdata = dev_get_platdata(&pdev->dev); sda_pin = pdata->sda_pin; scl_pin = pdata->scl_pin; } ret = devm_gpio_request(&pdev->dev, sda_pin, "sda"); if (ret) { if (ret == -EINVAL) ret = -EPROBE_DEFER; /* Try again later */ return ret; } ret = devm_gpio_request(&pdev->dev, scl_pin, "scl"); if (ret) { if (ret == -EINVAL) ret = -EPROBE_DEFER; /* Try again later */ return ret; } /* * 2.分配适配器 */ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; adap = &priv->adap; bit_data = &priv->bit_data; pdata = &priv->pdata; if (pdev->dev.of_node) { pdata->sda_pin = sda_pin; pdata->scl_pin = scl_pin; of_i2c_gpio_get_props(pdev->dev.of_node, pdata); } else { memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata)); } if (pdata->sda_is_open_drain) { gpio_direction_output(pdata->sda_pin, 1); bit_data->setsda = i2c_gpio_setsda_val; } else { gpio_direction_input(pdata->sda_pin); bit_data->setsda = i2c_gpio_setsda_dir; } if (pdata->scl_is_open_drain || pdata->scl_is_output_only) { gpio_direction_output(pdata->scl_pin, 1); bit_data->setscl = i2c_gpio_setscl_val; } else { gpio_direction_input(pdata->scl_pin); bit_data->setscl = i2c_gpio_setscl_dir; } if (!pdata->scl_is_output_only) bit_data->getscl = i2c_gpio_getscl; bit_data->getsda = i2c_gpio_getsda; /* * 3.计算i2c的时钟频率 */ if (pdata->udelay) bit_data->udelay = pdata->udelay; else if (pdata->scl_is_output_only) bit_data->udelay = 50; /* 10 kHz */ // 这里是计算i2c的时钟频率 else bit_data->udelay = 5; /* 100 kHz */ if (pdata->timeout) bit_data->timeout = pdata->timeout; else bit_data->timeout = HZ / 10; /* 100 ms */ bit_data->data = pdata; /* * 4.设置适配器 */ adap->owner = THIS_MODULE; if (pdev->dev.of_node) strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name)); else snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id); adap->algo_data = bit_data;// 算法结构体,注意:此时bit_data还没有具体的内容, adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->dev.parent = &pdev->dev; adap->dev.of_node = pdev->dev.of_node; adap->nr = pdev->id; /* * 5.注册适配器到内核里面 */ ret = i2c_bit_add_numbered_bus(adap);// 注意:这里才会真正实现算法结构体的赋值 if (ret) return ret; platform_set_drvdata(pdev, priv); dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n", pdata->sda_pin, pdata->scl_pin, pdata->scl_is_output_only ? ", no clock stretching" : ""); return 0; } ``` 3. 重点分下下算法结构体**bit_data** 上面介绍到,bit_data还没有具体的内容,会在probe调用`i2c_bit_add_numbered_bus`函数里面实现具体的赋值,参见【drivers/i2c/algos/i2c-algo-bit.c】文件如下: ![](media/image-20210926104455374.png) 所以下面就重点分析下bit_xfer函数即可。 ![](media/image-20210926100813308.png) 重点分析`bit_xfer`里面如下: ![](media/image-20210926101117280.png) 红色框中的函数就会根据前面的gpio拉i2c的时序,如下所示: ![](media/image-20210926101242485.png)