内核自带模拟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驱动也是使用平台总线驱动模型:

../../_images/063_i2c-gpio_module.png

从上图可知设备树为我们提供平台设备,当平台驱动在insmod的时候,如果匹配到设备树里面的compatible =i2c-gpio时候,就会调用平台驱动的i2c_gpio_probe函数。

设备树

  • ../../_images/image-20210926141124311.png

I2C-GPIO层次

../../_images/064_i2c-gpio_level.png

怎么使用I2C-GPIO

参见工程【19.i2c/ap3216_5】,工程目录如下:

../../_images/image-20210926093012739.png

使用说明:

  1. 配置内核,使能内核自带的模拟i2c驱动程序,这里选择编译出模块,如下所示,保存并退出

    ../../_images/image-20210926083825723.png

  2. 切换到【19.i2c/ap3216_5】目录,执行make,会自动到内核目录下执行make modules

    编译过程如下:

    ../../_images/image-20210926092929287.png

    注意:我们使能的内核自带的模拟i2c的模块位置如下:drivers/i2c/busses/i2c-gpio.ko

  3. 测试:这里使用i2c-tools来进行测试模拟的i2c驱动

    [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】文件

    ../../_images/image-20210926093603316.png

  2. 设备树:【19.i2c/ap3216_5/imx6ull-14x14-evk.dts】文件跟根节点下添加下面的信息:

    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适配器注册的那一套

    • 分配适配器

      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);
      
    • 设置适配器

      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和重要下面会分析。

    • 注册适配器

      i2c_bit_add_numbered_bus(adap);// 在该函数里面才会真正的实现adap->algo_data的注册,后面分析
      

    整个probe函数如下:

    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】文件如下:

    ../../_images/image-20210926104455374.png

    所以下面就重点分析下bit_xfer函数即可。

    ../../_images/image-20210926100813308.png

    重点分析bit_xfer里面如下:

    ../../_images/image-20210926101117280.png

    红色框中的函数就会根据前面的gpio拉i2c的时序,如下所示:

    ../../_images/image-20210926101242485.png