i2c adapter

i2c 适配器框架分析

i2c适配器驱动走的是平台总线驱动那一套东西,过程如下:

  1. 设备树在根节点提供i2c控制器的设备节点如下:

    / {
    	……
    	i2c-bus-virtual {
    		 compatible = "flyrobot,i2c-bus-virtual";
    	};
    	……
    };
    
  2. 编写平台总线驱动

    • 提供自己的平台驱动结构体,里面有probe和remove函数

      static const struct of_device_id i2c_bus_virtual_dt_ids[] = {
      	{ .compatible = "flyrobot,i2c-bus-virtual", },// 和设备树匹配
      	{ /* sentinel */ }
      };
      
      static struct platform_driver i2c_bus_virtual_driver = {
      	.driver		= {
      		.name	= "i2c-gpio",
      		.of_match_table	= of_match_ptr(i2c_bus_virtual_dt_ids),
      	},
      	.probe		= i2c_bus_virtual_probe,
      	.remove		= i2c_bus_virtual_remove,
      };
      
    • 入口函数把自己的i2c_bus_virtual_driver注册平台驱动总线上,当和设备树匹配成功后,会调用probe函数

      static int __init i2c_bus_virtual_init(void)
      {
      	int ret;
      
      	ret = platform_driver_register(&i2c_bus_virtual_driver);
      	if (ret)
      		printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
      
      	return ret;
      }
      
    • probe函数

      • 分配适配器

        g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);
        
      • 设置适配器

        g_adapter->owner = THIS_MODULE;
        g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
        g_adapter->nr = -1;
        g_adapter->algo = &i2c_bus_virtual_algo; // 算法结构体,很重要
        

        注意:i2c_bus_virtual_algo和重要下面会分析。

      • 注册适配器

        ret = i2c_add_adapter(adap); //不管adap->nr是什么,都动态设置adap->nr
        ret = i2c_add_numbered_adapter(adap); //如果adap->nr == -1 则动态分配nr; 否则使用该nr   
        

      整个probe函数如下:

      static int i2c_bus_virtual_probe(struct platform_device *pdev)
      {
      	/* get info from device tree, to set i2c_adapter/hardware  */
      
      	/* alloc, set, register i2c_adapter */
      	g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);
      
      	g_adapter->owner = THIS_MODULE;
      	g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
      	g_adapter->nr = -1;
      	snprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");
      
      	g_adapter->algo = &i2c_bus_virtual_algo;
      
      	i2c_add_adapter(g_adapter); // i2c_add_numbered_adapter(g_adapter);
      
      	return 0;
      }
      
    • remove函数如下

      static int i2c_bus_virtual_remove(struct platform_device *pdev)
      {
      	i2c_del_adapter(g_adapter); // 反注册适配器
      	return 0;
      }
      
    • 出口函数,unregister平台驱动,

      static void __exit i2c_bus_virtual_exit(void)
      {
      	platform_driver_unregister(&i2c_bus_virtual_driver);
      }
      
  3. 算法结构体分析:

    上面介绍了在probe函数中分配设置i2c_adapter结构体,该结构体内如如下:

    ../../_images/012_i2c_adapter.png

    从图中可以看到里面有个i2c_algorithm结构体。内容如下:

    ../../_images/058_i2c_algorithm.pngimage-20210303121043020

    • master_xfer:这是最重要的函数,它实现了一般的I2C传输,用来传输一个或多个i2c_msg

    • master_xfer_atomic:

      可选的函数,功能跟master_xfer一样,在atomic context环境下使用,比如在关机之前、所有中断都关闭的情况下,用来访问电源管理芯片

    • smbus_xfer:实现SMBus传输,如果不提供这个函数,SMBus传输会使用master_xfer来模拟

    • smbus_xfer_atomic:

      可选的函数,功能跟smbus_xfer一样,在atomic context环境下使用,比如在关机之前、所有中断都关闭的情况下,用来访问电源管理芯片

    • functionality:返回所支持的flags:各类I2C_FUNC_*

    • reg_slave/unreg_slave:

      有些I2C Adapter也可工作与Slave模式,用来实现或模拟一个I2C设备

    有上面的分析可知我们构建我们自己的算法结构体i2c_bus_virtual_algo即可,如下:

    static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
    {
    	return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |
    	       I2C_FUNC_SMBUS_READ_BLOCK_DATA |
    	       I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
    	       I2C_FUNC_PROTOCOL_MANGLING;
    }
    
    
    const struct i2c_algorithm i2c_bus_virtual_algo = {
    	.master_xfer   = i2c_bus_virtual_master_xfer, // i2c 硬件实现读写数据
    	.functionality = i2c_bus_virtual_func,		  // 提供该i2c驱动支持的功能
    };
    

    下面就是重要的i2c_bus_virtual_master_xfer函数,里面如果是模拟的i2c,就会通过io拉时序,如果是硬件i2c,就会操作i2c寄存器,然后写或者读数据。大致的操作如下:

    static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adapter,
    						struct i2c_msg *msgs, int num)
    {
        for (i = 0; i < num; i++) {
            struct i2c_msg *msg = msgs[i];
            {
            	// 1. 設置控制寄存器 发出S信号: 设置寄存器发出S信号
                CTLREG = S;
    
                // 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号
                //    判断是否有ACK
    
                if (!ACK)
                    return ERROR;
                else {
    	            // 3. read / write
    	            if (read) {
                        STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方
                        val = DATAREG; // 这会发起I2C读操作
                    } else if(write) {
                        DATAREG = val; // 这会发起I2C写操作
                        val = STATUS;  // 判断是否收到ACK
                        if (!ACK)
                            return ERROR;
                    }                
                }
                // 4. 設置控制寄存器 发出P信号
                CTLREG = P;
            }
        }
    
        return i;
    }
    

