linux I2C驱动详解(linux i2c从设备驱动)

一、i2c驱动总览

i2c通讯广泛运用在主控对周围芯片的配置上,i2c协议比较简单,这里不加说明,但linux的i2c驱动实现相当复杂,下面是i2c驱动在整个系统中的位置以及读写流程。


二、i2c驱动相关结构体

i2c_adapter:soc的i2c控制器

i2c_algorithm:i2c控制器具体发送和接收i2c数据方法

i2c_client:i2c从设备,比如带i2c接口的温湿度传感器

i2c_driver:i2c从设备驱动,比如读取温湿度传感器的驱动

struct i2c_adapter {
    struct module *owner;             // 所有者
    unsigned int id;
    unsigned int class;               // 该适配器支持的从设备的类型
    const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法
    void *algo_data;

    /* data fields that are valid for all devices    */
    struct rt_mutex bus_lock;

    int timeout;              // 超时时间
    int retries;
    struct device dev;        // 该适配器设备对应的device,i2c控制器是实际存在的,所有有个device成员

    int nr;                   // 适配器的编号
    char name[48];            // 适配器的名字
    struct completion dev_released;

    struct list_head userspace_clients;  // 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
};
struct i2c_client {          //  用来描述一个从次设备
     unsigned short flags;        //  描述i2c从设备特性的标志位  
     unsigned short addr;         //  i2c 从设备的地址
                     
     char name[I2C_NAME_SIZE];    //  i2c从设备的名字
     struct i2c_adapter *adapter; //  指向与从设备匹配成功的适配器
     struct i2c_driver *driver;   //  指向与从设备匹配成功的设备驱动
     struct device dev;           //  该从设备对应的device,i2c从设备是个实际存在的,所以有device
     int irq;                     //  从设备的中断引脚
     struct list_head detected;   //  作为一个链表节点挂接到与他匹配成功的i2c_driver 相应的链表头上                
 };
struct i2c_driver {    // 代表一个i2c设备驱动
    unsigned int class;      // i2c设备驱动所支持的i2c设备的类型

    int (*attach_adapter)(struct i2c_adapter *);   // 用来匹配适配器的函数 adapter
    int (*detach_adapter)(struct i2c_adapter *);

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *); // 设备驱动层的probe函数
    int (*remove)(struct i2c_client *);                              // 设备驱动层卸载函数

    /* driver model interfaces that don't relate to enumeration  */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);

    void (*alert)(struct i2c_client *, unsigned int data);

    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;           //  该i2c设备驱动所对应的device_driver
    const struct i2c_device_id *id_table;  //  设备驱动层用来匹配设备的id_table

    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;    //  该设备驱动支持的所有次设备的地址数组
    struct list_head clients;              //  用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
};
struct i2c_algorithm {
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);	//i2c读写方法,有芯片厂家实现
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};

这四个结构体的关系如下所示:

三、i2c驱动的加载过程

下面以hi3559为例说明,linux内核代码版本是4.9.

先看一hi3559设备树中对i2c控制器的定义,设备树文件为\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi,i2c节点定义如下所示:

从设备节点的compatible信息可以找到hi3559 i2c驱动文件为\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\i2c\busses\i2c-hibvt.c

可以看到,i2c-hibvt.c中的hibvt_i2c_match[]的第一项compatible与设备树是对应的,这里的驱动入口函数就是hibvt_i2c_probe。先看一下hi3559私有的i2c驱动结构体定义:

struct hibvt_i2c_dev {
    struct device       *dev;
    struct i2c_adapter  adap;    //i2c控制器
    resource_size_t     phybase;  //hi3559 i2c控制器寄存器起始地址
    void __iomem        *base;    //经过地址转换的起始地址
    struct clk      *clk;
    int         irq;

    unsigned int        freq;
    struct i2c_msg      *msg;
    unsigned int        msg_num;
    unsigned int        msg_idx;
    unsigned int        msg_buf_ptr;
    struct completion   msg_complete;

    spinlock_t      lock;
    int         status;
};

