ASoC驱动开发 之 Codec芯片ALC5677 驱动代码分析
来源:互联网 发布:淘宝搜索你会感谢我的 编辑:程序博客网 时间:2024/05/22 17:12
【补充】
关于 ASoC Codec驱动代码框架更详细的介绍可以阅读《ASoC Codec 驱动代码框架图》。(2016年9月7日 添加)
【前言】
Linux下的音频驱动多采用 ASoC 架构。在这个架构里,驱动分 Platform、Codec、Machine 这 3 部分,相关介绍可以参见前文《Linux AsoC音频驱动架构 及 Machine驱动代码分析》。本文分析的是 Codec 部分,即音频编解码芯片的驱动代码。这里用作例子的芯片型号为 ALC5677。
如前文《Linux I2C总线框架 学习笔记》所述,编写 I2C 驱动时会添加 2 个模块——Bus Driver 和 Device Driver。分别添加 client 结构体相关信息和实现 driver 函数如 (*probe)、(*remove) 等。
【client 设备注册流程】
先来看看 client 部分的内容。在源文件 rt5677.c 末尾可以看到如下一行代码:
device_initcall(rt5677_i2c_dev_init);
这里的代码入口不是我们常见的 module_init(fn) 方式,他们区别在哪里呢?使用 SourceInsight 可以很方便地跳转到 device_initcall(fn) 定义处,可以看到下面这段预处理代码:
#ifndef MODULE /* 如果没有定义 MODULE */#ifndef __ASSEMBLY__…#define fs_initcall(fn) __define_initcall(fn,5)#define fs_initcall_sync(fn) __define_initcall(fn, 5s)#define rootfs_initcall(fn) __define_initcall(fn, rootfs)#define device_initcall(fn) __define_initcall(fn,6)#define device_initcall_sync(fn) __define_initcall(fn, 6s) /* 注意 */#define late_initcall(fn) __define_initcall(fn, 7)#define late_initcall_sync(fn) __define_initcall(fn, 7s)…#endif /* __ASSEMBLY__ */#define module_init(x) __initcall(x);#define module_exit(x) __exitcall(x);#else /* MODULE*/ /* 如果定义了 MODULE */…#define subsys_initcall(fn) module_init(fn)#define fs_initcall(fn) module_init(fn)#define rootfs_initcall(fn) module_init(fn)#define device_initcall(fn) module_init(fn) /* 注意 */#define late_initcall(fn) module_init(fn)#define console_initcall(fn) module_init(fn)#define security_initcall(fn) module_init(fn)#define module_init(initfn) \ staticinline initcall_t __inittest(void) \ {return initfn; } \ intinit_module(void) __attribute__((alias(#initfn)));#define module_exit(exitfn) \ staticinline exitcall_t __exittest(void) \ {return exitfn; } \ voidcleanup_module(void) __attribute__((alias(#exitfn)));…#endif
可以看到,在定义了 MODULE 的情况下,device_inticall(fn) 就是 module_init(fn),而在另一种情况下是 __define_initcall(fn, 6)。对于后者,其中的参数值 6 表示 id,取值范围从 0~7s 共 17 种。这个 id 值越大,模块被内核加载的时间越晚。有的时候,一个模块 a 的正常工作需要依赖于模块 b,这种情况下就可以使用 __define_initcall(fn, id) 来规定模块的加载顺序。
继续分析,进入 rt5677_i2c_dev_init() 函数查看 rt5677 驱动模块初始化流程。代码如下:
static int __init rt5677_i2c_dev_init(void){ int i2c_busnum = 0; struct i2c_board_info i2c_info; struct i2c_adapter *adapter; ... memset(&i2c_info,0, sizeof(i2c_info)); strncpy(i2c_info.type,"rt5677", strlen("rt5677")); i2c_info.addr = 0x2c; /* 101100X. 读周期 X 为 1, 写周期 X 为 0 */ ... adapter = i2c_get_adapter(i2c_busnum); if(adapter) { if(i2c_new_device(adapter, &i2c_info)){ printk("addnew i2c device %s , addr 0x%x\n", "rt5677", i2c_info.addr); return0; }else{ printk("addnew i2c device %s , addr 0x%x fail !!!\n", "rt5677",i2c_info.addr); } }else{ printk("[%s]getadapter %d fail\n",__func__, i2c_busnum); return-EINVAL; }}
代码很短,流程很清楚。首先手动将 alc5677 的 i2c_busnum 划分为 0 ,然后给板级信息结构体 i2c_info 初始化 I2C 地址,值为 0x2c。该地址是芯片的 datasheet 里给定的,其中 I2C_ADDR 位对应的引脚电平为低电平,所以值为0,如下图:
之后,获取用于管理挂接在编号为 0 的 i2c_busnum 下的 i2c 设备的适配器(没看懂?可以参考一下《LinuxI2C总线框架 学习笔记》这篇文章)。如果这里获取适配器 adapter 失败,则直接退出,返回值为 -EINVAL; 如果获取成功,则调用 i2c_new_device(adapter, &i2c_info) 函数为该 adapter 添加一个内容与编解码芯片 ALC5677 一致的 client。相应代码如下:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_infoconst *info){ struct i2c_client *client; int status; client = kzalloc(sizeof *client,GFP_KERNEL); if (!client) return NULL; client->adapter = adap; // 将 adapter 和 client 绑定 client->dev.platform_data = info->platform_data; if (info->archdata) client->dev.archdata = *info->archdata; client->flags = info->flags; // 将板级信息结构体中的配置对应写入 client client->addr = info->addr; client->irq = info->irq; client->irq_flags = info->irq_flags; ... strlcpy(client->name,info->type, sizeof(client->name)); /* Check for address validity */ status = i2c_check_client_addr_validity(client); if (status) { dev_err(&adap->dev,"Invalid %d-bit I2C address 0x%02hx\n", client->flags& I2C_CLIENT_TEN ? 10 : 7, client->addr); goto out_err_silent; } /* Check for address business */ status = i2c_check_addr_busy(adap,client->addr); if (status) goto out_err; client->dev.parent = &client->adapter->dev; client->dev.bus = &i2c_bus_type; client->dev.type = &i2c_client_type; client->dev.of_node = info->of_node; ... status = device_register(&client->dev); /* 注册 client 设备 */ if (status) goto out_err; dev_dbg(&adap->dev, "client[%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); return client; out_err: dev_err(&adap->dev, "Failedto register i2c client %s at 0x%02x " "(%d)\n",client->name, client->addr, status);out_err_silent: kfree(client); return NULL;}
可以看到在这个函数里,1 个新的 client 被创建并被关联到 adapter 上,相应的 i2c 地址、中断号等信息都被赋为 info 结构体里的值。在所需 client 结构体的成员都被初始化之后,调用 device_register(&client->dev) 函数对 client 设备进行注册。至此,设备注册完成,即 client 部分代码结束。
【driver 驱动注册流程】
再来看看 driver 函数相关的内容。在源文件 rt5677.c 中,我们还能看到下面这样一句代码:
module_i2c_driver(rt5677_i2c_driver);
这是一个宏定义,对其进行追踪可以看到如下声明:
(〇)driver 结构体定义:
struct i2c_driver rt5677_i2c_driver = { .driver= { .name = "rt5677", .owner = THIS_MODULE,#ifdef CONFIG_PM .pm = &rt5677_pm_ops,#endif#ifdef RTACPI_I2C .acpi_match_table = ACPI_PTR(rt5677_acpi_match),#endif }, .probe = rt5677_i2c_probe, /* 关联 probe 函数 */ .remove = rt5677_i2c_remove, /* 关联 remove 函数 */ .shutdown = rt5677_i2c_shutdown, /* 关联 shutdown 函数 */ .id_table = rt5677_i2c_id, /* 所支持设备的 ID 表 */};
(一)module_i2c_driver() 宏:
/** *module_i2c_driver() - Helper macro for registering a I2C driver *@__i2c_driver: i2c_driver struct * *Helper macro for I2C drivers which do not do anything special in module *init/exit. This eliminates a lot of boilerplate. Each module may only *use this macro once, and calling it replaces module_init() and module_exit() */#define module_i2c_driver(__i2c_driver)\ module_driver(__i2c_driver,i2c_add_driver, \ i2c_del_driver)
(二)module_driver() 宏:
#define module_driver(__driver, __register, __unregister, ...) \static int __init__driver##_init(void) \{ \ return__register(&(__driver) ,##__VA_ARGS__); \} \module_init(__driver##_init); \ /* 模块入口声明 */static void __exit __driver##_exit(void) \{ \ __unregister(&(__driver), ##__VA_ARGS__); \} \module_exit(__driver##_exit);
(三)i2c_add_driver() 函数:
/* use a define to avoid include chaining to get THIS_MODULE */#define i2c_add_driver(driver)\ i2c_register_driver(THIS_MODULE,driver)
/* * Ani2c_driver is used with one or more i2c_client (device) nodes to access *i2c slave chips, on a bus instance associated with some i2c_adapter. */int i2c_register_driver(structmodule *owner, struct i2c_driver *driver){ int res; /* Can't register until after driver model init */ if(unlikely(WARN_ON(!i2c_bus_type.p))) return-EAGAIN; /*add the driver to the list of i2c drivers in the driver core */ driver->driver.owner = owner; driver->driver.bus = &i2c_bus_type; /*When registration returns, the driver core * will have called probe() for allmatching-but-unbound devices. */ res = driver_register(&driver->driver); /* 驱动注册。注册成功返回 0 */ if(res) return res; ... INIT_LIST_HEAD(&driver->clients); /* Walk the adapters that are already present */ i2c_for_each_dev(driver,__process_new_driver); return 0;}EXPORT_SYMBOL(i2c_register_driver);
通过层层传递,我们找到了 driver 函数注册相关代码的起始点—— i2c_register_driver() 函数。在这个函数里,我们传入了 driver 结构体并调用函数 driver_register() 对驱动进行了注册。至此,表层的驱动注册完成,即 driver 函数相关代码结束。
实际上,源文件 rt5677.c 中还有类似 rt5677_probe()、rt5677_remove() 这类更底层一些的驱动函数,我们在这篇文章里暂时先不讨论,随着代码阅读和学习的深入,以后我再找时间单独写一篇。
【扩展阅读】
[1] 《postcore_initcall(), arch_initcall(), subsys_initcall(),device_initcall() 调用顺序》
[2] 《linux内核__define_initcall分析》
- ASoC驱动开发 之 Codec芯片ALC5677 驱动代码分析
- ASoC Codec驱动代码框架图
- Android音频驱动-ASOC之Codec
- linux驱动:音频驱动(五)ASoc之codec驱动
- linux驱动开发: wm8960 codec代码分析
- linux驱动开发: wm8960 codec代码分析
- linux驱动:音频驱动(六)ASoc之codec设备
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
- 【u109】数字生成游戏(gen)
- MySql常用函数数学函数、加密函数等
- 快速幂算法
- 219.leetcode Contains Duplicate II(easy)[数组 滑动窗口]
- aggregate vs treeAggregate
- ASoC驱动开发 之 Codec芯片ALC5677 驱动代码分析
- (三)、ZooKeeper 命令操作
- Android S端双向配置证书
- 打造简单实用的Thinkphp分页样式(Bootstrap版本)
- 使用Damerau-Levenshtein自动机实现字符串模糊查询
- JEECMSv6源码导入eclipse步骤图文详解
- 闲聊javaweb之servlet
- Javacript 对元素赋值的处理
- yii 分页