前言:
今天各位老铁们对“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语音