龙空技术网

linux I2C驱动详解

YY蚂蚁 892

前言:

今天各位老铁们对“linux yy语音”大体比较讲究,小伙伴们都想要知道一些“linux yy语音”的相关资讯。那么小编也在网络上收集了一些关于“linux yy语音””的相关文章,希望小伙伴们能喜欢,你们一起来学习一下吧!

一、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;}

标签: #linux yy语音