整个驱动框架代碼如下:

#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
//#include <linux/i2c-algo-bit.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
//#include <linux/platform_data/i2c-gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

static struct i2c_adapter *g_adapter;

static int i2c_bus_virtual_master_xfer(struct i2c_adapter *i2c_adap,
		    struct i2c_msg msgs[], int num)
{
	// 這裏面和硬件有关系,具体的硬件自己添加 实现通过i2c控制器读写功能
}

static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |
	       I2C_FUNC_SMBUS_READ_BLOCK_DATA |
	       I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
	       I2C_FUNC_PROTOCOL_MANGLING;
}


const struct i2c_algorithm i2c_bus_virtual_algo = {
	.master_xfer   = i2c_bus_virtual_master_xfer,
	.functionality = i2c_bus_virtual_func,
};


static int i2c_bus_virtual_probe(struct platform_device *pdev)
{
	/* get info from device tree, to set i2c_adapter/hardware  */
	
	/* alloc, set, register i2c_adapter */
	g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);

	g_adapter->owner = THIS_MODULE;
	g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	g_adapter->nr = -1;
	snprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");

	g_adapter->algo = &i2c_bus_virtual_algo;

	i2c_add_adapter(g_adapter); // i2c_add_numbered_adapter(g_adapter);
	
	return 0;
}

static int i2c_bus_virtual_remove(struct platform_device *pdev)
{
	i2c_del_adapter(g_adapter);
	return 0;
}
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {
	{ .compatible = "100ask,i2c-bus-virtual", },
	{ /* sentinel */ }
};

static struct platform_driver i2c_bus_virtual_driver = {
	.driver		= {
		.name	= "i2c-gpio",
		.of_match_table	= of_match_ptr(i2c_bus_virtual_dt_ids),
	},
	.probe		= i2c_bus_virtual_probe,
	.remove		= i2c_bus_virtual_remove,
};


static int __init i2c_bus_virtual_init(void)
{
	int ret;

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ret = platform_driver_register(&i2c_bus_virtual_driver);
	if (ret)
		printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);

	return ret;
}
module_init(i2c_bus_virtual_init);

static void __exit i2c_bus_virtual_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_exit(i2c_bus_virtual_exit);

MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");