hibvt_i2c_probe函数初始化都以这个结构体展开,里面最重要的就是struct i2c_adapter adap,这个是i2c控制器的通用抽象,相当于c++中的基类。其它的是该i2c控制器的物理信息,大部分都可以从设备树中读取。

static int hibvt_i2c_probe(struct platform_device *pdev)
{
    int status;
    struct hibvt_i2c_dev *i2c;
    struct i2c_adapter *adap;
    struct resource *res;

    i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
    if (!i2c) {
        return -ENOMEM;
    }

    platform_set_drvdata(pdev, i2c);
    i2c->dev = &pdev->dev;
    spin_lock_init(&i2c->lock);
    init_completion(&i2c->msg_complete);

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(i2c->dev, "Invalid mem resource./n");
        return -ENODEV;
    }

    i2c->phybase = res->start;	//从设备树中读取i2c的起始物理地址
    i2c->base = devm_ioremap_resource(&pdev->dev, res);//转换成虚拟地址
    if (IS_ERR(i2c->base)) {
        dev_err(i2c->dev, "cannot ioremap resource\n");
        return -ENOMEM;
    }

    i2c->clk = devm_clk_get(&pdev->dev, NULL);	//读取时钟
    if (IS_ERR(i2c->clk)) {
        dev_err(i2c->dev, "cannot get clock\n");
        return -ENOENT;
    }
    clk_prepare_enable(i2c->clk);

    if (of_property_read_u32(pdev->dev.of_node, "clock-frequency",
                             &i2c->freq)) {
        dev_warn(i2c->dev, "setting default clock-frequency@%dHz\n",
                 I2C_DEFAULT_FREQUENCY);
        i2c->freq = I2C_DEFAULT_FREQUENCY;
    }

    /* i2c controller initialization, disable interrupt */
    hibvt_i2c_hw_init(i2c);//初始化其它硬件相关参数

    i2c->irq = platform_get_irq(pdev, 0);	//获取中断信息
    status = devm_request_irq(&pdev->dev, i2c->irq, hibvt_i2c_isr,
                              IRQF_SHARED, dev_name(&pdev->dev), i2c);
    if (status) {
        dev_dbg(i2c->dev, "falling back to polling mode");
        i2c->irq = -1;
    }

    adap = &i2c->adap;
    i2c_set_adapdata(adap, i2c);
    adap->owner = THIS_MODULE;
    strlcpy(adap->name, "hibvt-i2c", sizeof(adap->name));
    adap->dev.parent = &pdev->dev;
    adap->dev.of_node = pdev->dev.of_node;
    adap->algo = &hibvt_i2c_algo;	//i2c读写方法

    /* Add the i2c adapter */
    status = i2c_add_adapter(adap);	//注册adapter
    if (status) {
        dev_err(i2c->dev, "failed to add bus to i2c core\n");
        goto err_add_adapter;
    }

    dev_info(i2c->dev, "%s%d@%dhz registered\n",
             adap->name, adap->nr, i2c->freq);

    return 0;

err_add_adapter:
    clk_disable_unprepare(i2c->clk);
    return status;
}

从该函数流程可以看出,主要做的工作是:

1、从设备树读取i2c控制器相关参数填充到hibvt_i2c_dev上

2、需要提前实现一个hibvt_i2c_algo,这个是对i2c控制器的读写方法

3、调用i2c_add_adapter注册adapter

int i2c_add_adapter(struct i2c_adapter *adapter)
{
	struct device *dev = &adapter->dev;
	int id;

	if (dev->of_node) {
		id = of_alias_get_id(dev->of_node, "i2c");
		printk("yy i2c name =%s, id = %d\n", dev->init_name, id);
		if (id >= 0) {
			adapter->nr = id;
			return __i2c_add_numbered_adapter(adapter);
		}
	}

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adapter,
		       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (WARN(id < 0, "couldn't get idr"))
		return id;

	adapter->nr = id;

	return i2c_register_adapter(adapter);
}

of_alias_get_id函数会去设备树中查找i2c控制器的id号,以为一个soc里面集成多个i2c控制器,需要给每个控制器分配一个编号。这个函数是从hi3559av100-demb.dts中去查找aliases节点的i2c编号,这里adapter->nr的值会是0~19。如下图所示:


__i2c_add_numbered_adapter这个函数调用 idr_alloc 使 ID 号和 adapter 结构体按照 IDR 机制关联起来。IDR 用类基数树结构来构造一个稀疏数组,以 ID 为索引找到对应数组元素,进而找到对应的数据结构指针,这里面具体没研究过。最后调用了i2c_register_adapter继续完成注册。i2c_register_adapter这个函数主要完成以下工作:

1、初始化struct i2c_adapter的一些成员变量

2、调用of_i2c_register_devices查找i2c节点下是否有i2c从设备,如果有则生成i2c_client

四、i2c的节点生成

i2c驱动最终会在/dev下生成i2c字符设备节点,/dev/i2c-0、/dev/i2c-1 等。生成这些节点的驱动在drivers/i2c/i2c-dev.c中,我们看一下是怎么生成的。

这个文件里有个代码i2c字符设备的结构体

struct i2c_dev {
	struct list_head list;		//所有i2c字符设备都加入到链表i2c_dev_list中
	struct i2c_adapter *adap;	//i2c 控制器
	struct device *dev;	//dev目录下的设备节点
	struct cdev cdev; //字符设备
};

在i2c-dev.c文件中,会调用module_init加载i2c_dev_init,

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");
//申请主设备号为I2C_MAJOR的I2C_MINORS个i2c设备号
	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;

	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;

	/* 初始化i2c总线的通知链,i2c_register_adapter函数中在调用device_register注册adap->dev时会启动总线通知*/
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* 那些已经注册完的adap,这里会遍历i2c_bus_type上所有adap,然后注册字符设备 */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

i2cdev_notifier里面的回调函数是i2cdev_notifier_call,该函数里面有两个函数,一个是注册一个i2c字符设备,另一个是删除。下面看一下注册这个函数

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;

	if (dev->type != &i2c_adapter_type)
		return 0;
	adap = to_i2c_adapter(dev);

	i2c_dev = get_free_i2c_dev(adap);	//申请一个i2c_dev,并将该i2c_dev加入到i2c_dev_list中
	if (IS_ERR(i2c_dev))
		return PTR_ERR(i2c_dev);

	cdev_init(&i2c_dev->cdev, &i2cdev_fops);	//初始化字符设备
	i2c_dev->cdev.owner = THIS_MODULE;
	res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);	//添加一个字符设备
	if (res)
		goto error_cdev;

	/* register this i2c device with the driver core */
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,	//在/dev下生成i2c字符设备节点
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
	if (IS_ERR(i2c_dev->dev)) {
		res = PTR_ERR(i2c_dev->dev);
		goto error;
	}

	pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
		 adap->name, adap->nr);
	return 0;
error:
	cdev_del(&i2c_dev->cdev);
error_cdev:
	put_i2c_dev(i2c_dev);
	return res;
}

最后就是i2cdev_fops的这几个open、read、write、ioctl回调函数了。

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

下面看一下i2cdev_open这个函数

static int i2cdev_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);//通过inode获取次设备号
	struct i2c_client *client;
	struct i2c_adapter *adap;

	adap = i2c_get_adapter(minor);//通过次设备号获取adpa,以为adpa->nr就是次设备号
	if (!adap)
		return -ENODEV;

	/* This creates an anonymous i2c_client, which may later be
	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
	 *
	 * This client is ** NEVER REGISTERED ** with the driver model
	 * or I2C core code!!  It just holds private copies of addressing
	 * information and maybe a PEC flag.
	 */
	client = kzalloc(sizeof(*client), GFP_KERNEL);	//这里会申请一个i2c client
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

	client->adapter = adap;
	file->private_data = client;

	return 0;
}

下面看一下i2cdev_write

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = file->private_data;	//通过file中的私有数据得到i2c client ,在open时候已经将该私有数据设置成cilent

	if (count > 8192)
		count = 8192;

	if (count == 0)
		return -EINVAL;

	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
		iminor(file_inode(file)), count);

	ret = i2c_master_send(client, tmp, count);	//调用该函数完成数据传输即可
	kfree(tmp);
	return ret;
}